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