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