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