]> git.tdb.fi Git - libs/gui.git/blob - source/gbase/window.cpp
Add Window::warp_pointer (No win32 support for now)
[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::show_cursor(bool s)
188 {
189 #ifdef WIN32
190         ShowCursor(s);
191 #else
192         ::Display *dpy=display.get_private().display;
193
194         if(s)
195                 XUndefineCursor(dpy, priv->window);
196         else
197         {
198                 if(!priv->invisible_cursor)
199                 {
200                         int screen=DefaultScreen(dpy);
201
202                         Pixmap pm=XCreatePixmap(dpy, priv->window, 1, 1, 1);
203                         GC gc=XCreateGC(dpy, pm, 0, 0);
204                         XSetFunction(dpy, gc, GXclear);
205                         XDrawPoint(dpy, pm, gc, 0, 0);
206                         XFreeGC(dpy, gc);
207
208                         XColor black;
209                         black.pixel=BlackPixel(dpy, screen);
210                         XQueryColor(dpy, DefaultColormap(dpy, screen), &black);
211
212                         priv->invisible_cursor=XCreatePixmapCursor(dpy, pm, pm, &black, &black, 0, 0);
213
214                         XFreePixmap(dpy, pm);
215                 }
216                 XDefineCursor(dpy, priv->window, priv->invisible_cursor);
217         }
218 #endif
219 }
220
221 void Window::warp_pointer(int x, int y)
222 {
223 #ifndef WIN32
224         XWarpPointer(display.get_private().display, None, priv->window, 0, 0, 0, 0, x, y);
225 #endif
226 }
227
228 void Window::show()
229 {
230 #ifdef WIN32
231         ShowWindow(priv->window, SW_SHOWNORMAL);
232 #else
233         XMapRaised(display.get_private().display, priv->window);
234 #endif
235 }
236
237 void Window::hide()
238 {
239 #ifdef WIN32
240         ShowWindow(priv->window, SW_HIDE);
241 #else
242         XUnmapWindow(display.get_private().display, priv->window);
243 #endif
244 }
245
246 void Window::init()
247 {
248         priv=new Private;
249
250 #ifdef WIN32
251         static bool wndclass_created=false;
252
253         if(!wndclass_created)
254         {
255                 WNDCLASSEX wndcl;
256
257                 wndcl.cbSize=sizeof(WNDCLASSEX);
258                 wndcl.style=0;
259                 wndcl.lpfnWndProc=&wndproc_;
260                 wndcl.cbClsExtra=0;
261                 wndcl.cbWndExtra=sizeof(Window *);
262                 wndcl.hInstance=reinterpret_cast<HINSTANCE>(Application::get_data());
263                 wndcl.hIcon=0;
264                 wndcl.hCursor=LoadCursor(0, IDC_ARROW);
265                 wndcl.hbrBackground=0;
266                 wndcl.lpszMenuName=0;
267                 wndcl.lpszClassName="mspgbase";
268                 wndcl.hIconSm=0;
269
270                 if(!RegisterClassEx(&wndcl))
271                         throw Exception("Couldn't register window class");
272
273                 wndclass_created=true;
274         }
275
276         RECT rect;
277         SetRect(&rect, 0, 0, options.width, options.height);
278
279         int style=(options.fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
280         if(!options.resizable)
281                 style&=~WS_THICKFRAME;
282         int exstyle=(options.fullscreen ? WS_EX_APPWINDOW : WS_EX_OVERLAPPEDWINDOW);
283         AdjustWindowRectEx(&rect, style, false, exstyle);
284
285         priv->window=CreateWindowEx(exstyle,
286                 "mspgbase",
287                 "Window",
288                 style,
289                 CW_USEDEFAULT, CW_USEDEFAULT,
290                 rect.right-rect.left, rect.bottom-rect.top,
291                 0,
292                 0,
293                 reinterpret_cast<HINSTANCE>(Application::get_data()),
294                 this);
295         if(!priv->window)
296                 throw Exception("CreateWindowEx failed");
297
298         if(options.fullscreen)
299                 display.set_mode(VideoMode(options.width, options.height));
300
301 #else
302         ::Display *dpy=display.get_private().display;
303
304         priv->wm_delete_window=XInternAtom(dpy, "WM_DELETE_WINDOW", true);
305         priv->invisible_cursor=0;
306
307         XSetWindowAttributes attr;
308         attr.override_redirect=options.fullscreen;
309         attr.event_mask=ButtonPressMask|ButtonReleaseMask|PointerMotionMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask|EnterWindowMask;
310
311         priv->window=XCreateWindow(dpy,
312                 DefaultRootWindow(dpy),
313                 0, 0,
314                 options.width, options.height,
315                 0,
316                 CopyFromParent,
317                 InputOutput,
318                 CopyFromParent,
319                 CWOverrideRedirect|CWEventMask, &attr);
320
321         XSetWMProtocols(dpy, priv->window, &priv->wm_delete_window, 1);
322
323         if(!options.resizable)
324         {
325                 XSizeHints hints;
326                 hints.flags=PMinSize|PMaxSize;
327                 hints.min_width=hints.max_width=options.width;
328                 hints.min_height=hints.max_height=options.height;
329                 XSetWMNormalHints(dpy, priv->window, &hints);
330         }
331
332         if(options.fullscreen)
333         {
334                 display.set_mode(VideoMode(options.width, options.height));
335                 XWarpPointer(dpy, None, priv->window, 0, 0, 0, 0, options.width/2, options.height/2);
336         }
337 #endif
338
339         display.add_window(*this);
340         display.check_error();
341 }
342
343 bool Window::event(const Event &evnt)
344 {
345 #ifdef WIN32
346         WPARAM wp=evnt.wparam;
347         LPARAM lp=evnt.lparam;
348         switch(evnt.msg)
349         {
350         case WM_KEYDOWN:
351                 signal_key_press.emit(wp, 0, wp);
352                 break;
353         case WM_KEYUP:
354                 signal_key_release.emit(wp, 0);
355                 break;
356         case WM_LBUTTONDOWN:
357                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 1, 0);
358                 break;
359         case WM_LBUTTONUP:
360                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 1, 0);
361                 break;
362         case WM_MBUTTONDOWN:
363                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 2, 0);
364                 break;
365         case WM_MBUTTONUP:
366                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 2, 0);
367                 break;
368         case WM_RBUTTONDOWN:
369                 signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 3, 0);
370                 break;
371         case WM_RBUTTONUP:
372                 signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 3, 0);
373                 break;
374         case WM_MOUSEMOVE:
375                 signal_pointer_motion.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
376                 break;
377         case WM_SIZE:
378                 options.width=LOWORD(lp);
379                 options.height=HIWORD(lp);
380                 signal_resize.emit(options.width, options.height);
381                 break;
382         case WM_CLOSE:
383                 signal_close.emit();
384                 break;
385         default:
386                 return false;
387         }
388 #else
389         const XEvent &ev=evnt.xevent;
390         switch(ev.type)
391         {
392         case ButtonPress:
393                 signal_button_press.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
394                 break;
395         case ButtonRelease:
396                 signal_button_release.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
397                 break;
398         case MotionNotify:
399                 signal_pointer_motion.emit(ev.xmotion.x, ev.xmotion.y);
400                 break;
401         case KeyPress:
402                 {
403                         char buf[16];
404                         XLookupString(const_cast<XKeyEvent *>(&ev.xkey), buf, sizeof(buf), 0, 0);
405                         // XXX Handle the result according to locale
406                         signal_key_press.emit(XKeycodeToKeysym(display.get_private().display, ev.xkey.keycode, 0), ev.xkey.state, buf[0]);
407                 }
408                 break;
409         case KeyRelease:
410                 signal_key_release.emit(XKeycodeToKeysym(display.get_private().display, ev.xkey.keycode, 0), ev.xkey.state);
411                 break;
412         case ConfigureNotify:
413                 options.width=ev.xconfigure.width;
414                 options.height=ev.xconfigure.height;
415                 signal_resize.emit(options.width, options.height);
416                 break;
417         case ClientMessage:
418                 if(ev.xclient.data.l[0]==static_cast<long>(priv->wm_delete_window))
419                         signal_close.emit();
420                 break;
421         case EnterNotify:
422                 if(options.fullscreen)
423                         XSetInputFocus(display.get_private().display, priv->window, RevertToParent, CurrentTime);
424                 break;
425         case MapNotify:
426                 if(options.fullscreen)
427                         XGrabPointer(display.get_private().display, priv->window, true, None, GrabModeAsync, GrabModeAsync, priv->window, None, CurrentTime);
428                 break;
429         default:
430                 return false;
431         }
432 #endif
433         return true;
434 }
435
436 } // namespace Graphics
437 } // namespace Msp