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