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