]> git.tdb.fi Git - libs/gui.git/blob - source/gbase/window.cpp
b9e3fa49790df373fd08b2420b5d908f6dced860
[libs/gui.git] / source / gbase / window.cpp
1 /* $Id$
2
3 This file is part of libmspgbase
4 Copyright © 2007-2008  Mikko Rasa, Mikkosoft Productions
5 Distributed under the LGPL
6 */
7
8 #include <vector>
9 #ifndef WIN32
10 #include <X11/Xatom.h>
11 #include <X11/Xutil.h>
12 #else
13 #include <windowsx.h>
14 #endif
15 #include <msp/core/application.h>
16 #include <msp/core/except.h>
17 #include "display.h"
18 #include "window.h"
19 #include "display_priv.h"
20
21 using namespace std;
22
23 namespace {
24
25 #ifdef WIN32
26 LRESULT CALLBACK wndproc_(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
27 {
28         if(msg==WM_CREATE)
29         {
30                 CREATESTRUCT *cs=reinterpret_cast<CREATESTRUCT *>(lparam);
31                 SetWindowLong(hwnd, 0, reinterpret_cast<LONG>(cs->lpCreateParams));
32         }
33         else
34         {
35                 Msp::Graphics::Window *wnd=reinterpret_cast<Msp::Graphics::Window *>(GetWindowLong(hwnd, 0));
36                 Msp::Graphics::Window::Event ev;
37                 ev.msg=msg;
38                 ev.wparam=wparam;
39                 ev.lparam=lparam;
40                 if(wnd && wnd->event(ev))
41                         return 0;
42         }
43
44         return DefWindowProc(hwnd, msg, wparam, lparam);
45 }
46 #else
47 Bool match_event_type(Display *, XEvent *event, XPointer arg)
48 {
49         return event->type==reinterpret_cast<int>(arg);
50 }
51 #endif
52
53 }
54
55 namespace Msp {
56 namespace Graphics {
57
58 WindowOptions::WindowOptions():
59         width(640),
60         height(480),
61         fullscreen(false),
62         resizable(false)
63 { }
64
65
66 Window::Window(Display &dpy, unsigned w, unsigned h, bool fs):
67         display(dpy)
68 {
69         options.width=w;
70         options.height=h;
71         options.fullscreen=fs;
72
73         init();
74 }
75
76 Window::Window(Display &dpy, const WindowOptions &opts):
77         display(dpy),
78         options(opts)
79 {
80         init();
81 }
82
83 Window::~Window()
84 {
85         if(priv->window)
86 #ifdef WIN32
87                 CloseWindow(priv->window);
88 #else
89                 XDestroyWindow(display.get_private().display, priv->window);
90
91         if(priv->invisible_cursor)
92                 XFreeCursor(display.get_private().display, priv->invisible_cursor);
93 #endif
94
95         display.remove_window(*this);
96
97         if(options.fullscreen)
98                 display.restore_mode();
99 }
100
101 void Window::set_title(const string &title)
102 {
103 #ifdef WIN32
104         SetWindowText(priv->window, title.c_str());
105 #else
106         vector<unsigned char> buf(title.begin(), title.end());
107         XTextProperty prop;
108         prop.value=&buf[0];
109         prop.encoding=XA_STRING;
110         prop.format=8;
111         prop.nitems=title.size();
112         XSetWMName(display.get_private().display, priv->window, &prop);
113         display.check_error();
114 #endif
115 }
116
117 void Window::reconfigure(const WindowOptions &opts)
118 {
119         bool fullscreen_changed=(opts.fullscreen!=options.fullscreen);
120
121         options=opts;
122
123 #ifdef WIN32
124         RECT rect;
125         SetRect(&rect, 0, 0, options.width, options.height);
126
127         int style=(options.fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
128         if(!options.resizable)
129                 style&=~WS_THICKFRAME;
130         int exstyle=(options.fullscreen ? WS_EX_APPWINDOW : WS_EX_OVERLAPPEDWINDOW);
131         AdjustWindowRectEx(&rect, style, false, exstyle);
132
133         if(fullscreen_changed)
134         {
135                 hide();
136                 SetWindowLong(priv->window, GWL_EXSTYLE, exstyle);
137                 SetWindowLong(priv->window, GWL_STYLE, style);
138                 show();
139         }
140
141         if(options.fullscreen)
142                 SetWindowPos(priv->window, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOZORDER);
143         else
144                 SetWindowPos(priv->window, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER);
145 #else
146         ::Display *dpy=display.get_private().display;
147
148         if(fullscreen_changed)
149         {
150                 hide();
151
152                 // Wait for the window to be unmapped.  This makes window managers happy.
153                 XEvent ev;
154                 XPeekIfEvent(dpy, &ev, match_event_type, reinterpret_cast<XPointer>(UnmapNotify));
155
156                 XSetWindowAttributes attr;
157                 attr.override_redirect=options.fullscreen;
158                 XChangeWindowAttributes(dpy, priv->window, CWOverrideRedirect, &attr);
159                 show();
160         }
161
162         XSizeHints hints;
163         if(options.resizable)
164                 hints.flags=0;
165         else
166         {
167                 hints.flags=PMinSize|PMaxSize;
168                 hints.min_width=hints.max_width=options.width;
169                 hints.min_height=hints.max_height=options.height;
170         }
171         XSetWMNormalHints(dpy, priv->window, &hints);
172
173         if(options.fullscreen)
174                 XMoveResizeWindow(dpy, priv->window, 0, 0, options.width, options.height);
175         else
176                 XResizeWindow(dpy, priv->window, options.width, options.height);
177 #endif
178
179         if(options.fullscreen)
180                 display.set_mode(VideoMode(options.width, options.height));
181         else if(fullscreen_changed)
182                 display.restore_mode();
183 }
184
185 void Window::show_cursor(bool s)
186 {
187 #ifdef WIN32
188         ShowCursor(s);
189 #else
190         ::Display *dpy=display.get_private().display;
191
192         if(s)
193                 XUndefineCursor(dpy, priv->window);
194         else
195         {
196                 if(!priv->invisible_cursor)
197                 {
198                         int screen=DefaultScreen(dpy);
199
200                         Pixmap pm=XCreatePixmap(dpy, priv->window, 1, 1, 1);
201                         GC gc=XCreateGC(dpy, pm, 0, 0);
202                         XSetFunction(dpy, gc, GXclear);
203                         XDrawPoint(dpy, pm, gc, 0, 0);
204                         XFreeGC(dpy, gc);
205
206                         XColor black;
207                         black.pixel=BlackPixel(dpy, screen);
208                         XQueryColor(dpy, DefaultColormap(dpy, screen), &black);
209
210                         priv->invisible_cursor=XCreatePixmapCursor(dpy, pm, pm, &black, &black, 0, 0);
211
212                         XFreePixmap(dpy, pm);
213                 }
214                 XDefineCursor(dpy, priv->window, priv->invisible_cursor);
215         }
216 #endif
217 }
218
219 void Window::show()
220 {
221 #ifdef WIN32
222         ShowWindow(priv->window, SW_SHOWNORMAL);
223 #else
224         XMapRaised(display.get_private().display, priv->window);
225 #endif
226 }
227
228 void Window::hide()
229 {
230 #ifdef WIN32
231         ShowWindow(priv->window, SW_HIDE);
232 #else
233         XUnmapWindow(display.get_private().display, priv->window);
234 #endif
235 }
236
237 void Window::init()
238 {
239         priv=new Private;
240
241 #ifdef WIN32
242         static bool wndclass_created=false;
243
244         if(!wndclass_created)
245         {
246                 WNDCLASSEX wndcl;
247
248                 wndcl.cbSize=sizeof(WNDCLASSEX);
249                 wndcl.style=0;
250                 wndcl.lpfnWndProc=&wndproc_;
251                 wndcl.cbClsExtra=0;
252                 wndcl.cbWndExtra=sizeof(Window *);
253                 wndcl.hInstance=reinterpret_cast<HINSTANCE>(Application::get_data());
254                 wndcl.hIcon=0;
255                 wndcl.hCursor=LoadCursor(0, IDC_ARROW);
256                 wndcl.hbrBackground=0;
257                 wndcl.lpszMenuName=0;
258                 wndcl.lpszClassName="mspgbase";
259                 wndcl.hIconSm=0;
260
261                 if(!RegisterClassEx(&wndcl))
262                         throw Exception("Couldn't register window class");
263
264                 wndclass_created=true;
265         }
266
267         RECT rect;
268         SetRect(&rect, 0, 0, options.width, options.height);
269
270         int style=(options.fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
271         if(!options.resizable)
272                 style&=~WS_THICKFRAME;
273         int exstyle=(options.fullscreen ? WS_EX_APPWINDOW : WS_EX_OVERLAPPEDWINDOW);
274         AdjustWindowRectEx(&rect, style, false, exstyle);
275
276         priv->window=CreateWindowEx(exstyle,
277                 "mspgbase",
278                 "Window",
279                 style,
280                 CW_USEDEFAULT, CW_USEDEFAULT,
281                 rect.right-rect.left, rect.bottom-rect.top,
282                 0,
283                 0,
284                 reinterpret_cast<HINSTANCE>(Application::get_data()),
285                 this);
286         if(!priv->window)
287                 throw Exception("CreateWindowEx failed");
288
289         if(options.fullscreen)
290                 display.set_mode(VideoMode(options.width, options.height));
291
292 #else
293         ::Display *dpy=display.get_private().display;
294
295         priv->wm_delete_window=XInternAtom(dpy, "WM_DELETE_WINDOW", true);
296         priv->invisible_cursor=0;
297
298         XSetWindowAttributes attr;
299         attr.override_redirect=options.fullscreen;
300         attr.event_mask=ButtonPressMask|ButtonReleaseMask|PointerMotionMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask|EnterWindowMask;
301
302         priv->window=XCreateWindow(dpy,
303                 DefaultRootWindow(dpy),
304                 0, 0,
305                 options.width, options.height,
306                 0,
307                 CopyFromParent,
308                 InputOutput,
309                 CopyFromParent,
310                 CWOverrideRedirect|CWEventMask, &attr);
311
312         XSetWMProtocols(dpy, priv->window, &priv->wm_delete_window, 1);
313
314         if(!options.resizable)
315         {
316                 XSizeHints hints;
317                 hints.flags=PMinSize|PMaxSize;
318                 hints.min_width=hints.max_width=options.width;
319                 hints.min_height=hints.max_height=options.height;
320                 XSetWMNormalHints(dpy, priv->window, &hints);
321         }
322
323         if(options.fullscreen)
324         {
325                 display.set_mode(VideoMode(options.width, options.height));
326                 XWarpPointer(dpy, None, priv->window, 0, 0, 0, 0, options.width/2, options.height/2);
327         }
328 #endif
329
330         display.add_window(*this);
331         display.check_error();
332 }
333
334 bool Window::event(const Event &evnt)
335 {
336 #ifdef WIN32
337         WPARAM wp=evnt.wparam;
338         LPARAM lp=evnt.lparam;
339         switch(evnt.msg)
340         {
341         case WM_KEYDOWN:
342                 signal_key_press.emit(wp, 0, wp);
343                 break;
344         case WM_KEYUP:
345                 signal_key_release.emit(wp, 0);
346                 break;
347         case WM_LBUTTONDOWN:
348                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 1, 0);
349                 break;
350         case WM_LBUTTONUP:
351                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 1, 0);
352                 break;
353         case WM_MBUTTONDOWN:
354                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 2, 0);
355                 break;
356         case WM_MBUTTONUP:
357                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 2, 0);
358                 break;
359         case WM_RBUTTONDOWN:
360                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 3, 0);
361                 break;
362         case WM_RBUTTONUP:
363                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 3, 0);
364                 break;
365         case WM_MOUSEMOVE:
366                 signal_pointer_motion.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
367                 break;
368         case WM_SIZE:
369                 options.width=LOWORD(lp);
370                 options.height=HIWORD(lp);
371                 signal_resize.emit(options.width, options.height);
372                 break;
373         case WM_CLOSE:
374                 signal_close.emit();
375                 break;
376         default:
377                 return false;
378         }
379 #else
380         const XEvent &ev=evnt.xevent;
381         switch(ev.type)
382         {
383         case ButtonPress:
384                 signal_button_press.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
385                 break;
386         case ButtonRelease:
387                 signal_button_release.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
388                 break;
389         case MotionNotify:
390                 signal_pointer_motion.emit(ev.xmotion.x, ev.xmotion.y);
391                 break;
392         case KeyPress:
393                 {
394                         char buf[16];
395                         XLookupString(const_cast<XKeyEvent *>(&ev.xkey), buf, sizeof(buf), 0, 0);
396                         // XXX Handle the result according to locale
397                         signal_key_press.emit(XKeycodeToKeysym(display.get_private().display, ev.xkey.keycode, 0), ev.xkey.state, buf[0]);
398                 }
399                 break;
400         case KeyRelease:
401                 signal_key_release.emit(XKeycodeToKeysym(display.get_private().display, ev.xkey.keycode, 0), ev.xkey.state);
402                 break;
403         case ConfigureNotify:
404                 options.width=ev.xconfigure.width;
405                 options.height=ev.xconfigure.height;
406                 signal_resize.emit(options.width, options.height);
407                 break;
408         case ClientMessage:
409                 if(ev.xclient.data.l[0]==static_cast<long>(priv->wm_delete_window))
410                         signal_close.emit();
411                 break;
412         case EnterNotify:
413                 if(options.fullscreen)
414                         XSetInputFocus(display.get_private().display, priv->window, RevertToParent, CurrentTime);
415                 break;
416         case MapNotify:
417                 if(options.fullscreen)
418                         XGrabPointer(display.get_private().display, priv->window, true, None, GrabModeAsync, GrabModeAsync, priv->window, None, CurrentTime);
419                 break;
420         default:
421                 return false;
422         }
423 #endif
424         return true;
425 }
426
427 } // namespace Graphics
428 } // namespace Msp