]> git.tdb.fi Git - libs/gui.git/blob - source/gbase/window.cpp
77959ded2ddd3de77627846028f5a7946ae9cd8b
[libs/gui.git] / source / gbase / window.cpp
1 #include <vector>
2 #ifndef WIN32
3 #include <X11/Xatom.h>
4 #include <X11/Xutil.h>
5 #ifdef WITH_XF86VIDMODE
6 #include <X11/extensions/xf86vmode.h>
7 #endif
8 #else
9 #include <windowsx.h>
10 #endif
11 #include <msp/core/application.h>
12 #include "display.h"
13 #include "window.h"
14 #include "display_priv.h"
15
16 using namespace std;
17
18 namespace {
19
20 #ifdef WIN32
21 LRESULT CALLBACK wndproc_(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
22 {
23         if(msg==WM_CREATE)
24         {
25                 CREATESTRUCT *cs = reinterpret_cast<CREATESTRUCT *>(lparam);
26                 SetWindowLong(hwnd, 0, reinterpret_cast<LONG>(cs->lpCreateParams));
27         }
28         else
29         {
30                 Msp::Graphics::Window *wnd = reinterpret_cast<Msp::Graphics::Window *>(GetWindowLong(hwnd, 0));
31                 Msp::Graphics::Window::Event ev;
32                 ev.msg = msg;
33                 ev.wparam = wparam;
34                 ev.lparam = lparam;
35                 if(wnd && wnd->event(ev))
36                         return 0;
37         }
38
39         return DefWindowProc(hwnd, msg, wparam, lparam);
40 }
41 #else
42 Bool match_event_type(Display *, XEvent *event, XPointer arg)
43 {
44         return event->type==*reinterpret_cast<int *>(arg);
45 }
46 #endif
47
48 }
49
50 namespace Msp {
51 namespace Graphics {
52
53 WindowOptions::WindowOptions():
54         width(640),
55         height(480),
56         fullscreen(false),
57         resizable(false)
58 { }
59
60
61 Window::Window(Display &dpy, unsigned w, unsigned h, bool fs):
62         display(dpy)
63 {
64         options.width = w;
65         options.height = h;
66         options.fullscreen = fs;
67
68         init();
69 }
70
71 Window::Window(Display &dpy, const WindowOptions &opts):
72         display(dpy),
73         options(opts)
74 {
75         init();
76 }
77
78 void Window::init()
79 {
80         visible = false;
81         kbd_autorepeat = true;
82         resizing = false;
83         priv = new Private;
84
85 #ifdef WIN32
86         static bool wndclass_created = false;
87
88         if(!wndclass_created)
89         {
90                 WNDCLASSEX wndcl;
91
92                 wndcl.cbSize = sizeof(WNDCLASSEX);
93                 wndcl.style = 0;
94                 wndcl.lpfnWndProc = &wndproc_;
95                 wndcl.cbClsExtra = 0;
96                 wndcl.cbWndExtra = sizeof(Window *);
97                 wndcl.hInstance = reinterpret_cast<HINSTANCE>(Application::get_data());
98                 wndcl.hIcon = 0;
99                 wndcl.hCursor = LoadCursor(0, IDC_ARROW);
100                 wndcl.hbrBackground = 0;
101                 wndcl.lpszMenuName = 0;
102                 wndcl.lpszClassName = "mspgbase";
103                 wndcl.hIconSm = 0;
104
105                 if(!RegisterClassEx(&wndcl))
106                         throw system_error("RegisterClassEx");
107
108                 wndclass_created = true;
109         }
110
111         RECT rect;
112         SetRect(&rect, 0, 0, options.width, options.height);
113
114         int style = (options.fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
115         if(!options.resizable)
116                 style &= ~WS_THICKFRAME;
117         int exstyle = (options.fullscreen ? WS_EX_APPWINDOW : WS_EX_OVERLAPPEDWINDOW);
118         AdjustWindowRectEx(&rect, style, false, exstyle);
119
120         priv->window = CreateWindowEx(exstyle,
121                 "mspgbase",
122                 "Window",
123                 style,
124                 CW_USEDEFAULT, CW_USEDEFAULT,
125                 rect.right-rect.left, rect.bottom-rect.top,
126                 0,
127                 0,
128                 reinterpret_cast<HINSTANCE>(Application::get_data()),
129                 this);
130         if(!priv->window)
131                 throw system_error("CreateWindowEx");
132
133 #else
134         ::Display *dpy = display.get_private().display;
135
136         priv->wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", true);
137         priv->invisible_cursor = 0;
138
139         XSetWindowAttributes attr;
140         attr.override_redirect = options.fullscreen;
141         attr.event_mask = ButtonPressMask|ButtonReleaseMask|PointerMotionMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask|EnterWindowMask;
142
143         priv->window = XCreateWindow(dpy,
144                 DefaultRootWindow(dpy),
145                 0, 0,
146                 options.width, options.height,
147                 0,
148                 CopyFromParent,
149                 InputOutput,
150                 CopyFromParent,
151                 CWOverrideRedirect|CWEventMask, &attr);
152
153         XSetWMProtocols(dpy, priv->window, &priv->wm_delete_window, 1);
154
155         if(!options.resizable)
156         {
157                 XSizeHints hints;
158                 hints.flags = PMinSize|PMaxSize;
159                 hints.min_width=hints.max_width = options.width;
160                 hints.min_height=hints.max_height = options.height;
161                 XSetWMNormalHints(dpy, priv->window, &hints);
162         }
163
164 #endif
165
166         display.add_window(*this);
167         display.check_error();
168 }
169
170 Window::~Window()
171 {
172         if(priv->window)
173 #ifdef WIN32
174                 CloseWindow(priv->window);
175 #else
176                 XDestroyWindow(display.get_private().display, priv->window);
177
178         if(priv->invisible_cursor)
179                 XFreeCursor(display.get_private().display, priv->invisible_cursor);
180 #endif
181
182         display.remove_window(*this);
183
184         if(options.fullscreen)
185                 display.restore_mode();
186
187         delete priv;
188 }
189
190 void Window::set_title(const string &title)
191 {
192 #ifdef WIN32
193         SetWindowText(priv->window, title.c_str());
194 #else
195         vector<unsigned char> buf(title.begin(), title.end());
196         XTextProperty prop;
197         prop.value = &buf[0];
198         prop.encoding = XA_STRING;
199         prop.format = 8;
200         prop.nitems = title.size();
201         XSetWMName(display.get_private().display, priv->window, &prop);
202         display.check_error();
203 #endif
204 }
205
206 void Window::reconfigure(const WindowOptions &opts)
207 {
208         bool fullscreen_changed = (opts.fullscreen!=options.fullscreen);
209         resizing = (opts.width!=options.width || opts.height!=options.height);
210
211         options = opts;
212
213 #ifdef WIN32
214         RECT rect;
215         SetRect(&rect, 0, 0, options.width, options.height);
216
217         int style = (options.fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
218         if(!options.resizable)
219                 style &= ~WS_THICKFRAME;
220         int exstyle = (options.fullscreen ? WS_EX_APPWINDOW : WS_EX_OVERLAPPEDWINDOW);
221         AdjustWindowRectEx(&rect, style, false, exstyle);
222
223         if(fullscreen_changed)
224         {
225                 hide();
226                 SetWindowLong(priv->window, GWL_EXSTYLE, exstyle);
227                 SetWindowLong(priv->window, GWL_STYLE, style);
228                 show();
229         }
230
231         if(options.fullscreen)
232                 SetWindowPos(priv->window, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOZORDER);
233         else
234                 SetWindowPos(priv->window, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER);
235 #else
236         ::Display *dpy = display.get_private().display;
237
238         bool was_visible = visible;
239         if(fullscreen_changed)
240         {
241                 if(was_visible)
242                 {
243                         hide();
244
245                         // Wait for the window to be unmapped.  This makes window managers happy.
246                         XEvent ev;
247                         int ev_type = UnmapNotify;
248                         XPeekIfEvent(dpy, &ev, match_event_type, reinterpret_cast<char *>(&ev_type));
249                 }
250
251                 XSetWindowAttributes attr;
252                 attr.override_redirect = options.fullscreen;
253                 XChangeWindowAttributes(dpy, priv->window, CWOverrideRedirect, &attr);
254         }
255
256         XSizeHints hints;
257         if(options.resizable)
258                 hints.flags = 0;
259         else
260         {
261                 hints.flags = PMinSize|PMaxSize;
262                 hints.min_width=hints.max_width = options.width;
263                 hints.min_height=hints.max_height = options.height;
264         }
265         XSetWMNormalHints(dpy, priv->window, &hints);
266
267         if(options.fullscreen)
268                 XMoveResizeWindow(dpy, priv->window, 0, 0, options.width, options.height);
269         else
270                 XResizeWindow(dpy, priv->window, options.width, options.height);
271
272         if(fullscreen_changed)
273         {
274                 if(was_visible)
275                         show();
276         }
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                 if((ev.xconfigure.width==static_cast<int>(options.width) && ev.xconfigure.height==static_cast<int>(options.height)) == resizing)
446                 {
447                         options.width = ev.xconfigure.width;
448                         options.height = ev.xconfigure.height;
449                         resizing = false;
450                         signal_resize.emit(options.width, options.height);
451                 }
452 #ifdef WITH_XF86VIDMODE
453                 if(options.fullscreen)
454                 {
455                         ::Display *dpy = display.get_private().display;
456                         int screen = DefaultScreen(dpy);
457                         XF86VidModeSetViewPort(dpy, screen, ev.xconfigure.x, ev.xconfigure.y);
458                 }
459 #endif
460                 break;
461         case ClientMessage:
462                 if(ev.xclient.data.l[0]==static_cast<long>(priv->wm_delete_window))
463                         signal_close.emit();
464                 break;
465         case EnterNotify:
466                 if(options.fullscreen)
467                         XSetInputFocus(display.get_private().display, priv->window, RevertToParent, CurrentTime);
468                 break;
469         case MapNotify:
470                 if(options.fullscreen)
471                         XGrabPointer(display.get_private().display, priv->window, true, None, GrabModeAsync, GrabModeAsync, priv->window, None, CurrentTime);
472                 break;
473         default:
474                 return false;
475         }
476 #endif
477         return true;
478 }
479
480 } // namespace Graphics
481 } // namespace Msp