]> git.tdb.fi Git - libs/gui.git/blob - source/gbase/window.cpp
ad130d9eb3c08a27d319c4f1f8b7adeed8967e9c
[libs/gui.git] / source / gbase / window.cpp
1 /* $Id$
2
3 This file is part of libmspgbase
4 Copyright © 2007-2010  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                         int ev_type=UnmapNotify;
256                         XPeekIfEvent(dpy, &ev, match_event_type, reinterpret_cast<char *>(&ev_type));
257                 }
258
259                 XSetWindowAttributes attr;
260                 attr.override_redirect=options.fullscreen;
261                 XChangeWindowAttributes(dpy, priv->window, CWOverrideRedirect, &attr);
262         }
263
264         XSizeHints hints;
265         if(options.resizable)
266                 hints.flags=0;
267         else
268         {
269                 hints.flags=PMinSize|PMaxSize;
270                 hints.min_width=hints.max_width=options.width;
271                 hints.min_height=hints.max_height=options.height;
272         }
273         XSetWMNormalHints(dpy, priv->window, &hints);
274
275         if(options.fullscreen)
276                 XMoveResizeWindow(dpy, priv->window, 0, 0, options.width, options.height);
277         else
278                 XResizeWindow(dpy, priv->window, options.width, options.height);
279
280         if(fullscreen_changed)
281         {
282                 if(was_visible)
283                         show();
284         }
285 #endif
286
287         if(visible)
288         {
289                 if(options.fullscreen)
290                         display.set_mode(VideoMode(options.width, options.height));
291                 else if(fullscreen_changed)
292                         display.restore_mode();
293         }
294 }
295
296 void Window::set_keyboard_autorepeat(bool r)
297 {
298         kbd_autorepeat=r;
299 }
300
301 void Window::show_cursor(bool s)
302 {
303 #ifdef WIN32
304         ShowCursor(s);
305 #else
306         ::Display *dpy=display.get_private().display;
307
308         if(s)
309                 XUndefineCursor(dpy, priv->window);
310         else
311         {
312                 if(!priv->invisible_cursor)
313                 {
314                         int screen=DefaultScreen(dpy);
315
316                         Pixmap pm=XCreatePixmap(dpy, priv->window, 1, 1, 1);
317                         GC gc=XCreateGC(dpy, pm, 0, 0);
318                         XSetFunction(dpy, gc, GXclear);
319                         XDrawPoint(dpy, pm, gc, 0, 0);
320                         XFreeGC(dpy, gc);
321
322                         XColor black;
323                         black.pixel=BlackPixel(dpy, screen);
324                         XQueryColor(dpy, DefaultColormap(dpy, screen), &black);
325
326                         priv->invisible_cursor=XCreatePixmapCursor(dpy, pm, pm, &black, &black, 0, 0);
327
328                         XFreePixmap(dpy, pm);
329                 }
330                 XDefineCursor(dpy, priv->window, priv->invisible_cursor);
331         }
332 #endif
333 }
334
335 void Window::warp_pointer(int x, int y)
336 {
337 #ifndef WIN32
338         XWarpPointer(display.get_private().display, None, priv->window, 0, 0, 0, 0, x, y);
339 #else
340         (void)x;
341         (void)y;
342 #endif
343 }
344
345 void Window::show()
346 {
347 #ifdef WIN32
348         ShowWindow(priv->window, SW_SHOWNORMAL);
349 #else
350         XMapRaised(display.get_private().display, priv->window);
351 #endif
352         visible=true;
353
354         if(options.fullscreen)
355         {
356                 display.set_mode(VideoMode(options.width, options.height));
357 #ifndef WIN32
358                 XWarpPointer(display.get_private().display, None, priv->window, 0, 0, 0, 0, options.width/2, options.height/2);
359 #endif
360         }
361 }
362
363 void Window::hide()
364 {
365 #ifdef WIN32
366         ShowWindow(priv->window, SW_HIDE);
367 #else
368         XUnmapWindow(display.get_private().display, priv->window);
369 #endif
370         visible=false;
371
372         if(options.fullscreen)
373                 display.restore_mode();
374 }
375
376 bool Window::event(const Event &evnt)
377 {
378 #ifdef WIN32
379         WPARAM wp=evnt.wparam;
380         LPARAM lp=evnt.lparam;
381         switch(evnt.msg)
382         {
383         case WM_KEYDOWN:
384                 signal_key_press.emit(wp, 0, wp);
385                 break;
386         case WM_KEYUP:
387                 signal_key_release.emit(wp, 0);
388                 break;
389         case WM_LBUTTONDOWN:
390                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 1, 0);
391                 break;
392         case WM_LBUTTONUP:
393                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 1, 0);
394                 break;
395         case WM_MBUTTONDOWN:
396                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 2, 0);
397                 break;
398         case WM_MBUTTONUP:
399                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 2, 0);
400                 break;
401         case WM_RBUTTONDOWN:
402                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 3, 0);
403                 break;
404         case WM_RBUTTONUP:
405                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 3, 0);
406                 break;
407         case WM_MOUSEWHEEL:
408                 {
409                         unsigned btn = (HIWORD(wp)&0x8000) ? 5 : 4;
410                         signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), btn, 0);
411                         signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), btn, 0);
412                 }
413                 break;
414         case WM_MOUSEMOVE:
415                 signal_pointer_motion.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
416                 break;
417         case WM_SIZE:
418                 options.width=LOWORD(lp);
419                 options.height=HIWORD(lp);
420                 signal_resize.emit(options.width, options.height);
421                 break;
422         case WM_CLOSE:
423                 signal_close.emit();
424                 break;
425         default:
426                 return false;
427         }
428 #else
429         const XEvent &ev=evnt.xevent;
430         switch(ev.type)
431         {
432         case ButtonPress:
433                 signal_button_press.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
434                 break;
435         case ButtonRelease:
436                 signal_button_release.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
437                 break;
438         case MotionNotify:
439                 signal_pointer_motion.emit(ev.xmotion.x, ev.xmotion.y);
440                 break;
441         case KeyPress:
442                 {
443                         char buf[16];
444                         XLookupString(const_cast<XKeyEvent *>(&ev.xkey), buf, sizeof(buf), 0, 0);
445                         // XXX Handle the result according to locale
446                         signal_key_press.emit(XKeycodeToKeysym(display.get_private().display, ev.xkey.keycode, 0), ev.xkey.state, buf[0]);
447                 }
448                 break;
449         case KeyRelease:
450                 signal_key_release.emit(XKeycodeToKeysym(display.get_private().display, ev.xkey.keycode, 0), ev.xkey.state);
451                 break;
452         case ConfigureNotify:
453                 if((ev.xconfigure.width==static_cast<int>(options.width) && ev.xconfigure.height==static_cast<int>(options.height)) == resizing)
454                 {
455                         options.width=ev.xconfigure.width;
456                         options.height=ev.xconfigure.height;
457                         resizing=false;
458                         signal_resize.emit(options.width, options.height);
459                 }
460 #ifdef WITH_XF86VIDMODE
461                 if(options.fullscreen)
462                 {
463                         ::Display *dpy=display.get_private().display;
464                         int screen=DefaultScreen(dpy);
465                         XF86VidModeSetViewPort(dpy, screen, ev.xconfigure.x, ev.xconfigure.y);
466                 }
467 #endif
468                 break;
469         case ClientMessage:
470                 if(ev.xclient.data.l[0]==static_cast<long>(priv->wm_delete_window))
471                         signal_close.emit();
472                 break;
473         case EnterNotify:
474                 if(options.fullscreen)
475                         XSetInputFocus(display.get_private().display, priv->window, RevertToParent, CurrentTime);
476                 break;
477         case MapNotify:
478                 if(options.fullscreen)
479                         XGrabPointer(display.get_private().display, priv->window, true, None, GrabModeAsync, GrabModeAsync, priv->window, None, CurrentTime);
480                 break;
481         default:
482                 return false;
483         }
484 #endif
485         return true;
486 }
487
488 } // namespace Graphics
489 } // namespace Msp