]> git.tdb.fi Git - libs/gui.git/blobdiff - source/gbase/window.cpp
Header updates
[libs/gui.git] / source / gbase / window.cpp
index da877e966679b8ffd8f66b362a9f8feec2b6e695..fbcbd519f99dbe01a49f70490b4f6de259d6c87b 100644 (file)
@@ -1,14 +1,10 @@
-/* $Id$
-
-This file is part of libmspgbase
-Copyright © 2007 Mikko Rasa, Mikkosoft Productions
-Distributed under the LGPL
-*/
-
 #include <vector>
 #ifndef WIN32
 #include <X11/Xatom.h>
 #include <X11/Xutil.h>
+#ifdef WITH_XF86VIDMODE
+#include <X11/extensions/xf86vmode.h>
+#endif
 #else
 #include <windowsx.h>
 #endif
@@ -16,10 +12,41 @@ Distributed under the LGPL
 #include <msp/core/except.h>
 #include "display.h"
 #include "window.h"
+#include "display_priv.h"
 
 using namespace std;
 
-#include <iostream>
+namespace {
+
+#ifdef WIN32
+LRESULT CALLBACK wndproc_(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+       if(msg==WM_CREATE)
+       {
+               CREATESTRUCT *cs = reinterpret_cast<CREATESTRUCT *>(lparam);
+               SetWindowLong(hwnd, 0, reinterpret_cast<LONG>(cs->lpCreateParams));
+       }
+       else
+       {
+               Msp::Graphics::Window *wnd = reinterpret_cast<Msp::Graphics::Window *>(GetWindowLong(hwnd, 0));
+               Msp::Graphics::Window::Event ev;
+               ev.msg = msg;
+               ev.wparam = wparam;
+               ev.lparam = lparam;
+               if(wnd && wnd->event(ev))
+                       return 0;
+       }
+
+       return DefWindowProc(hwnd, msg, wparam, lparam);
+}
+#else
+Bool match_event_type(Display *, XEvent *event, XPointer arg)
+{
+       return event->type==*reinterpret_cast<int *>(arg);
+}
+#endif
+
+}
 
 namespace Msp {
 namespace Graphics {
@@ -35,9 +62,9 @@ WindowOptions::WindowOptions():
 Window::Window(Display &dpy, unsigned w, unsigned h, bool fs):
        display(dpy)
 {
-       options.width=w;
-       options.height=h;
-       options.fullscreen=fs;
+       options.width = w;
+       options.height = h;
+       options.fullscreen = fs;
 
        init();
 }
@@ -49,225 +76,308 @@ Window::Window(Display &dpy, const WindowOptions &opts):
        init();
 }
 
+void Window::init()
+{
+       visible = false;
+       kbd_autorepeat = true;
+       resizing = false;
+       priv = new Private;
+
+#ifdef WIN32
+       static bool wndclass_created = false;
+
+       if(!wndclass_created)
+       {
+               WNDCLASSEX wndcl;
+
+               wndcl.cbSize = sizeof(WNDCLASSEX);
+               wndcl.style = 0;
+               wndcl.lpfnWndProc = &wndproc_;
+               wndcl.cbClsExtra = 0;
+               wndcl.cbWndExtra = sizeof(Window *);
+               wndcl.hInstance = reinterpret_cast<HINSTANCE>(Application::get_data());
+               wndcl.hIcon = 0;
+               wndcl.hCursor = LoadCursor(0, IDC_ARROW);
+               wndcl.hbrBackground = 0;
+               wndcl.lpszMenuName = 0;
+               wndcl.lpszClassName = "mspgbase";
+               wndcl.hIconSm = 0;
+
+               if(!RegisterClassEx(&wndcl))
+                       throw Exception("Couldn't register window class");
+
+               wndclass_created = true;
+       }
+
+       RECT rect;
+       SetRect(&rect, 0, 0, options.width, options.height);
+
+       int style = (options.fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
+       if(!options.resizable)
+               style &= ~WS_THICKFRAME;
+       int exstyle = (options.fullscreen ? WS_EX_APPWINDOW : WS_EX_OVERLAPPEDWINDOW);
+       AdjustWindowRectEx(&rect, style, false, exstyle);
+
+       priv->window = CreateWindowEx(exstyle,
+               "mspgbase",
+               "Window",
+               style,
+               CW_USEDEFAULT, CW_USEDEFAULT,
+               rect.right-rect.left, rect.bottom-rect.top,
+               0,
+               0,
+               reinterpret_cast<HINSTANCE>(Application::get_data()),
+               this);
+       if(!priv->window)
+               throw Exception("CreateWindowEx failed");
+
+#else
+       ::Display *dpy = display.get_private().display;
+
+       priv->wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", true);
+       priv->invisible_cursor = 0;
+
+       XSetWindowAttributes attr;
+       attr.override_redirect = options.fullscreen;
+       attr.event_mask = ButtonPressMask|ButtonReleaseMask|PointerMotionMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask|EnterWindowMask;
+
+       priv->window = XCreateWindow(dpy,
+               DefaultRootWindow(dpy),
+               0, 0,
+               options.width, options.height,
+               0,
+               CopyFromParent,
+               InputOutput,
+               CopyFromParent,
+               CWOverrideRedirect|CWEventMask, &attr);
+
+       XSetWMProtocols(dpy, priv->window, &priv->wm_delete_window, 1);
+
+       if(!options.resizable)
+       {
+               XSizeHints hints;
+               hints.flags = PMinSize|PMaxSize;
+               hints.min_width=hints.max_width = options.width;
+               hints.min_height=hints.max_height = options.height;
+               XSetWMNormalHints(dpy, priv->window, &hints);
+       }
+
+#endif
+
+       display.add_window(*this);
+       display.check_error();
+}
+
 Window::~Window()
 {
-       if(window)
+       if(priv->window)
 #ifdef WIN32
-               CloseWindow(window);
+               CloseWindow(priv->window);
 #else
-               XDestroyWindow(display.get_display(), window);
+               XDestroyWindow(display.get_private().display, priv->window);
+
+       if(priv->invisible_cursor)
+               XFreeCursor(display.get_private().display, priv->invisible_cursor);
 #endif
 
-       display.remove_window(this);
+       display.remove_window(*this);
 
        if(options.fullscreen)
                display.restore_mode();
+
+       delete priv;
 }
 
 void Window::set_title(const string &title)
 {
 #ifdef WIN32
-       SetWindowText(window, title.c_str());
+       SetWindowText(priv->window, title.c_str());
 #else
        vector<unsigned char> buf(title.begin(), title.end());
        XTextProperty prop;
-       prop.value=&buf[0];
-       prop.encoding=XA_STRING;
-       prop.format=8;
-       prop.nitems=title.size();
-       XSetWMName(display.get_display(), window, &prop);
+       prop.value = &buf[0];
+       prop.encoding = XA_STRING;
+       prop.format = 8;
+       prop.nitems = title.size();
+       XSetWMName(display.get_private().display, priv->window, &prop);
        display.check_error();
 #endif
 }
 
 void Window::reconfigure(const WindowOptions &opts)
 {
-       bool fullscreen_changed=(opts.fullscreen!=options.fullscreen);
+       bool fullscreen_changed = (opts.fullscreen!=options.fullscreen);
+       resizing = (opts.width!=options.width || opts.height!=options.height);
 
-       options=opts;
+       options = opts;
 
 #ifdef WIN32
-       // XXX Preserve position
-       MoveWindow(window, 0, 0, options.width, options.height, false);
-
-       (void)fullscreen_changed;
-#else
-       ::Display *dpy=display.get_display();
+       RECT rect;
+       SetRect(&rect, 0, 0, options.width, options.height);
 
-       XMoveResizeWindow(dpy, window, 0, 0, options.width, options.height);
+       int style = (options.fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
+       if(!options.resizable)
+               style &= ~WS_THICKFRAME;
+       int exstyle = (options.fullscreen ? WS_EX_APPWINDOW : WS_EX_OVERLAPPEDWINDOW);
+       AdjustWindowRectEx(&rect, style, false, exstyle);
 
        if(fullscreen_changed)
        {
                hide();
-               XSetWindowAttributes attr;
-               attr.override_redirect=options.fullscreen;
-               XChangeWindowAttributes(dpy, window, CWOverrideRedirect, &attr);
+               SetWindowLong(priv->window, GWL_EXSTYLE, exstyle);
+               SetWindowLong(priv->window, GWL_STYLE, style);
                show();
        }
 
        if(options.fullscreen)
-               display.set_mode(VideoMode(options.width, options.height));
-       else if(fullscreen_changed)
-               display.restore_mode();
-#endif
-}
-
-void Window::show()
-{
-#ifdef WIN32
-       ShowWindow(window, SW_SHOWNORMAL);
+               SetWindowPos(priv->window, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOZORDER);
+       else
+               SetWindowPos(priv->window, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER);
 #else
-       XMapRaised(display.get_display(), window);
-       display.check_error();
-#endif
-}
+       ::Display *dpy = display.get_private().display;
 
-void Window::hide()
-{
-#ifdef WIN32
-       ShowWindow(window, SW_HIDE);
-#else
-       XUnmapWindow(display.get_display(), window);
-       display.check_error();
-#endif
-}
+       bool was_visible = visible;
+       if(fullscreen_changed)
+       {
+               if(was_visible)
+               {
+                       hide();
 
-void Window::init()
-{
-#ifdef WIN32
-       static bool wndclass_created=false;
+                       // Wait for the window to be unmapped.  This makes window managers happy.
+                       XEvent ev;
+                       int ev_type = UnmapNotify;
+                       XPeekIfEvent(dpy, &ev, match_event_type, reinterpret_cast<char *>(&ev_type));
+               }
 
-       if(!wndclass_created)
-       {
-               WNDCLASSEX wndcl;
+               XSetWindowAttributes attr;
+               attr.override_redirect = options.fullscreen;
+               XChangeWindowAttributes(dpy, priv->window, CWOverrideRedirect, &attr);
+       }
 
-               wndcl.cbSize=sizeof(WNDCLASSEX);
-               wndcl.style=0;
-               wndcl.lpfnWndProc=&wndproc_;
-               wndcl.cbClsExtra=0;
-               wndcl.cbWndExtra=sizeof(Window *);
-               wndcl.hInstance=reinterpret_cast<HINSTANCE>(Application::get_data());
-               wndcl.hIcon=0;
-               wndcl.hCursor=LoadCursor(0, IDC_ARROW);
-               wndcl.hbrBackground=0;
-               wndcl.lpszMenuName=0;
-               wndcl.lpszClassName="mspgbase";
-               wndcl.hIconSm=0;
+       XSizeHints hints;
+       if(options.resizable)
+               hints.flags = 0;
+       else
+       {
+               hints.flags = PMinSize|PMaxSize;
+               hints.min_width=hints.max_width = options.width;
+               hints.min_height=hints.max_height = options.height;
+       }
+       XSetWMNormalHints(dpy, priv->window, &hints);
 
-               if(!RegisterClassEx(&wndcl))
-                       throw Exception("Couldn't register window class");
+       if(options.fullscreen)
+               XMoveResizeWindow(dpy, priv->window, 0, 0, options.width, options.height);
+       else
+               XResizeWindow(dpy, priv->window, options.width, options.height);
 
-               wndclass_created=true;
+       if(fullscreen_changed)
+       {
+               if(was_visible)
+                       show();
        }
+#endif
 
-       RECT rect;
-       rect.left=0;
-       rect.top=0;
-       rect.right=options.width;
-       rect.bottom=options.height;
-       AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, false, WS_EX_OVERLAPPEDWINDOW);
+       if(visible)
+       {
+               if(options.fullscreen)
+                       display.set_mode(VideoMode(options.width, options.height));
+               else if(fullscreen_changed)
+                       display.restore_mode();
+       }
+}
 
-       window=CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
-               "mspgbase",
-               "Window",
-               WS_OVERLAPPEDWINDOW,
-               CW_USEDEFAULT, CW_USEDEFAULT,
-               rect.right-rect.left, rect.bottom-rect.top,
-               0,
-               0,
-               reinterpret_cast<HINSTANCE>(Application::get_data()),
-               this);
-       if(!window)
-               throw Exception("CreateWindowEx failed");
+void Window::set_keyboard_autorepeat(bool r)
+{
+       kbd_autorepeat = r;
+}
 
+void Window::show_cursor(bool s)
+{
+#ifdef WIN32
+       ShowCursor(s);
 #else
-       ::Display *dpy=display.get_display();
+       ::Display *dpy = display.get_private().display;
 
-       wm_delete_window=XInternAtom(dpy, "WM_DELETE_WINDOW", true);
+       if(s)
+               XUndefineCursor(dpy, priv->window);
+       else
+       {
+               if(!priv->invisible_cursor)
+               {
+                       int screen = DefaultScreen(dpy);
 
-       XSetWindowAttributes attr;
-       attr.override_redirect=options.fullscreen;
-       attr.event_mask=ButtonPressMask|ButtonReleaseMask|PointerMotionMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask|EnterWindowMask;
+                       Pixmap pm = XCreatePixmap(dpy, priv->window, 1, 1, 1);
+                       GC gc = XCreateGC(dpy, pm, 0, 0);
+                       XSetFunction(dpy, gc, GXclear);
+                       XDrawPoint(dpy, pm, gc, 0, 0);
+                       XFreeGC(dpy, gc);
 
-       window=XCreateWindow(dpy,
-               DefaultRootWindow(dpy),
-               0, 0,
-               options.width, options.height,
-               0,
-               CopyFromParent,
-               InputOutput,
-               CopyFromParent,
-               CWOverrideRedirect|CWEventMask, &attr);
+                       XColor black;
+                       black.pixel = BlackPixel(dpy, screen);
+                       XQueryColor(dpy, DefaultColormap(dpy, screen), &black);
 
-       XSetWMProtocols(dpy, window, &wm_delete_window, 1);
+                       priv->invisible_cursor = XCreatePixmapCursor(dpy, pm, pm, &black, &black, 0, 0);
 
-       if(options.fullscreen)
-       {
-               display.set_mode(VideoMode(options.width, options.height));
-               XWarpPointer(dpy, None, window, 0, 0, 0, 0, options.width/2, options.height/2);
+                       XFreePixmap(dpy, pm);
+               }
+               XDefineCursor(dpy, priv->window, priv->invisible_cursor);
        }
 #endif
-
-       display.add_window(this);
-       display.check_error();
 }
 
+void Window::warp_pointer(int x, int y)
+{
 #ifndef WIN32
-void Window::event(const XEvent &ev)
+       XWarpPointer(display.get_private().display, None, priv->window, 0, 0, 0, 0, x, y);
+#else
+       (void)x;
+       (void)y;
+#endif
+}
+
+void Window::show()
 {
-       switch(ev.type)
+#ifdef WIN32
+       ShowWindow(priv->window, SW_SHOWNORMAL);
+#else
+       XMapRaised(display.get_private().display, priv->window);
+#endif
+       visible = true;
+
+       if(options.fullscreen)
        {
-       case ButtonPress:
-               signal_button_press.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
-               break;
-       case ButtonRelease:
-               signal_button_release.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
-               break;
-       case MotionNotify:
-               signal_pointer_motion.emit(ev.xmotion.x, ev.xmotion.y);
-               break;
-       case KeyPress:
-               {
-                       char buf[16];
-                       XLookupString(const_cast<XKeyEvent *>(&ev.xkey), buf, sizeof(buf), 0, 0);
-                       // XXX Handle the result according to locale
-                       signal_key_press.emit(ev.xkey.keycode, ev.xkey.state, buf[0]);
-               }
-               break;
-       case KeyRelease:
-               signal_key_release.emit(ev.xkey.keycode, ev.xkey.state);
-               break;
-       case ConfigureNotify:
-               options.width=ev.xconfigure.width;
-               options.height=ev.xconfigure.height;
-               signal_resize.emit(options.width, options.height);
-               break;
-       case ClientMessage:
-               if(ev.xclient.data.l[0]==static_cast<long>(wm_delete_window))
-                       signal_close.emit();
-               break;
-       case EnterNotify:
-               XSetInputFocus(display.get_display(), window, RevertToParent, CurrentTime);
-               break;
-       case MapNotify:
-               if(options.fullscreen)
-                       XGrabPointer(display.get_display(), window, true, None, GrabModeAsync, GrabModeAsync, window, None, CurrentTime);
-               break;
-       default:;
+               display.set_mode(VideoMode(options.width, options.height));
+#ifndef WIN32
+               XWarpPointer(display.get_private().display, None, priv->window, 0, 0, 0, 0, options.width/2, options.height/2);
+#endif
        }
 }
-#endif
 
+void Window::hide()
+{
 #ifdef WIN32
-int Window::wndproc(UINT msg, WPARAM wp, LPARAM lp)
+       ShowWindow(priv->window, SW_HIDE);
+#else
+       XUnmapWindow(display.get_private().display, priv->window);
+#endif
+       visible = false;
+
+       if(options.fullscreen)
+               display.restore_mode();
+}
+
+bool Window::event(const Event &evnt)
 {
-       switch(msg)
+#ifdef WIN32
+       WPARAM wp = evnt.wparam;
+       LPARAM lp = evnt.lparam;
+       switch(evnt.msg)
        {
        case WM_KEYDOWN:
-               signal_key_press.emit((lp>>16)&0x1FF, 0, wp);
+               signal_key_press.emit(wp, 0, wp);
                break;
        case WM_KEYUP:
-               signal_key_release.emit((lp>>16)&0x1FF, 0);
+               signal_key_release.emit(wp, 0);
                break;
        case WM_LBUTTONDOWN:
                signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 1, 0);
@@ -287,36 +397,86 @@ int Window::wndproc(UINT msg, WPARAM wp, LPARAM lp)
        case WM_RBUTTONUP:
                signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 3, 0);
                break;
+       case WM_MOUSEWHEEL:
+               {
+                       unsigned btn = (HIWORD(wp)&0x8000) ? 5 : 4;
+                       signal_button_press.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), btn, 0);
+                       signal_button_release.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp), btn, 0);
+               }
+               break;
        case WM_MOUSEMOVE:
                signal_pointer_motion.emit(GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
                break;
+       case WM_SIZE:
+               options.width = LOWORD(lp);
+               options.height = HIWORD(lp);
+               signal_resize.emit(options.width, options.height);
+               break;
        case WM_CLOSE:
                signal_close.emit();
                break;
        default:
-               return 0;
-       }
-
-       return 1;
-}
-
-LRESULT CALLBACK Window::wndproc_(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
-{
-       if(msg==WM_CREATE)
-       {
-               CREATESTRUCT *cs=reinterpret_cast<CREATESTRUCT *>(lparam);
-               SetWindowLong(hwnd, 0, reinterpret_cast<LONG>(cs->lpCreateParams));
+               return false;
        }
-       else
+#else
+       const XEvent &ev = evnt.xevent;
+       switch(ev.type)
        {
-               Window *wnd=reinterpret_cast<Window *>(GetWindowLong(hwnd, 0));
-               if(wnd && wnd->wndproc(msg, wparam, lparam))
-                       return 0;
+       case ButtonPress:
+               signal_button_press.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
+               break;
+       case ButtonRelease:
+               signal_button_release.emit(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.state);
+               break;
+       case MotionNotify:
+               signal_pointer_motion.emit(ev.xmotion.x, ev.xmotion.y);
+               break;
+       case KeyPress:
+               {
+                       char buf[16];
+                       XLookupString(const_cast<XKeyEvent *>(&ev.xkey), buf, sizeof(buf), 0, 0);
+                       // XXX Handle the result according to locale
+                       signal_key_press.emit(XKeycodeToKeysym(display.get_private().display, ev.xkey.keycode, 0), ev.xkey.state, buf[0]);
+               }
+               break;
+       case KeyRelease:
+               signal_key_release.emit(XKeycodeToKeysym(display.get_private().display, ev.xkey.keycode, 0), ev.xkey.state);
+               break;
+       case ConfigureNotify:
+               if((ev.xconfigure.width==static_cast<int>(options.width) && ev.xconfigure.height==static_cast<int>(options.height)) == resizing)
+               {
+                       options.width = ev.xconfigure.width;
+                       options.height = ev.xconfigure.height;
+                       resizing = false;
+                       signal_resize.emit(options.width, options.height);
+               }
+#ifdef WITH_XF86VIDMODE
+               if(options.fullscreen)
+               {
+                       ::Display *dpy = display.get_private().display;
+                       int screen = DefaultScreen(dpy);
+                       XF86VidModeSetViewPort(dpy, screen, ev.xconfigure.x, ev.xconfigure.y);
+               }
+#endif
+               break;
+       case ClientMessage:
+               if(ev.xclient.data.l[0]==static_cast<long>(priv->wm_delete_window))
+                       signal_close.emit();
+               break;
+       case EnterNotify:
+               if(options.fullscreen)
+                       XSetInputFocus(display.get_private().display, priv->window, RevertToParent, CurrentTime);
+               break;
+       case MapNotify:
+               if(options.fullscreen)
+                       XGrabPointer(display.get_private().display, priv->window, true, None, GrabModeAsync, GrabModeAsync, priv->window, None, CurrentTime);
+               break;
+       default:
+               return false;
        }
-
-       return DefWindowProc(hwnd, msg, wparam, lparam);
-}
 #endif
+       return true;
+}
 
 } // namespace Graphics
 } // namespace Msp