diff --git a/examples/example1/src/example1.d b/examples/example1/src/example1.d index 45652fda..742e724d 100644 --- a/examples/example1/src/example1.d +++ b/examples/example1/src/example1.d @@ -358,6 +358,9 @@ extern (C) int UIAppMain(string[] args) { MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c)); windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES")); + windowItem.add(new Action(31, UIString.fromId("MENU_WINDOW_MINIMIZE"))); + windowItem.add(new Action(32, UIString.fromId("MENU_WINDOW_MAXIMIZE"))); + windowItem.add(new Action(33, UIString.fromId("MENU_WINDOW_RESTORE"))); MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c)); helpItem.add(new Action(40, "MENU_HELP_VIEW_HELP")); MenuItem aboutItem = new MenuItem(new Action(41, "MENU_HELP_ABOUT")); @@ -377,6 +380,15 @@ extern (C) int UIAppMain(string[] args) { if (a.id == ACTION_FILE_EXIT) { window.close(); return true; + } else if (a.id == 31) { + window.minimizeWindow(); + return true; + } else if (a.id == 32) { + window.maximizeWindow(); + return true; + } else if (a.id == 33) { + window.restoreWindow(); + return true; } else if (a.id == 41) { window.showMessageBox(UIString.fromRaw("About"d), UIString.fromRaw("DLangUI demo app\n(C) Vadim Lopatin, 2014\nhttp://github.com/buggins/dlangui"d)); return true; diff --git a/examples/example1/views/res/i18n/en.ini b/examples/example1/views/res/i18n/en.ini index f71127d3..adc417f1 100644 --- a/examples/example1/views/res/i18n/en.ini +++ b/examples/example1/views/res/i18n/en.ini @@ -23,6 +23,9 @@ MENU_VIEW_THEME_DARK=Dark MENU_VIEW_THEME_CUSTOM1=Custom 1 MENU_WINDOW=&Window MENU_WINDOW_PREFERENCES=&Preferences +MENU_WINDOW_MINIMIZE=Minimize +MENU_WINDOW_MAXIMIZE=Maximize +MENU_WINDOW_RESTORE=Restore MENU_HELP=&Help MENU_HELP_VIEW_HELP=&View help MENU_HELP_ABOUT=&About diff --git a/examples/example1/views/res/i18n/ru.ini b/examples/example1/views/res/i18n/ru.ini index ab52d3d0..6fad07b2 100644 --- a/examples/example1/views/res/i18n/ru.ini +++ b/examples/example1/views/res/i18n/ru.ini @@ -21,6 +21,9 @@ MENU_VIEW_THEME_DARK=Тёмная MENU_VIEW_THEME_CUSTOM1=Пример 1 MENU_WINDOW=&Окно MENU_WINDOW_PREFERENCES=&Настройки +MENU_WINDOW_MINIMIZE=Свернуть +MENU_WINDOW_MAXIMIZE=Развернуть +MENU_WINDOW_RESTORE=Восстановить MENU_HELP=&Справка MENU_HELP_VIEW_HELP=&Просмотр справки MENU_HELP_ABOUT=&О программе diff --git a/src/dlangui/platforms/x11/x11app.d b/src/dlangui/platforms/x11/x11app.d index 21fd869e..11ac5835 100644 --- a/src/dlangui/platforms/x11/x11app.d +++ b/src/dlangui/platforms/x11/x11app.d @@ -40,28 +40,43 @@ static if (ENABLE_OPENGL) { //pragma(lib, "X11"); -private __gshared Display * x11display; -private __gshared Display * x11display2; -private __gshared int x11screen; -private __gshared XIM xim; - alias XWindow = x11.Xlib.Window; alias DWindow = dlangui.platforms.common.platform.Window; -private __gshared string localClipboardContent; -private __gshared Atom atom_UTF8_STRING; -private __gshared Atom atom_CLIPBOARD; -private __gshared Atom atom_TARGETS; +private __gshared +{ + Display * x11display; + Display * x11display2; + int x11screen; + XIM xim; -private __gshared Atom atom_WM_PROTOCOLS; -private __gshared Atom atom_WM_DELETE_WINDOW; + string localClipboardContent; + bool _enableOpengl = false; -private __gshared Atom atom_NET_WM_ICON; -private __gshared Atom atom_NET_WM_NAME; -private __gshared Atom atom_NET_WM_ICON_NAME; + Cursor[CursorType.Hand + 1] x11cursors; -private __gshared Atom atom_DLANGUI_TIMER_EVENT; -private __gshared Atom atom_DLANGUI_TASK_EVENT; + Atom atom_UTF8_STRING; + Atom atom_CLIPBOARD; + Atom atom_TARGETS; + + Atom atom_WM_PROTOCOLS; + Atom atom_WM_DELETE_WINDOW; + + Atom atom_NET_WM_ICON; + Atom atom_NET_WM_NAME; + Atom atom_NET_WM_ICON_NAME; + + Atom atom_NET_WM_STATE; + Atom atom_NET_WM_STATE_MODAL; + Atom atom_NET_WM_STATE_MAXIMIZED_VERT; + Atom atom_NET_WM_STATE_MAXIMIZED_HORZ; + Atom atom_NET_WM_STATE_HIDDEN; + Atom atom_NET_WM_STATE_FULLSCREEN; + + Atom atom_DLANGUI_TIMER_EVENT; + Atom atom_DLANGUI_TASK_EVENT; + Atom atom_DLANGUI_CLOSE_WINDOW_EVENT; +} static void setupX11Atoms() { @@ -75,14 +90,18 @@ static void setupX11Atoms() atom_NET_WM_ICON = XInternAtom(x11display, "_NET_WM_ICON", True); atom_NET_WM_NAME = XInternAtom(x11display, "_NET_WM_NAME", True); atom_NET_WM_ICON_NAME = XInternAtom(x11display, "_NET_WM_ICON_NAME", True); + atom_NET_WM_STATE = XInternAtom(x11display, "_NET_WM_STATE", True); + atom_NET_WM_STATE_MODAL = XInternAtom(x11display, "_NET_WM_STATE_MODAL", True); + atom_NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(x11display, "_NET_WM_STATE_MAXIMIZED_VERT", True); + atom_NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(x11display, "_NET_WM_STATE_MAXIMIZED_HORZ", True); + atom_NET_WM_STATE_HIDDEN = XInternAtom(x11display, "_NET_WM_STATE_HIDDEN", True); + atom_NET_WM_STATE_FULLSCREEN = XInternAtom(x11display, "_NET_WM_STATE_FULLSCREEN", True); atom_DLANGUI_TIMER_EVENT = XInternAtom(x11display, "DLANGUI_TIMER_EVENT", False); atom_DLANGUI_TASK_EVENT = XInternAtom(x11display, "DLANGUI_TASK_EVENT", False); + atom_DLANGUI_CLOSE_WINDOW_EVENT = XInternAtom(x11display, "DLANGUI_CLOSE_WINDOW_EVENT", False); } -private __gshared bool _enableOpengl = false; - -private __gshared Cursor[CursorType.Hand + 1] x11cursors; // Cursor font constants enum { XC_X_cursor=0, @@ -205,6 +224,12 @@ class X11Window : DWindow { protected GC _gc; private __gshared XIC xic; + X11Window[] _children; + X11Window _parent; + + bool _needRedraw; + int _cachedWidth, _cachedHeight; + static if (ENABLE_OPENGL) { GLXContext _glc; } @@ -217,9 +242,9 @@ class X11Window : DWindow { width = 500; if (height == 0) height = 300; - _dx = width; - _dy = height; - //create(flags); + _cachedWidth = _dx = width; + _cachedHeight = _dy = height; + _flags = flags; /* get the colors black and white (see section for details) */ ulong black, white; @@ -282,18 +307,42 @@ class X11Window : DWindow { return; } - //XMapWindow(x11display, _win); - //XSync(x11display, false); - - //readln(); - - /* here is where some properties of the window can be set. - The third and fourth items indicate the name which appears - at the top of the window and the name of the minimized window - respectively. - */ windowCaption = caption; XSetWMProtocols(x11display, _win, &atom_WM_DELETE_WINDOW, 1); + + _children.reserve(20); + _parent = cast(X11Window) parent; + if (_parent) + _parent._children ~= this; + + if (!(flags & WindowFlag.Resizable)) { + XSizeHints sizeHints; + sizeHints.min_width = width; + sizeHints.min_height = height; + sizeHints.max_width = width; + sizeHints.max_height = height; + sizeHints.flags = PMaxSize | PMinSize; + XSetWMNormalHints(x11display, _win, &sizeHints); + } + if (flags & WindowFlag.Fullscreen) { + if (atom_NET_WM_STATE_FULLSCREEN != None) { + changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_FULLSCREEN); + } + else + Log.w("Missing _NET_WM_STATE_FULLSCREEN atom"); + } + if (flags & WindowFlag.Modal) { + if (_parent) { + XSetTransientForHint(x11display, _win, _parent._win); + } else { + Log.w("Top-level modal window"); + } + if (atom_NET_WM_STATE_MODAL != None) { + changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_MODAL); + } else { + Log.w("Missing _NET_WM_STATE_MODAL atom"); + } + } /* this routine determines which types of input are allowed in the input. see the appropriate section for details... */ @@ -321,15 +370,26 @@ class X11Window : DWindow { } ~this() { + debug Log.d("Destroying X11 window"); if (timer) { timer.stop(); } + if (_parent) { + import std.algorithm : countUntil, remove; + ptrdiff_t index = countUntil(_parent._children,this); + if (index > -1 ) { + _parent._children = _parent._children.remove(index); + } + _parent = null; + } static if (ENABLE_OPENGL) { if (_glc) { glXDestroyContext(x11display, _glc); _glc = null; } } + if (_drawbuf) + destroy(_drawbuf); if (_gc) { XFreeGC(x11display, _gc); _gc = null; @@ -369,6 +429,79 @@ class X11Window : DWindow { _mainWidget.setFocus(); } + protected final void changeWindowState(int action, Atom firstProperty, Atom secondProperty = None) nothrow + { + XEvent ev; + memset(&ev, 0, ev.sizeof); + ev.xany.type = ClientMessage; + ev.xclient.window = _win; + ev.xclient.message_type = atom_NET_WM_STATE; + ev.xclient.format = 32; + ev.xclient.data.l[0] = action; + ev.xclient.data.l[1] = firstProperty; + if (secondProperty != None) + ev.xclient.data.l[2] = secondProperty; + ev.xclient.data.l[3] = 0; + XSendEvent(x11display, RootWindow(x11display, x11screen), false, SubstructureNotifyMask|SubstructureRedirectMask, &ev); + } + + protected enum { + _NET_WM_STATE_REMOVE = 0, + _NET_WM_STATE_ADD, + _NET_WM_STATE_TOGGLE + } + + override bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) { + if (_win == None) { + return false; + } + bool result = false; + switch(newState) { + case WindowState.maximized: + if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_MAXIMIZED_HORZ != None && atom_NET_WM_STATE_MAXIMIZED_VERT != None) { + changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_MAXIMIZED_HORZ, atom_NET_WM_STATE_MAXIMIZED_VERT); + result = true; + } + break; + case WindowState.minimized: + if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_HIDDEN != None) { + changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_HIDDEN); + result = true; + } + break; + case WindowState.hidden: + XUnmapWindow(x11display, _win); + result = true; + break; + case WindowState.normal: + if (atom_NET_WM_STATE != None && + atom_NET_WM_STATE_MAXIMIZED_HORZ != None && + atom_NET_WM_STATE_MAXIMIZED_VERT != None && + atom_NET_WM_STATE_HIDDEN != None) + { + changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_MAXIMIZED_HORZ, atom_NET_WM_STATE_MAXIMIZED_VERT); + changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_HIDDEN); + changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_FULLSCREEN); + result = true; + } + break; + case WindowState.fullscreen: + if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_FULLSCREEN != None) { + changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_FULLSCREEN); + result = true; + } + break; + default: + break; + } + if (activate) { + XMapRaised(x11display, _win); + result = true; + } + XFlush(x11display); + return result; + } + override @property dstring windowCaption() { return _caption; } @@ -399,6 +532,7 @@ class X11Window : DWindow { immutable int iconw = 32; immutable int iconh = 32; ColorDrawBuf iconDraw = new ColorDrawBuf(iconw, iconh); + scope(exit) destroy(iconDraw); iconDraw.fill(0xFF000000); iconDraw.drawRescaled(Rect(0, 0, iconw, iconh), icon, Rect(0, 0, icon.width, icon.height)); iconDraw.invertAndPreMultiplyAlpha(); @@ -411,31 +545,19 @@ class X11Window : DWindow { } XChangeProperty(x11display, _win, atom_NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, cast(ubyte*)propData.ptr, cast(int)propData.length); } + /// request window redraw override void invalidate() { - debug(x11) Log.d("Window.invalidate()"); - XEvent ev; - memset(&ev, 0, ev.sizeof); - ev.type = Expose; - ev.xexpose.window = _win; - - static if (true) { - //ev.xclient.display = x11display2; - //ev.xclient.message_type = atom_DLANGUI_TASK_EVENT; - //ev.xclient.format = 32; - //ev.xclient.data.l[0] = event.uniqueId; - XLockDisplay(x11display2); - XSendEvent(x11display2, _win, false, StructureNotifyMask, &ev); - XFlush(x11display2); - XUnlockDisplay(x11display2); - } else { - XSendEvent(x11display, _win, false, ExposureMask, &ev); - XFlush(x11display); + if (!_needRedraw) { + debug(x11) Log.d("Window.invalidate()"); + _needRedraw = true; } } /// close window override void close() { + Log.d("X11Window.close()"); + _platform.closeWindow(this); } ColorDrawBuf _drawbuf; @@ -475,7 +597,7 @@ class X11Window : DWindow { 0, 0, 0, 0, _drawbuf.width, _drawbuf.height); - XFlush(x11display); + //XFlush(x11display); // no need to XFlush since it will be called in event loop } } @@ -500,21 +622,21 @@ class X11Window : DWindow { } } - void processExpose() { - XWindowAttributes window_attributes_return; - XGetWindowAttributes(x11display, _win, &window_attributes_return); + void redraw() { + _needRedraw = false; + //Use values cached by ConfigureNotify to avoid XGetWindowAttributes call. + //XWindowAttributes window_attributes_return; + //XGetWindowAttributes(x11display, _win, &window_attributes_return); //Log.d(format("XGetWindowAttributes reported size %d, %d", window_attributes_return.width, window_attributes_return.height)); - int width = window_attributes_return.width; - int height = window_attributes_return.height; + immutable width = _cachedWidth; + immutable height = _cachedHeight; if (width > 0 && height > 0) onResize(width, height); - debug(x11) Log.d(format("processExpose(%d, %d)", width, height)); + debug(x11) Log.d(format("redraw(%d, %d)", width, height)); if (_enableOpengl) drawUsingOpengl(); else drawUsingBitmap(); - //Log.d("processExpose - drawing finished"); - } protected ButtonDetails _lbutton; @@ -926,12 +1048,11 @@ class X11Window : DWindow { TimerThread timer; private long _nextExpectedTimerTs; - XEvent ev; - /// schedule timer for interval in milliseconds - call window.onTimer when finished override protected void scheduleSystemTimer(long intervalMillis) { if (!timer) { timer = new TimerThread(delegate() { + XEvent ev; memset(&ev, 0, ev.sizeof); //ev.xclient = XClientMessageEvent.init; ev.xclient.type = ClientMessage; @@ -1042,7 +1163,19 @@ class X11Platform : Platform { * Closes window earlier created with createWindow() */ override void closeWindow(DWindow w) { - _windowMap.remove((cast(X11Window)w)._win); + X11Window window = cast(X11Window)w; + XEvent ev; + memset(&ev, 0, ev.sizeof); + ev.xclient.type = ClientMessage; + ev.xclient.message_type = atom_DLANGUI_CLOSE_WINDOW_EVENT; + ev.xclient.window = window._win; + ev.xclient.display = x11display2; + ev.xclient.format = 32; + Log.d("Sending close window event"); + XLockDisplay(x11display2); + XSendEvent(x11display2, window._win, false, StructureNotifyMask, &ev); + XFlush(x11display2); + XUnlockDisplay(x11display2); } bool handleTimers() { @@ -1056,6 +1189,10 @@ class X11Platform : Platform { return handled; } + final bool allWindowsClosed() { + return _windowMap.length == 0; + } + /** * Starts application message loop. * @@ -1068,8 +1205,6 @@ class X11Platform : Platform { char[255] text; /* a char buffer for KeyPress Events */ Log.d("enterMessageLoop()"); - /* look for events forever... */ - bool finished = false; XComposeStatus compose; import core.sys.posix.sys.select; @@ -1077,23 +1212,27 @@ class X11Platform : Platform { fd_set fdSet; FD_ZERO(&fdSet); FD_SET(x11displayFd, &fdSet); - while(!finished) { - /* get the next event and stuff it into our event variable. - Note: only events we set the mask for are detected! - */ - //bool timersHandled = handleTimers(); - //if (timersHandled) - //XFlush(x11display); - //while (XEventsQueued(x11display, QueuedAfterFlush)) {//QueuedAfterFlush + scope(exit) FD_ZERO(&fdSet); + while(!allWindowsClosed()) { + // Note: only events we set the mask for are detected! + foreach(win; _windowMap) { + if (win._needRedraw) { + win.redraw(); + } + } XFlush(x11display); int eventsInQueue = XEventsQueued(x11display, QueuedAlready); if (!eventsInQueue) { - timeval zeroTime; - auto selectResult = select(x11displayFd + 1, &fdSet, null, null, &zeroTime); + import core.stdc.errno; + int selectResult; + do { + timeval zeroTime; + selectResult = select(x11displayFd + 1, &fdSet, null, null, &zeroTime); + } while(selectResult == -1 && errno == EINTR); if (selectResult < 0) { - Log.e("select error"); + Log.e("X11: display fd select error"); } else if (selectResult == 1) { - Log.d("Pending"); + Log.d("X11: XPending"); eventsInQueue = XPending(x11display); } } @@ -1103,21 +1242,26 @@ class X11Platform : Platform { } foreach(eventIndex; 0..eventsInQueue) { - //Thread.sleep(dur!("msecs")(10)); - //continue; + if (allWindowsClosed()) + break; XNextEvent(x11display, &event); - switch (event.type) { + case ConfigureNotify: + X11Window w = findWindow(event.xconfigure.window); + if (w) { + w._cachedWidth = event.xconfigure.width; + w._cachedHeight = event.xconfigure.height; + } else { + Log.e("ConfigureNotify: Window not found"); + } + break; case Expose: if (event.xexpose.count == 0) { - /* the window was exposed redraw it! */ - //redraw(); X11Window w = findWindow(event.xexpose.window); if (w) { - - w.processExpose(); + w.invalidate(); } else { - Log.e("Window not found"); + Log.e("Expose: Window not found"); } } else { Log.d("Expose: non-0 count"); @@ -1167,8 +1311,6 @@ class X11Platform : Platform { //event.xkey.keycode, event.xkey.state); } - - } else { Log.e("Window not found"); } @@ -1235,56 +1377,37 @@ class X11Platform : Platform { case CreateNotify: Log.d("X11: CreateNotify event"); X11Window w = findWindow(event.xcreatewindow.window); - if (w) { - //w.processExpose(); - } else { + if (!w) { Log.e("Window not found"); } break; case DestroyNotify: Log.d("X11: DestroyNotify event"); - X11Window w = findWindow(event.xdestroywindow.window); - if (w) { - //w.processExpose(); - } else { - Log.e("Window not found"); - } break; case ResizeRequest: Log.d("X11: ResizeRequest event"); X11Window w = findWindow(event.xresizerequest.window); - if (w) { - //w.processExpose(); - } else { + if (!w) { Log.e("Window not found"); } break; case FocusIn: Log.d("X11: FocusIn event"); X11Window w = findWindow(event.xfocus.window); - if (w) { - //w.processExpose(); - } else { + if (!w) { Log.e("Window not found"); } break; case FocusOut: Log.d("X11: FocusOut event"); X11Window w = findWindow(event.xfocus.window); - if (w) { - //w.processExpose(); - } else { + if (!w) { Log.e("Window not found"); } break; case KeymapNotify: Log.d("X11: KeymapNotify event"); X11Window w = findWindow(event.xkeymap.window); - if (w) { - //w.processExpose(); - } else { - Log.e("Window not found"); - } break; case ClientMessage: debug(x11) Log.d("X11: ClientMessage event"); @@ -1298,9 +1421,12 @@ class X11Platform : Platform { Log.d("Handling WM_PROTOCOLS"); if ((event.xclient.format == 32) && (event.xclient.data.l[0]) == atom_WM_DELETE_WINDOW) { Log.d("Handling WM_DELETE_WINDOW"); - closeWindow(w); + _windowMap.remove(w._win); destroy(w); } + } else if (event.xclient.message_type == atom_DLANGUI_CLOSE_WINDOW_EVENT) { + _windowMap.remove(w._win); + destroy(w); } } else { Log.e("Window not found"); @@ -1310,11 +1436,6 @@ class X11Platform : Platform { break; } } - if (_windowMap.length == 0) { - finished = true; - } - //Thread.sleep(dur!("msecs")(10)); - //XFlush(x11display); } return 0; }