1 // Written in the D programming language.
2 
3 /**
4 This module contains common Plaform definitions.
5 
6 Platform is abstraction layer for application.
7 
8 
9 Synopsis:
10 
11 ----
12 import dlangui.platforms.common.platform;
13 
14 ----
15 
16 Copyright: Vadim Lopatin, 2014
17 License:   Boost License 1.0
18 Authors:   Vadim Lopatin, coolreader.org@gmail.com
19 */
20 module dlangui.platforms.common.platform;
21 
22 public import dlangui.core.config;
23 public import dlangui.core.events;
24 import dlangui.core.collections;
25 import dlangui.widgets.widget;
26 import dlangui.widgets.popup;
27 import dlangui.widgets.scrollbar;
28 import dlangui.graphics.drawbuf;
29 import dlangui.core.stdaction;
30 import dlangui.core.asyncsocket;
31 import dlangui.graphics.iconprovider;
32 
33 static if (ENABLE_OPENGL) {
34     private import dlangui.graphics.gldrawbuf;
35 }
36 private import std.algorithm;
37 private import core.sync.mutex;
38 private import std.string;
39 
40 /// entry point - declare such function to use as main for dlangui app
41 extern(C) int UIAppMain(string[] args);
42 
43 
44 // specify debug=DebugMouseEvents for logging mouse handling
45 // specify debug=DebugRedraw for logging drawing and layouts handling
46 // specify debug=DebugKeys for logging of key events
47 
48 /// window creation flags
49 enum WindowFlag : uint {
50     /// window can be resized
51     Resizable = 1,
52     /// window should be shown in fullscreen mode
53     Fullscreen = 2,
54     /// modal window - grabs input focus
55     Modal = 4,
56     /// measure window size on window.show() - helps if you want scrollWindow but on show() you want to set window to mainWidget measured size
57     MeasureSize = 8,
58     /// window without decorations
59     Borderless = 16,
60     /// expand window size if main widget minimal size is greater than size defined in window constructor
61     ExpandSize = 32,
62 }
63 
64 /// Window states
65 enum WindowState : int {
66     /// state is unknown (not supported by platform?), as well for using in setWindowState when only want to activate window or change its size/position
67     unspecified,
68     /// normal state
69     normal,
70     /// window is maximized
71     maximized,
72     /// window is maximized
73     minimized,
74     /// fullscreen mode (supported not on all platforms)
75     fullscreen,
76     /// application is paused (e.g. on Android)
77     paused,
78     /// window is hidden
79     hidden,
80     /// closed
81     closed,
82 }
83 
84 ///  Flags to set start window position on show
85 enum ShowPosition {
86     /// do nothing just show
87     dontCare,
88     /// window will be centered on parent window
89     parentWindowCenter
90 }
91 
92 /// Dialog display modes - used to configure dialogs should be showed as a popup or window
93 enum DialogDisplayMode : ulong {
94     /// show all types of dialogs in windows
95     allTypesOfDialogsInWindow = 0,
96     /// show file dialogs in popups
97     fileDialogInPopup = 1,
98     /// show message boxes in popups
99     messageBoxInPopup = 2,
100     /// show input boxes in popups
101     inputBoxInPopup = 4,
102     /// show settings dialogs in popups
103     settingsDialogInPopup = 8,
104     /// show user dialogs in popups - flag for user dialogs
105     userDialogInPopup = 16,
106     /// show all types of dialogs in popups
107     allTypesOfDialogsInPopup = fileDialogInPopup | messageBoxInPopup | inputBoxInPopup | settingsDialogInPopup | userDialogInPopup
108 }
109 
110 /// Sets what's should be done when window content is too big
111 enum WindowOrContentResizeMode {
112     /// widgets are shrink to fit in window
113     shrinkWidgets,
114     /// resize window when window is too small
115     resizeWindow,
116     /// add scrollbars to window
117     scrollWindow
118 }
119 
120 /// Window state signal listener
121 interface OnWindowStateHandler {
122     /// signal listener - called when state of window is changed
123     bool onWindowStateChange(Window window, WindowState winState, Rect rect);
124 }
125 
126 /// Window activate/deactivate signal listener
127 interface OnWindowActivityHandler {
128     /// signal listener - called when window activity is changed
129     bool onWindowActivityChange(Window window, bool isWindowActive);
130 }
131 
132 /// Window main widget changing signal listener
133 interface OnMainWidgetChange
134 {
135     void onMainWidgetChange(Window window, Widget previous, Widget newWidget);
136 }
137 
138 /// protected event list
139 /// references to posted messages can be stored here at least to keep live reference and avoid GC
140 /// as well, on some platforms it's easy to send id to message queue, but not pointer
141 class EventList {
142     protected Mutex _mutex;
143     protected Collection!CustomEvent _events;
144     this() {
145         _mutex = new Mutex();
146     }
147     ~this() {
148         destroy(_mutex);
149         _mutex = null;
150     }
151     /// puts event into queue, returns event's unique id
152     long put(CustomEvent event) {
153         _mutex.lock();
154         scope(exit) _mutex.unlock();
155         _events.pushBack(event);
156         return event.uniqueId;
157     }
158     /// return next event
159     CustomEvent get() {
160         _mutex.lock();
161         scope(exit) _mutex.unlock();
162         return _events.popFront();
163     }
164     /// return event by unique id
165     CustomEvent get(uint uniqueId) {
166         _mutex.lock();
167         scope(exit) _mutex.unlock();
168         for (int i = 0; i < _events.length; i++) {
169             if (_events[i].uniqueId == uniqueId) {
170                 return _events.remove(i);
171             }
172         }
173         // not found
174         return null;
175     }
176 }
177 
178 class TimerInfo {
179     static __gshared ulong nextId;
180 
181     this(Widget targetWidget, long intervalMillis) {
182         _id = ++nextId;
183         assert(intervalMillis >= 0 && intervalMillis < 7*24*60*60*1000L);
184         _targetWidget = targetWidget;
185         _interval = intervalMillis;
186         _nextTimestamp = currentTimeMillis + _interval;
187     }
188     /// cancel timer
189     void cancel() {
190         _targetWidget = null;
191     }
192     /// cancel timer
193     void notify() {
194         if (_targetWidget) {
195             _nextTimestamp = currentTimeMillis + _interval;
196             if (!_targetWidget.onTimer(_id)) {
197                 _targetWidget = null;
198             }
199         }
200     }
201     /// unique Id of timer
202     @property ulong id() { return _id; }
203     /// timer interval, milliseconds
204     @property long interval() { return _interval; }
205     /// next timestamp to invoke timer at, as per currentTimeMillis()
206     @property long nextTimestamp() { return _nextTimestamp; }
207     /// widget to route timer event to
208     @property Widget targetWidget() { return _targetWidget; }
209     /// return true if timer is not yet cancelled
210     @property bool valid() { return _targetWidget !is null; }
211 
212     protected ulong _id;
213     protected long _interval;
214     protected long _nextTimestamp;
215     protected Widget _targetWidget;
216 
217     override bool opEquals(Object obj) const {
218         TimerInfo b = cast(TimerInfo)obj;
219         if (!b)
220             return false;
221         return b._nextTimestamp == _nextTimestamp;
222     }
223     override int opCmp(Object obj) {
224         TimerInfo b = cast(TimerInfo)obj;
225         if (!b)
226             return false;
227         if (valid && !b.valid)
228             return -1;
229         if (!valid && b.valid)
230             return 1;
231         if (!valid && !b.valid)
232             return 0;
233         if (_nextTimestamp < b._nextTimestamp)
234             return -1;
235         if (_nextTimestamp > b._nextTimestamp)
236             return 1;
237         return 0;
238     }
239 }
240 
241 
242 /**
243  * Window abstraction layer. Widgets can be shown only inside window.
244  *
245  */
246 class Window : CustomEventTarget {
247     import dlangui.core.settings;
248 
249     protected int _dx;
250     protected int _dy;
251     protected uint _keyboardModifiers;
252     protected uint _backgroundColor;
253     protected Widget _mainWidget;
254     protected EventList _eventList;
255     protected uint _flags;
256     /// minimal good looking content width
257     protected int _minContentWidth;
258     /// minimal good looking content height
259     protected int _minContentHeight;
260 
261     // current content width calculated using _windowOrContentResizeMode flag, usually used in measure()
262     protected int _currentContentWidth;
263     // current content height calculated using _windowOrContentResizeMode flag, usually used in measure()
264     protected int _currentContentHeight;
265 
266     @property uint flags() { return _flags; }
267     @property uint backgroundColor() const { return _backgroundColor; }
268     @property void backgroundColor(uint color) { _backgroundColor = color; }
269     @property int width() const { return _dx; }
270     @property int height() const { return _dy; }
271     @property uint keyboardModifiers() const { return _keyboardModifiers; }
272     @property inout(Widget) mainWidget() inout { return _mainWidget; }
273     @property void mainWidget(Widget widget)
274     {
275         mainWidgetChange(this, _mainWidget, widget);
276         if (_mainWidget !is null)
277         {
278             _mainWidget.window = null;
279             destroy(_mainWidget);
280         }
281         _mainWidget = widget;
282         if (_mainWidget !is null)
283             _mainWidget.window = this;
284     }
285 
286     Signal!OnMainWidgetChange mainWidgetChange;
287 
288     /// save window state to setting object
289     void saveWindowState(Setting setting) {
290         if (!setting)
291             return;
292         WindowState state = windowState;
293         Rect rect = windowRect;
294         if (state == WindowState.fullscreen
295             || state == WindowState.minimized
296             || state == WindowState.maximized
297             || state == WindowState.normal) {
298             //
299             setting.setInteger("windowState", state);
300             if (rect.right > 0 && rect.bottom > 0) {
301                 setting.setInteger("windowPositionLeft", rect.left);
302                 setting.setInteger("windowPositionTop", rect.top);
303                 setting.setInteger("windowWidth", rect.right);
304                 setting.setInteger("windowHeight", rect.bottom);
305             }
306         }
307     }
308 
309     /// restore window state from setting object
310     bool restoreWindowState(Setting setting) {
311         if (!setting)
312             return false;
313         WindowState state = cast(WindowState)setting.getInteger("windowState", WindowState.unspecified);
314         Rect rect;
315         rect.left = cast(int)setting.getInteger("windowPositionLeft", WindowState.unspecified);
316         rect.top = cast(int)setting.getInteger("windowPositionTop", 0);
317         int w = cast(int)setting.getInteger("windowWidth", 0);
318         int h = cast(int)setting.getInteger("windowHeight", 0);
319         if (w <= 0 || h <= 0)
320             return false;
321         rect.right = w;
322         rect.bottom = h;
323         if (correctWindowPositionOnScreen(rect) && (state == WindowState.fullscreen
324              || state == WindowState.minimized
325              || state == WindowState.maximized
326              || state == WindowState.normal)) {
327              setWindowState(state, false, rect);
328              return true;
329         }
330         return false;
331     }
332 
333     /// check if window position inside screen bounds; try to correct if needed; returns true if position is ok.
334     bool correctWindowPositionOnScreen(ref Rect rect) {
335         // override to apply screen size bounds
336         return true;
337     }
338 
339     protected Rect _caretRect;
340     /// blinking caret position (empty rect if no blinking caret)
341     @property void caretRect(Rect rc) { _caretRect = rc; }
342     @property Rect caretRect() { return _caretRect; }
343 
344     protected bool _caretReplace;
345     /// blinking caret is in Replace mode if true, insert mode if false
346     @property void caretReplace(bool flg) { _caretReplace = flg; }
347     @property bool caretReplace() { return _caretReplace; }
348 
349     // window content resize mode
350     //protected WindowOrContentResizeMode _windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow;
351     //protected WindowOrContentResizeMode _windowOrContentResizeMode = WindowOrContentResizeMode.shrinkWidgets;
352     protected WindowOrContentResizeMode _windowOrContentResizeMode = WindowOrContentResizeMode.scrollWindow;
353 
354     @property WindowOrContentResizeMode windowOrContentResizeMode() {return _windowOrContentResizeMode; }
355     @property void windowOrContentResizeMode(WindowOrContentResizeMode newMode) {
356         _windowOrContentResizeMode = newMode;
357         if (_mainWidget) {
358             _mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
359             adjustWindowOrContentSize(_mainWidget.measuredWidth, _mainWidget.measuredHeight);
360         }
361     }
362 
363     protected ShowPosition _showPosition = ShowPosition.parentWindowCenter;
364 
365     ///returns current window show position (don't care or parent center)
366     @property ShowPosition showPosition() {
367         return _showPosition;
368     }
369 
370     /// sets window position after show (don't care or parent center)
371     @property void showPosition(ShowPosition newPosition) {
372         _showPosition = newPosition;
373     }
374 
375     // Abstract methods : override in platform implementation
376 
377     /// show window
378     abstract void show();
379     /// returns window caption
380     abstract @property dstring windowCaption() const;
381     /// sets window caption
382     abstract @property void windowCaption(dstring caption);
383     /// sets window icon
384     abstract @property void windowIcon(DrawBufRef icon);
385     /// request window redraw
386     abstract void invalidate();
387     /// close window
388     abstract void close();
389     /// returns parent window
390     @property Window parentWindow() {
391         return null;
392     }
393 
394     protected WindowState _windowState = WindowState.normal;
395     /// returns current window state
396     @property WindowState windowState() {
397         return _windowState;
398     }
399 
400     protected Rect _windowRect = RECT_VALUE_IS_NOT_SET;
401     /// returns window rectangle on screen (includes window frame and title)
402     @property Rect windowRect() {
403         if (_windowRect != RECT_VALUE_IS_NOT_SET)
404             return _windowRect;
405         // fake window rectangle -- at position 0,0 and
406         return Rect(0, 0, _dx, _dy);
407     }
408     /// window state change signal
409     Signal!OnWindowStateHandler windowStateChanged;
410     /// update and signal window state and/or size/positon changes - for using in platform inplementations
411     protected void handleWindowStateChange(WindowState newState, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
412         bool signalWindow = false;
413         if (newState != WindowState.unspecified && newState != _windowState) {
414             _windowState = newState;
415             //Log.d("Window ", windowCaption, " has new state - ", newState);
416             signalWindow = true;
417         }
418         if (newWindowRect != RECT_VALUE_IS_NOT_SET && newWindowRect != _windowRect) {
419             _windowRect = newWindowRect;
420             //Log.d("Window ", windowCaption, " rect changed - ", newWindowRect);
421             signalWindow = true;
422         }
423 
424         if (signalWindow && windowStateChanged.assigned)
425             windowStateChanged(this, newState, newWindowRect);
426     }
427 
428     /// change window state, position, or size; returns true if successful, false if not supported by platform
429     bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
430         // override for particular platforms
431         return false;
432     }
433     /// maximize window
434     bool maximizeWindow(bool activate = false) { return setWindowState(WindowState.maximized, activate); }
435     /// minimize window
436     bool minimizeWindow() { return setWindowState(WindowState.minimized); }
437     /// restore window if maximized/minimized/hidden
438     bool restoreWindow(bool activate = false) { return setWindowState(WindowState.normal, activate); }
439     /// restore window if maximized/minimized/hidden
440     bool hideWindow() { return setWindowState(WindowState.hidden); }
441     /// just activate window
442     bool activateWindow() { return setWindowState(WindowState.unspecified, true); }
443     /// change window position only
444     bool moveWindow(Point topLeft, bool activate = false) { return setWindowState(WindowState.unspecified, activate, Rect(topLeft.x, topLeft.y, int.min, int.min)); }
445     /// change window size only
446     bool resizeWindow(Point sz, bool activate = false) { return setWindowState(WindowState.unspecified, activate, Rect(int.min, int.min, sz.x, sz.y)); }
447     /// set window rectangle
448     bool moveAndResizeWindow(Rect rc, bool activate = false) { return setWindowState(WindowState.unspecified, activate, rc); }
449 
450     /// centers window on parent window, do nothing if there is no parent window
451     void centerOnParentWindow() {
452         if (parentWindow) {
453             Rect parentRect = parentWindow.windowRect();
454             Point newPos;
455             newPos.x = parentRect.left + (parentRect.right - _windowRect.right) / 2;
456             newPos.y = parentRect.top + (parentRect.bottom - _windowRect.bottom) / 2;
457             moveWindow(newPos);
458         }
459     }
460 
461     ///adjust window position during show()
462     protected void adjustPositionDuringShow() {
463         final switch (_showPosition) {
464             case ShowPosition.dontCare:
465                 return;
466             case ShowPosition.parentWindowCenter: {
467                 centerOnParentWindow();
468                 break;
469             }
470         }
471     }
472 
473     // things needed for WindowOrContentResizeMode.scrollWindow:
474     /// vertical scrollbar control
475     protected ScrollBar _vScrollBar = null;
476     /// horizontal scrollbar control
477     protected ScrollBar _hScrollBar = null;
478 
479     /// Sets the minimal content size and adjust window or content should be called from window show
480     void adjustWindowOrContentSize(int minContentWidth, int minContentHeight) {
481         _minContentWidth = minContentWidth;
482         _minContentHeight = minContentHeight;
483         if (_windowOrContentResizeMode == WindowOrContentResizeMode.resizeWindow || flags & WindowFlag.ExpandSize)
484             resizeWindow(Point(max(_windowRect.right, minContentWidth), max(_windowRect.bottom, minContentHeight)));
485         updateWindowOrContentSize();
486     }
487 
488     /// update current content size based on windowOrContentResizeMode flag, usually used when window is resized
489     void updateWindowOrContentSize() {
490         final switch (_windowOrContentResizeMode) {
491             case WindowOrContentResizeMode.shrinkWidgets: {
492                 _currentContentWidth = _windowRect.right;
493                 _currentContentHeight = _windowRect.bottom;
494                 return;
495             }
496             case WindowOrContentResizeMode.resizeWindow: {
497                     //maybe should not permit to resize when new window size is smaller than content size, now do nothing
498                 _currentContentWidth = _windowRect.right;
499                 _currentContentHeight = _windowRect.bottom;
500                 break;
501             }
502             case WindowOrContentResizeMode.scrollWindow: {
503                 if (_windowRect.right < _minContentWidth) {
504                     // create scrollbar
505                     _currentContentWidth = _minContentWidth;
506                     if (!_hScrollBar) {
507                         _hScrollBar = new ScrollBar(null,Orientation.Horizontal);
508                         _hScrollBar.scrollEvent = delegate bool (AbstractSlider source, ScrollEvent event) {
509                             if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased)
510                                 requestLayout();
511                             else if (event.action == ScrollAction.PageUp) {
512                                 source.position = max(0, source.position - _windowRect.right * 3 / 4);
513                                 requestLayout();
514                             } else if (event.action == ScrollAction.PageDown) {
515                                 source.position = min(source.maxValue - _windowRect.right, source.position + _windowRect.right *3 / 4);
516                                 requestLayout();
517                             } else if (event.action == ScrollAction.LineUp) {
518                                 source.position = max(0, source.position - _windowRect.right / 10);
519                                 requestLayout();
520                             } else if (event.action == ScrollAction.LineDown) {
521                                 source.position = min(source.maxValue - _windowRect.right, source.position + _windowRect.right / 10);
522                                 requestLayout();
523                             }
524                             return true;
525                         };
526                     }
527                     _hScrollBar.measure(_windowRect.right, _windowRect.bottom);
528                     if (windowRect().bottom < _minContentHeight)
529                         _hScrollBar.setRange(0, _minContentWidth + _hScrollBar.measuredHeight);
530                     else
531                         _hScrollBar.setRange(0, _minContentWidth);
532                     _hScrollBar.pageSize(_windowRect.right);
533                     _hScrollBar.position(0);
534                 }
535                 else {
536                     if (_hScrollBar) {
537                         destroy(_hScrollBar);
538                         _hScrollBar = null;
539                     }
540                     _currentContentWidth = _windowRect.right;
541                 }
542 
543                 if (windowRect().bottom < _minContentHeight) {
544                     // create scrollbar
545                     _currentContentHeight = _minContentHeight;
546                     if (!_vScrollBar) {
547                         _vScrollBar = new ScrollBar(null,Orientation.Vertical);
548                         _vScrollBar.scrollEvent = delegate bool (AbstractSlider source, ScrollEvent event) {
549                             if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased)
550                                 requestLayout();
551                             else if (event.action == ScrollAction.PageUp) {
552                                 source.position = max(0, source.position - _windowRect.bottom * 3 / 4);
553                                 requestLayout();
554                             } else if (event.action == ScrollAction.PageDown) {
555                                 source.position = min(source.maxValue - _windowRect.bottom, source.position + _windowRect.bottom *3 / 4);
556                                 requestLayout();
557                             } else if (event.action == ScrollAction.LineUp) {
558                                 source.position = max(0, source.position - _windowRect.bottom / 10);
559                                 requestLayout();
560                             } else if (event.action == ScrollAction.LineDown) {
561                                 source.position = min(source.maxValue - _windowRect.bottom, source.position + _windowRect.bottom / 10);
562                                 requestLayout();
563                             }
564                             return true;
565                         };
566                     }
567                     _vScrollBar.measure(_windowRect.right, _windowRect.bottom);
568                     if (_hScrollBar)
569                         _vScrollBar.setRange(0, _minContentHeight+_hScrollBar.measuredHeight);
570                     else
571                         _vScrollBar.setRange(0, _minContentHeight);
572                     _vScrollBar.pageSize(windowRect().bottom);
573                     _vScrollBar.position(0);
574 
575                     if (!_hScrollBar)
576                         _currentContentWidth = _windowRect.right - _vScrollBar.measuredWidth;
577                 }
578                 else {
579                     if (_vScrollBar) {
580                         destroy(_vScrollBar);
581                         _vScrollBar = null;
582                     }
583                     _currentContentHeight = _hScrollBar ? _windowRect.bottom - _hScrollBar.measuredHeight : _windowRect.bottom;
584                 }
585                 return;
586             }
587         }
588     }
589 
590 
591     /// requests layout for main widget and popups
592     void requestLayout() {
593         if (_mainWidget)
594             _mainWidget.requestLayout();
595         if (_hScrollBar)
596             _hScrollBar.requestLayout();
597         if (_vScrollBar)
598             _vScrollBar.requestLayout();
599         foreach(p; _popups)
600             p.requestLayout();
601         if (_tooltip.popup)
602             _tooltip.popup.requestLayout();
603     }
604     void measure() {
605         if (_hScrollBar)
606             _hScrollBar.measure(_dx, _dy);
607 
608         if (_vScrollBar)
609             _vScrollBar.measure(_dx, _dy);
610 
611         if (_mainWidget !is null) {
612             _mainWidget.measure(_currentContentWidth, _currentContentHeight);
613         }
614         foreach(p; _popups)
615             p.measure(_currentContentWidth, _currentContentHeight);
616         if (_tooltip.popup)
617             _tooltip.popup.measure(_currentContentWidth, _currentContentHeight);
618     }
619     void layout() {
620         if (_hScrollBar)
621             _hScrollBar.layout(Rect(0, _dy - _hScrollBar.measuredHeight, _vScrollBar ? _dx - _vScrollBar.measuredWidth : _dx, _dy));
622 
623         if (_vScrollBar)
624             _vScrollBar.layout(Rect(_dx - _vScrollBar.measuredWidth, 0, _dx, _hScrollBar ? _dy - _hScrollBar.measuredHeight : _dy));
625 
626         int deltaX = 0;
627         if (_hScrollBar)
628             deltaX = -_hScrollBar.position;
629 
630         int deltaY = 0;
631         if (_vScrollBar)
632             deltaY = -_vScrollBar.position;
633 
634         Rect rc = Rect(deltaX, deltaY, _currentContentWidth + deltaX, _currentContentHeight + deltaY);
635         if (_mainWidget !is null) {
636             _mainWidget.layout(rc);
637         }
638         foreach(p; _popups)
639             p.layout(rc);
640         if (_tooltip.popup)
641             _tooltip.popup.layout(rc);
642     }
643     void onResize(int width, int height) {
644         if (_dx == width && _dy == height)
645             return;
646         _dx = width;
647         _dy = height;
648         // fix window rect for platforms that don't set it yet
649         _windowRect.right = width;
650         _windowRect.bottom = height;
651         if (_mainWidget !is null) {
652             Log.d("onResize ", _dx, "x", _dy);
653             long measureStart = currentTimeMillis;
654             updateWindowOrContentSize();
655             measure();
656             //Log.d("measured size: ", _mainWidget.measuredWidth, "x", _mainWidget.measuredHeight);
657             long measureEnd = currentTimeMillis;
658             debug Log.d("resize: measure took ", measureEnd - measureStart, " ms");
659             layout();
660             long layoutEnd = currentTimeMillis;
661             debug Log.d("resize: layout took ", layoutEnd - measureEnd, " ms");
662             //Log.d("layout position: ", _mainWidget.pos);
663         }
664         update(true);
665     }
666 
667     protected PopupWidget[] _popups;
668 
669     protected static struct TooltipInfo {
670         PopupWidget popup;
671         ulong timerId;
672         Widget ownerWidget;
673         uint alignment;
674         int x;
675         int y;
676     }
677 
678     protected TooltipInfo _tooltip;
679 
680     /// schedule tooltip for widget be shown with specified delay
681     void scheduleTooltip(Widget ownerWidget, long delay, uint alignment = PopupAlign.Below, int x = 0, int y = 0) {
682         _tooltip.alignment = alignment;
683         _tooltip.x = x;
684         _tooltip.y = y;
685         _tooltip.ownerWidget = ownerWidget;
686         _tooltip.timerId = setTimer(ownerWidget, delay);
687     }
688 
689     /// call when tooltip timer is expired
690     private bool onTooltipTimer() {
691         _tooltip.timerId = 0;
692         if (isChild(_tooltip.ownerWidget)) {
693             Widget w = _tooltip.ownerWidget.createTooltip(_lastMouseX, _lastMouseY, _tooltip.alignment, _tooltip.x, _tooltip.y);
694             if (w)
695                 showTooltip(w, _tooltip.ownerWidget, _tooltip.alignment, _tooltip.x, _tooltip.y);
696         }
697         return false;
698     }
699 
700     /// called when user dragged file(s) to application window
701     void handleDroppedFiles(string[] filenames) {
702         //Log.d("handleDroppedFiles(", filenames, ")");
703         if (_onFilesDropped)
704             _onFilesDropped(filenames);
705     }
706 
707     protected void delegate(string[]) _onFilesDropped;
708     /// get handler for files dropped to app window
709     @property void delegate(string[]) onFilesDropped() { return _onFilesDropped; }
710     /// set handler for files dropped to app window
711     @property Window onFilesDropped(void delegate(string[]) handler) { _onFilesDropped = handler; return this; }
712 
713     protected bool delegate() _onCanClose;
714     /// get handler for closing of app (it must return true to allow immediate close, false to cancel close or close window later)
715     @property bool delegate() onCanClose() { return _onCanClose; }
716     /// set handler for closing of app (it must return true to allow immediate close, false to cancel close or close window later)
717     @property Window onCanClose(bool delegate() handler) { _onCanClose = handler; return this; }
718 
719     protected void delegate() _onClose;
720     /// get handler for closing of window
721     @property void delegate() onClose() { return _onClose; }
722     /// set handler for closing of window
723     @property Window onClose(void delegate() handler) { _onClose = handler; return this; }
724 
725     /// returns true if there is some modal window opened above this window, and this window should not process mouse/key input and should not allow closing
726     bool hasModalWindowsAbove() {
727         return platform.hasModalWindowsAbove(this);
728     }
729 
730     /// calls onCanClose handler if set to check if system may close window
731     bool handleCanClose() {
732         if (hasModalWindowsAbove())
733             return false;
734         if (!_onCanClose)
735             return true;
736         bool res = _onCanClose();
737         if (!res)
738             update(true); // redraw window if it was decided to not close immediately
739         return res;
740     }
741 
742 
743     /// hide tooltip if shown and cancel tooltip timer if set
744     void hideTooltip() {
745         if (_tooltip.popup) {
746             destroy(_tooltip.popup);
747             _tooltip.popup = null;
748             if (_mainWidget)
749                 _mainWidget.invalidate();
750         }
751         if (_tooltip.timerId)
752             cancelTimer(_tooltip.timerId);
753     }
754 
755     /// show tooltip immediately
756     PopupWidget showTooltip(Widget content, Widget anchor = null, uint alignment = PopupAlign.Center, int x = 0, int y = 0) {
757         hideTooltip();
758         if (!content)
759             return null;
760         PopupWidget res = new PopupWidget(content, this);
761         res.anchor.widget = anchor !is null ? anchor : _mainWidget;
762         res.anchor.alignment = alignment;
763         res.anchor.x = x;
764         res.anchor.y = y;
765         _tooltip.popup = res;
766         return res;
767     }
768 
769     /// show new popup
770     PopupWidget showPopup(Widget content, Widget anchor = null, uint alignment = PopupAlign.Center, int x = 0, int y = 0) {
771         PopupWidget res = new PopupWidget(content, this);
772         res.anchor.widget = anchor !is null ? anchor : _mainWidget;
773         res.anchor.alignment = alignment;
774         res.anchor.x = x;
775         res.anchor.y = y;
776         _popups ~= res;
777         if (_mainWidget !is null) {
778             _mainWidget.requestLayout();
779         }
780         update(false);
781         return res;
782     }
783     /// remove popup
784     bool removePopup(PopupWidget popup) {
785         if (!popup)
786             return false;
787         for (int i = 0; i < _popups.length; i++) {
788             PopupWidget p = _popups[i];
789             if (p is popup) {
790                 for (int j = i; j < _popups.length - 1; j++)
791                     _popups[j] = _popups[j + 1];
792                 _popups.length--;
793                 p.onClose();
794                 destroy(p);
795                 // force redraw
796                 _mainWidget.invalidate();
797                 return true;
798             }
799         }
800         return false;
801     }
802 
803     /// returns last modal popup widget, or null if no modal popups opened
804     PopupWidget modalPopup() {
805         for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
806             if (_popups[i].flags & PopupFlags.Modal)
807                 return _popups[i];
808         }
809         return null;
810     }
811 
812     /// returns true if widget is child of either main widget, one of popups or window scrollbar
813     bool isChild(Widget w) {
814         if (_mainWidget !is null && _mainWidget.isChild(w))
815             return true;
816         foreach(p; _popups)
817             if (p.isChild(w))
818                 return true;
819         if (_tooltip.popup)
820             if (_tooltip.popup.isChild(w))
821                 return true;
822         if (_hScrollBar !is null && _hScrollBar.isChild(w))
823             return true;
824         if (_vScrollBar !is null && _vScrollBar.isChild(w))
825             return true;
826         return false;
827     }
828 
829     private long lastDrawTs;
830 
831     this() {
832         _eventList = new EventList();
833         _timerQueue = new TimerQueue();
834         _backgroundColor = 0xFFFFFF;
835         if (currentTheme)
836             _backgroundColor = currentTheme.customColor(STYLE_COLOR_WINDOW_BACKGROUND);
837     }
838     ~this() {
839         debug Log.d("Destroying window");
840         if (_onClose)
841             _onClose();
842         if (_tooltip.popup) {
843             destroy(_tooltip.popup);
844             _tooltip.popup = null;
845         }
846         foreach(p; _popups)
847             destroy(p);
848         _popups = null;
849         if (_mainWidget !is null) {
850             destroy(_mainWidget);
851             _mainWidget = null;
852         }
853         if (_hScrollBar) {
854             destroy(_hScrollBar);
855             _hScrollBar = null;
856         }
857         if (_vScrollBar) {
858             destroy(_vScrollBar);
859             _vScrollBar = null;
860         }
861         destroy(_eventList);
862         destroy(_timerQueue);
863         _eventList = null;
864     }
865 
866     /**
867     Allows queue destroy of widget.
868 
869     Sometimes when you have very complicated UI with dynamic create/destroy lists of widgets calling simple destroy()
870     on widget makes segmentation fault.
871 
872     Usually because you destroy widget that on some stage call another that tries to destroy widget that calls it.
873     When the control flow returns widget not exist and you have seg. fault.
874 
875     This function use internally $(LINK2 $(DDOX_ROOT_DIR)dlangui/core/events/QueueDestroyEvent.html, QueueDestroyEvent).
876     */
877     void queueWidgetDestroy(Widget widgetToDestroy)
878     {
879         QueueDestroyEvent ev = new QueueDestroyEvent(widgetToDestroy);
880         postEvent(ev);
881     }
882 
883     private void animate(Widget root, long interval) {
884         if (root is null)
885             return;
886         if (root.visibility != Visibility.Visible)
887             return;
888         for (int i = 0; i < root.childCount; i++)
889             animate(root.child(i), interval);
890         if (root.animating)
891             root.animate(interval);
892     }
893 
894     private void animate(long interval) {
895         animate(_mainWidget, interval);
896         foreach(p; _popups)
897             p.animate(interval);
898         if (_tooltip.popup)
899             _tooltip.popup.animate(interval);
900     }
901 
902     static immutable int PERFORMANCE_LOGGING_THRESHOLD_MS = 20;
903 
904     /// set when first draw is called: don't handle mouse/key input until draw (layout) is called
905     protected bool _firstDrawCalled = false;
906     void onDraw(DrawBuf buf) {
907         _firstDrawCalled = true;
908         static import std.datetime;
909         try {
910             bool needDraw = false;
911             bool needLayout = false;
912             bool animationActive = false;
913             checkUpdateNeeded(needDraw, needLayout, animationActive);
914             if (needLayout || animationActive)
915                 needDraw = true;
916             long ts = std.datetime.Clock.currStdTime;
917             if (animationActive && lastDrawTs != 0) {
918                 animate(ts - lastDrawTs);
919                 // layout required flag could be changed during animate - check again
920                 checkUpdateNeeded(needDraw, needLayout, animationActive);
921             }
922             lastDrawTs = ts;
923             if (needLayout) {
924                 long measureStart = currentTimeMillis;
925                 measure();
926                 long measureEnd = currentTimeMillis;
927                 if (measureEnd - measureStart > PERFORMANCE_LOGGING_THRESHOLD_MS) {
928                     debug(DebugRedraw) Log.d("measure took ", measureEnd - measureStart, " ms");
929                 }
930                 layout();
931                 long layoutEnd = currentTimeMillis;
932                 if (layoutEnd - measureEnd > PERFORMANCE_LOGGING_THRESHOLD_MS) {
933                     debug(DebugRedraw) Log.d("layout took ", layoutEnd - measureEnd, " ms");
934                 }
935                 //checkUpdateNeeded(needDraw, needLayout, animationActive);
936             }
937             long drawStart = currentTimeMillis;
938             // draw main widget
939             _mainWidget.onDraw(buf);
940 
941             PopupWidget modal = modalPopup();
942 
943             // draw popups
944             foreach(p; _popups) {
945                 if (p is modal) {
946                     // TODO: get shadow color from theme
947                     buf.fillRect(Rect(0, 0, buf.width, buf.height), 0xD0404040);
948                 }
949                 p.onDraw(buf);
950             }
951 
952             if (_tooltip.popup)
953                 _tooltip.popup.onDraw(buf);
954 
955             if (_hScrollBar)
956                 _hScrollBar.onDraw(buf);
957             if (_vScrollBar)
958                 _vScrollBar.onDraw(buf);
959             if (_hScrollBar && _vScrollBar)
960                 buf.fillRect(Rect(_vScrollBar.left, _hScrollBar.top, buf.width, buf.height), _backgroundColor);
961 
962             long drawEnd = currentTimeMillis;
963             debug(DebugRedraw) {
964                 if (drawEnd - drawStart > PERFORMANCE_LOGGING_THRESHOLD_MS)
965                     Log.d("draw took ", drawEnd - drawStart, " ms");
966             }
967             if (animationActive)
968                 scheduleAnimation();
969             _actionsUpdateRequested = false;
970         } catch (Exception e) {
971             Log.e("Exception inside winfow.onDraw: ", e);
972         }
973     }
974 
975     /// after drawing, call to schedule redraw if animation is active
976     void scheduleAnimation() {
977         // override if necessary
978     }
979 
980 
981     protected void setCaptureWidget(Widget w, MouseEvent event) {
982         _mouseCaptureWidget = w;
983         _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
984     }
985 
986     protected Widget _focusedWidget;
987     protected auto _focusStateToApply = State.Focused;
988     /// returns current focused widget
989     @property Widget focusedWidget() {
990         if (!isChild(_focusedWidget))
991             _focusedWidget = null;
992         return _focusedWidget;
993     }
994 
995     /// change focus to widget
996     Widget setFocus(Widget newFocus, FocusReason reason = FocusReason.Unspecified) {
997         if (!isChild(_focusedWidget))
998             _focusedWidget = null;
999         Widget oldFocus = _focusedWidget;
1000         auto targetState = State.Focused;
1001         if(reason == FocusReason.TabFocus)
1002             targetState = State.Focused | State.KeyboardFocused;
1003         _focusStateToApply = targetState;
1004         if (oldFocus is newFocus)
1005             return oldFocus;
1006         if (oldFocus !is null) {
1007             oldFocus.resetState(targetState);
1008             if (oldFocus)
1009                 oldFocus.focusGroupFocused(false);
1010         }
1011         if (newFocus is null || isChild(newFocus)) {
1012             if (newFocus !is null) {
1013                 // when calling, setState(focused), window.focusedWidget is still previously focused widget
1014                 debug(DebugFocus) Log.d("new focus: ", newFocus.id);
1015                 newFocus.setState(targetState);
1016             }
1017             _focusedWidget = newFocus;
1018             if (_focusedWidget)
1019                 _focusedWidget.focusGroupFocused(true);
1020             // after focus change, ask for actions update automatically
1021             //requestActionsUpdate();
1022         }
1023         return _focusedWidget;
1024     }
1025 
1026     protected Widget removeFocus() {
1027         if (!isChild(_focusedWidget))
1028             _focusedWidget = null;
1029         if (_focusedWidget) {
1030             _focusedWidget.resetState(_focusStateToApply);
1031             update();
1032         }
1033         return _focusedWidget;
1034     }
1035 
1036     protected Widget applyFocus() {
1037         if (!isChild(_focusedWidget))
1038             _focusedWidget = null;
1039         if (_focusedWidget) {
1040             _focusedWidget.setState(_focusStateToApply);
1041             update();
1042         }
1043         return _focusedWidget;
1044     }
1045 
1046     @property bool isActive() {
1047         return true;
1048     }
1049 
1050     /// window state change signal
1051     Signal!OnWindowActivityHandler windowActivityChanged;
1052 
1053     protected void handleWindowActivityChange(bool isWindowActive) {
1054         if (isWindowActive)
1055             applyFocus();
1056         else
1057             removeFocus();
1058         if (windowActivityChanged.assigned)
1059             windowActivityChanged(this, isWindowActive);
1060     }
1061 
1062     /// dispatch key event to widgets which have wantsKeyTracking == true
1063     protected bool dispatchKeyEvent(Widget root, KeyEvent event) {
1064         if (root.visibility != Visibility.Visible)
1065             return false;
1066         if (root.wantsKeyTracking) {
1067             if (root.onKeyEvent(event))
1068                 return true;
1069         }
1070         for (int i = 0; i < root.childCount; i++) {
1071             Widget w = root.child(i);
1072             if (dispatchKeyEvent(w, event))
1073                 return true;
1074         }
1075         return false;
1076     }
1077 
1078     /// dispatch keyboard event
1079     bool dispatchKeyEvent(KeyEvent event) {
1080         if (hasModalWindowsAbove() || !_firstDrawCalled)
1081             return false;
1082         bool res = false;
1083         hideTooltip();
1084         PopupWidget modal = modalPopup();
1085         if (event.action == KeyAction.KeyDown || event.action == KeyAction.KeyUp) {
1086             _keyboardModifiers = event.flags;
1087             if (event.keyCode == KeyCode.ALT || event.keyCode == KeyCode.LALT || event.keyCode == KeyCode.RALT) {
1088                 debug(DebugKeys) Log.d("ALT key: keyboardModifiers = ", _keyboardModifiers);
1089                 if (_mainWidget) {
1090                     _mainWidget.invalidate();
1091                     res = true;
1092                 }
1093             }
1094         }
1095         if (event.action == KeyAction.Text) {
1096             // filter text
1097             if (event.text.length < 1)
1098                 return res;
1099             dchar ch = event.text[0];
1100             if (ch < ' ' || ch == 0x7F) // filter out control symbols
1101                 return res;
1102         }
1103         Widget focus = focusedWidget;
1104         if (!modal || modal.isChild(focus)) {
1105             while (focus) {
1106                 if (focus.onKeyEvent(event))
1107                     return true; // processed by focused widget
1108                 if (focus.focusGroup)
1109                     break;
1110                 focus = focus.parent;
1111             }
1112         }
1113         if (modal) {
1114             if (dispatchKeyEvent(modal, event))
1115                 return res;
1116             return modal.onKeyEvent(event) || res;
1117         } else if (_mainWidget) {
1118             if (dispatchKeyEvent(_mainWidget, event))
1119                 return res;
1120             return _mainWidget.onKeyEvent(event) || res;
1121         }
1122         return res;
1123     }
1124 
1125     /// Keep overrided cursor type or NotSet to get cursor from widget
1126     protected CursorType _overrideCursorType = CursorType.NotSet;
1127 
1128     /// Allow override cursor for entire window. Set to CursorType.NotSet to remove cursor type overriding.
1129     @property void overrideCursorType(CursorType newCursorType) {
1130         _overrideCursorType = newCursorType;
1131         setCursorType(newCursorType);
1132     }
1133 
1134     /// Returns current window override cursor type or NotSet if not overriding.
1135     @property CursorType overrideCursorType() {
1136         return _overrideCursorType;
1137     }
1138 
1139     protected bool dispatchMouseEvent(Widget root, MouseEvent event, ref bool cursorIsSet) {
1140         // only route mouse events to visible widgets
1141         if (root.visibility != Visibility.Visible)
1142             return false;
1143         if (!root.isPointInside(event.pos))
1144             return false;
1145         // offer event to children first
1146         for (int i = 0; i < root.childCount; i++) {
1147             Widget child = root.child(i);
1148             if (child.isPointInside(event.pos) && dispatchMouseEvent(child, event, cursorIsSet))
1149                 return true;
1150         }
1151 
1152         if (event.action == MouseAction.Move && !cursorIsSet) {
1153             uint cursorType = root.getCursorType(event.x, event.y);
1154             if (cursorType != CursorType.NotSet) {
1155                 setCursorType(cursorType);
1156                 cursorIsSet = true;
1157             }
1158         }
1159         // if not processed by children, offer event to root
1160         if (sendAndCheckOverride(root, event)) {
1161             debug(DebugMouseEvents) Log.d("MouseEvent is processed");
1162             if (event.action == MouseAction.ButtonDown && _mouseCaptureWidget is null && !event.doNotTrackButtonDown) {
1163                 debug(DebugMouseEvents) Log.d("Setting active widget");
1164                 setCaptureWidget(root, event);
1165             } else if (event.action == MouseAction.Move) {
1166                 addTracking(root);
1167             }
1168             return true;
1169         }
1170         return false;
1171     }
1172 
1173     /// widget which tracks Move events
1174     //protected Widget _mouseTrackingWidget;
1175     protected Widget[] _mouseTrackingWidgets;
1176     private void addTracking(Widget w) {
1177         for(int i = 0; i < _mouseTrackingWidgets.length; i++)
1178             if (w is _mouseTrackingWidgets[i])
1179                 return;
1180         //foreach(widget; _mouseTrackingWidgets)
1181         //    if (widget is w)
1182         //       return;
1183         //Log.d("addTracking ", w.id, " items before: ", _mouseTrackingWidgets.length);
1184         _mouseTrackingWidgets ~= w;
1185         //Log.d("addTracking ", w.id, " items after: ", _mouseTrackingWidgets.length);
1186     }
1187     private bool checkRemoveTracking(MouseEvent event) {
1188         bool res = false;
1189         for(int i = cast(int)_mouseTrackingWidgets.length - 1; i >=0; i--) {
1190             Widget w = _mouseTrackingWidgets[i];
1191             if (!isChild(w)) {
1192                 // std.algorithm.remove does not work for me
1193                 //_mouseTrackingWidgets.remove(i);
1194                 for (int j = i; j < _mouseTrackingWidgets.length - 1; j++)
1195                     _mouseTrackingWidgets[j] = _mouseTrackingWidgets[j + 1];
1196                 _mouseTrackingWidgets.length--;
1197                 continue;
1198             }
1199             if (event.action == MouseAction.Leave || !w.isPointInside(event.x, event.y)) {
1200                 // send Leave message
1201                 MouseEvent leaveEvent = new MouseEvent(event);
1202                 leaveEvent.changeAction(MouseAction.Leave);
1203                 res = w.onMouseEvent(leaveEvent) || res;
1204                 // std.algorithm.remove does not work for me
1205                 //Log.d("removeTracking ", w.id, " items before: ", _mouseTrackingWidgets.length);
1206                 //_mouseTrackingWidgets.remove(i);
1207                 //_mouseTrackingWidgets.length--;
1208                 for (int j = i; j < _mouseTrackingWidgets.length - 1; j++)
1209                     _mouseTrackingWidgets[j] = _mouseTrackingWidgets[j + 1];
1210                 _mouseTrackingWidgets.length--;
1211                 //Log.d("removeTracking ", w.id, " items after: ", _mouseTrackingWidgets.length);
1212             }
1213         }
1214         return res;
1215     }
1216 
1217     /// widget which tracks all events after processed ButtonDown
1218     protected Widget _mouseCaptureWidget;
1219     protected ushort _mouseCaptureButtons;
1220     protected bool _mouseCaptureFocusedOut;
1221     /// does current capture widget want to receive move events even if pointer left it
1222     protected bool _mouseCaptureFocusedOutTrackMovements;
1223 
1224     protected void clearMouseCapture() {
1225         _mouseCaptureWidget = null;
1226         _mouseCaptureFocusedOut = false;
1227         _mouseCaptureFocusedOutTrackMovements = false;
1228         _mouseCaptureButtons = 0;
1229     }
1230 
1231     protected bool dispatchCancel(MouseEvent event) {
1232         event.changeAction(MouseAction.Cancel);
1233         bool res = _mouseCaptureWidget.onMouseEvent(event);
1234         clearMouseCapture();
1235         return res;
1236     }
1237 
1238     protected bool sendAndCheckOverride(Widget widget, MouseEvent event) {
1239         if (!isChild(widget))
1240             return false;
1241         bool res = widget.onMouseEvent(event);
1242         if (event.trackingWidget !is null && _mouseCaptureWidget !is event.trackingWidget) {
1243             setCaptureWidget(event.trackingWidget, event);
1244         }
1245         return res;
1246     }
1247 
1248     /// returns true if mouse is currently captured
1249     bool isMouseCaptured() {
1250         return (_mouseCaptureWidget !is null && isChild(_mouseCaptureWidget));
1251     }
1252 
1253     /// dispatch action to main widget
1254     bool dispatchAction(const Action action, Widget sourceWidget = null) {
1255         // try to handle by source widget
1256         if(sourceWidget && isChild(sourceWidget)) {
1257             if (sourceWidget.handleAction(action))
1258                 return true;
1259             sourceWidget = sourceWidget.parent;
1260         }
1261         Widget focus = focusedWidget;
1262         // then offer action to focused widget
1263         if (focus && isChild(focus)) {
1264             if (focus.handleAction(action))
1265                 return true;
1266             focus = focus.parent;
1267         }
1268         // then offer to parent chain of source widget
1269         while (sourceWidget && isChild(sourceWidget)) {
1270             if (sourceWidget.handleAction(action))
1271                 return true;
1272             sourceWidget = sourceWidget.parent;
1273         }
1274         // then offer to parent chain of focused widget
1275         while (focus && isChild(focus)) {
1276             if (focus.handleAction(action))
1277                 return true;
1278             focus = focus.parent;
1279         }
1280         if (_mainWidget)
1281             return _mainWidget.handleAction(action);
1282         return false;
1283     }
1284 
1285     /// dispatch action to main widget
1286     bool dispatchActionStateRequest(const Action action, Widget sourceWidget = null) {
1287         // try to handle by source widget
1288         if(sourceWidget && isChild(sourceWidget)) {
1289             if (sourceWidget.handleActionStateRequest(action))
1290                 return true;
1291             sourceWidget = sourceWidget.parent;
1292         }
1293         Widget focus = focusedWidget;
1294         // then offer action to focused widget
1295         if (focus && isChild(focus)) {
1296             if (focus.handleActionStateRequest(action))
1297                 return true;
1298             focus = focus.parent;
1299         }
1300         // then offer to parent chain of source widget
1301         while (sourceWidget && isChild(sourceWidget)) {
1302             if (sourceWidget.handleActionStateRequest(action))
1303                 return true;
1304             sourceWidget = sourceWidget.parent;
1305         }
1306         // then offer to parent chain of focused widget
1307         while (focus && isChild(focus)) {
1308             if (focus.handleActionStateRequest(action))
1309                 return true;
1310             focus = focus.parent;
1311         }
1312         if (_mainWidget)
1313             return _mainWidget.handleActionStateRequest(action);
1314         return false;
1315     }
1316 
1317     /// handle theme change: e.g. reload some themed resources
1318     void dispatchThemeChanged() {
1319         if (_hScrollBar)
1320             _hScrollBar.onThemeChanged();
1321         if (_vScrollBar)
1322             _vScrollBar.onThemeChanged();
1323         if (_mainWidget)
1324             _mainWidget.onThemeChanged();
1325         // draw popups
1326         foreach(p; _popups) {
1327             p.onThemeChanged();
1328         }
1329         if (_tooltip.popup)
1330             _tooltip.popup.onThemeChanged();
1331         if (currentTheme) {
1332             _backgroundColor = currentTheme.customColor(STYLE_COLOR_WINDOW_BACKGROUND);
1333         }
1334         invalidate();
1335     }
1336 
1337 
1338     /// post event to handle in UI thread (this method can be used from background thread)
1339     void postEvent(CustomEvent event) {
1340         // override to post event into window message queue
1341         _eventList.put(event);
1342     }
1343 
1344     /// post task to execute in UI thread (this method can be used from background thread)
1345     void executeInUiThread(void delegate() runnable) {
1346         RunnableEvent event = new RunnableEvent(CUSTOM_RUNNABLE, null, runnable);
1347         postEvent(event);
1348     }
1349 
1350     /// Creates async socket
1351     AsyncSocket createAsyncSocket(AsyncSocketCallback callback) {
1352         AsyncClientConnection conn = new AsyncClientConnection(new AsyncSocketCallbackProxy(callback, &executeInUiThread));
1353         return conn;
1354     }
1355 
1356     /// remove event from queue by unique id if not yet dispatched (this method can be used from background thread)
1357     void cancelEvent(uint uniqueId) {
1358         CustomEvent ev = _eventList.get(uniqueId);
1359         if (ev) {
1360             //destroy(ev);
1361         }
1362     }
1363 
1364     /// remove event from queue by unique id if not yet dispatched and dispatch it
1365     void handlePostedEvent(uint uniqueId) {
1366         CustomEvent ev = _eventList.get(uniqueId);
1367         if (ev) {
1368             dispatchCustomEvent(ev);
1369         }
1370     }
1371 
1372     /// handle all events from queue, if any (call from UI thread only)
1373     void handlePostedEvents() {
1374         for(;;) {
1375             CustomEvent e = _eventList.get();
1376             if (!e)
1377                 break;
1378             dispatchCustomEvent(e);
1379         }
1380     }
1381 
1382     /// dispatch custom event
1383     bool dispatchCustomEvent(CustomEvent event) {
1384         if (event.destinationWidget) {
1385             if (!isChild(event.destinationWidget)) {
1386                 //Event is sent to widget which does not exist anymore
1387                 return false;
1388             }
1389             return event.destinationWidget.onEvent(event);
1390         } else {
1391             // no destination widget
1392             RunnableEvent runnable = cast(RunnableEvent)event;
1393             if (runnable) {
1394                 // handle runnable
1395                 runnable.run();
1396                 return true;
1397             }
1398         }
1399         return false;
1400     }
1401 
1402     private int _lastMouseX;
1403     private int _lastMouseY;
1404     /// dispatch mouse event to window content widgets
1405     bool dispatchMouseEvent(MouseEvent event) {
1406         if (hasModalWindowsAbove() || !_firstDrawCalled)
1407             return false;
1408         // ignore events if there is no root
1409         if (_mainWidget is null)
1410             return false;
1411 
1412         bool actualChange = true;
1413         if (event.action == MouseAction.Move) {
1414             actualChange = (_lastMouseX != event.x || _lastMouseY != event.y);
1415             _lastMouseX = event.x;
1416             _lastMouseY = event.y;
1417         }
1418         if (actualChange) hideTooltip();
1419 
1420         PopupWidget modal = modalPopup();
1421 
1422         // check if _mouseCaptureWidget and _mouseTrackingWidget still exist in child of root widget
1423         if (_mouseCaptureWidget !is null && (!isChild(_mouseCaptureWidget) || (modal && !modal.isChild(_mouseCaptureWidget)))) {
1424             clearMouseCapture();
1425         }
1426 
1427         debug(DebugMouseEvents) Log.d("dispatchMouseEvent ", event.action, "  (", event.x, ",", event.y, ")");
1428 
1429         bool res = false;
1430         ushort currentButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
1431         if (_mouseCaptureWidget !is null) {
1432             // try to forward message directly to active widget
1433             if (event.action == MouseAction.Move) {
1434                 debug(DebugMouseEvents) Log.d("dispatchMouseEvent: Move; buttons state=", currentButtons);
1435                 if (!_mouseCaptureWidget.isPointInside(event.x, event.y)) {
1436                     if (currentButtons != _mouseCaptureButtons) {
1437                         debug(DebugMouseEvents) Log.d("dispatchMouseEvent: Move; buttons state changed from ", _mouseCaptureButtons, " to ", currentButtons, " - cancelling capture");
1438                         return dispatchCancel(event);
1439                     }
1440                     // point is no more inside of captured widget
1441                     if (!_mouseCaptureFocusedOut) {
1442                         // sending FocusOut message
1443                         event.changeAction(MouseAction.FocusOut);
1444                         _mouseCaptureFocusedOut = true;
1445                         _mouseCaptureButtons = currentButtons;
1446                         _mouseCaptureFocusedOutTrackMovements = sendAndCheckOverride(_mouseCaptureWidget, event);
1447                         return true;
1448                     } else if (_mouseCaptureFocusedOutTrackMovements) {
1449                         // pointer is outside, but we still need to track pointer
1450                         return sendAndCheckOverride(_mouseCaptureWidget, event);
1451                     }
1452                     // don't forward message
1453                     return true;
1454                 } else {
1455                     // point is inside widget
1456                     if (_mouseCaptureFocusedOut) {
1457                         _mouseCaptureFocusedOut = false;
1458                         if (currentButtons != _mouseCaptureButtons)
1459                             return dispatchCancel(event);
1460                            event.changeAction(MouseAction.FocusIn); // back in after focus out
1461                     }
1462                     return sendAndCheckOverride(_mouseCaptureWidget, event);
1463                 }
1464             } else if (event.action == MouseAction.Leave) {
1465                 if (!_mouseCaptureFocusedOut) {
1466                     // sending FocusOut message
1467                     event.changeAction(MouseAction.FocusOut);
1468                     _mouseCaptureFocusedOut = true;
1469                     _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
1470                     return sendAndCheckOverride(_mouseCaptureWidget, event);
1471                 } else {
1472                     debug(DebugMouseEvents) Log.d("dispatchMouseEvent: mouseCaptureFocusedOut + Leave - cancelling capture");
1473                     return dispatchCancel(event);
1474                 }
1475             } else if (event.action == MouseAction.ButtonDown || event.action == MouseAction.ButtonUp) {
1476                 if (!_mouseCaptureWidget.isPointInside(event.x, event.y)) {
1477                     if (currentButtons != _mouseCaptureButtons) {
1478                         debug(DebugMouseEvents) Log.d("dispatchMouseEvent: ButtonUp/ButtonDown; buttons state changed from ", _mouseCaptureButtons, " to ", currentButtons, " - cancelling capture");
1479                         return dispatchCancel(event);
1480                     }
1481                 }
1482             }
1483             // other messages
1484             res = sendAndCheckOverride(_mouseCaptureWidget, event);
1485             if (!currentButtons) {
1486                 // usable capturing - no more buttons pressed
1487                 debug(DebugMouseEvents) Log.d("unsetting active widget");
1488                 clearMouseCapture();
1489             }
1490             return res;
1491         }
1492 
1493         bool processed = false;
1494         if (event.action == MouseAction.Move || event.action == MouseAction.Leave) {
1495             processed = checkRemoveTracking(event);
1496         }
1497 
1498         bool cursorIsSet = false;
1499         if (overrideCursorType != CursorType.NotSet) {
1500             cursorIsSet = true;
1501         }
1502 
1503         if (!res) {
1504             bool insideOneOfPopups = false;
1505             for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
1506                 auto p = _popups[i];
1507                 if (p.isPointInside(event.x, event.y)) {
1508                     if (p !is modal)
1509                         insideOneOfPopups = true;
1510                 }
1511                 if (p is modal)
1512                     break;
1513             }
1514             for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
1515                 auto p = _popups[i];
1516                 if (p is modal)
1517                     break;
1518                 if (!insideOneOfPopups) {
1519                     if (event.action == MouseAction.ButtonDown)
1520                         return true; // mouse button down should do nothing when click outside when popup visible
1521                     if (p.onMouseEventOutside(event)) {
1522                         return true; // mouse button up should do nothing when click outside when popup visible
1523                     }
1524                 } else {
1525                     if (dispatchMouseEvent(p, event, cursorIsSet))
1526                         return true;
1527                 }
1528             }
1529             if (!modal) {
1530                 res = false;
1531                 if (_hScrollBar)
1532                     res = dispatchMouseEvent(_hScrollBar, event, cursorIsSet);
1533                 if (!res && _vScrollBar)
1534                     res = dispatchMouseEvent(_vScrollBar, event, cursorIsSet);
1535                 if (!res)
1536                     res = dispatchMouseEvent(_mainWidget, event, cursorIsSet);
1537             }
1538             else {
1539                 if (_hScrollBar)
1540                     res = dispatchMouseEvent(_hScrollBar, event, cursorIsSet);
1541                 if (!res && _vScrollBar)
1542                     res = dispatchMouseEvent(_vScrollBar, event, cursorIsSet);
1543                 if (!res)
1544                         res = dispatchMouseEvent(modal, event, cursorIsSet);
1545             }
1546         }
1547         return res || processed || _mainWidget.needDraw;
1548     }
1549 
1550     /// calls update actions recursively
1551     protected void dispatchWidgetUpdateActionStateRecursive(Widget root) {
1552         if (root is null)
1553             return;
1554         root.updateActionState(true);
1555         for (int i = 0; i < root.childCount; i++)
1556             dispatchWidgetUpdateActionStateRecursive(root.child(i));
1557     }
1558     /// checks content widgets for necessary redraw and/or layout
1559     protected void checkUpdateNeeded(Widget root, ref bool needDraw, ref bool needLayout, ref bool animationActive) {
1560         if (root is null)
1561             return;
1562         if (root.visibility != Visibility.Visible)
1563             return;
1564         needDraw = root.needDraw || needDraw;
1565         if (!needLayout) {
1566             needLayout = root.needLayout || needLayout;
1567             if (needLayout) {
1568                 debug(DebugRedraw) Log.d("need layout: ", root.classinfo.name, " id=", root.id);
1569             }
1570         }
1571         if (root.animating && root.visible)
1572             animationActive = true; // check animation only for visible widgets
1573         for (int i = 0; i < root.childCount; i++)
1574             checkUpdateNeeded(root.child(i), needDraw, needLayout, animationActive);
1575     }
1576     /// sets cursor type for window
1577     protected void setCursorType(uint cursorType) {
1578         // override to support different mouse cursors
1579     }
1580     /// update action states
1581     protected void dispatchWidgetUpdateActionStateRecursive() {
1582         if (_mainWidget !is null)
1583             dispatchWidgetUpdateActionStateRecursive(_mainWidget);
1584         foreach(p; _popups)
1585             dispatchWidgetUpdateActionStateRecursive(p);
1586     }
1587     /// checks content widgets for necessary redraw and/or layout
1588     bool checkUpdateNeeded(ref bool needDraw, ref bool needLayout, ref bool animationActive) {
1589         if (_actionsUpdateRequested) {
1590             // call update action check - as requested
1591             dispatchWidgetUpdateActionStateRecursive();
1592             _actionsUpdateRequested = false;
1593         }
1594         needDraw = needLayout = animationActive = false;
1595         if (_mainWidget is null)
1596             return false;
1597         checkUpdateNeeded(_mainWidget, needDraw, needLayout, animationActive);
1598         if (_hScrollBar)
1599             checkUpdateNeeded(_hScrollBar, needDraw, needLayout, animationActive);
1600         if (_vScrollBar)
1601             checkUpdateNeeded(_vScrollBar, needDraw, needLayout, animationActive);
1602         foreach(p; _popups)
1603             checkUpdateNeeded(p, needDraw, needLayout, animationActive);
1604         if (_tooltip.popup)
1605             checkUpdateNeeded(_tooltip.popup, needDraw, needLayout, animationActive);
1606         return needDraw || needLayout || animationActive;
1607     }
1608 
1609     protected bool _animationActive;
1610 
1611     @property bool isAnimationActive() {
1612         return _animationActive;
1613     }
1614 
1615     /// requests update for window (unless force is true, update will be performed only if layout, redraw or animation is required).
1616     void update(bool force = false) {
1617         if (_mainWidget is null)
1618             return;
1619         bool needDraw = false;
1620         bool needLayout = false;
1621         _animationActive = false;
1622         if (checkUpdateNeeded(needDraw, needLayout, _animationActive) || force) {
1623             debug(DebugRedraw) Log.d("Requesting update");
1624             invalidate();
1625         }
1626         debug(DebugRedraw) Log.d("checkUpdateNeeded returned needDraw=", needDraw, " needLayout=", needLayout, " animationActive=", _animationActive);
1627     }
1628 
1629     protected bool _actionsUpdateRequested = true;
1630 
1631     /// set action update request flag, will be cleared after redraw
1632     void requestActionsUpdate(bool immediateUpdate = false) {
1633         if (!immediateUpdate)
1634             _actionsUpdateRequested = true;
1635         else
1636             dispatchWidgetUpdateActionStateRecursive();
1637     }
1638 
1639     @property bool actionsUpdateRequested() {
1640         return _actionsUpdateRequested;
1641     }
1642 
1643     /// Show message box with specified title and message (title and message as UIString)
1644     void showMessageBox(UIString title, UIString message, const (Action)[] actions = [ACTION_OK], int defaultActionIndex = 0, bool delegate(const Action result) handler = null) {
1645         import dlangui.dialogs.msgbox;
1646         MessageBox dlg = new MessageBox(title, message, this, actions, defaultActionIndex, handler);
1647         dlg.show();
1648     }
1649 
1650     /// Show message box with specified title and message (title and message as dstring)
1651     void showMessageBox(dstring title, dstring message, const (Action)[] actions = [ACTION_OK], int defaultActionIndex = 0, bool delegate(const Action result) handler = null) {
1652         showMessageBox(UIString.fromRaw(title), UIString.fromRaw(message), actions, defaultActionIndex, handler);
1653     }
1654 
1655     static if (BACKEND_GUI) {
1656         void showInputBox(UIString title, UIString message, dstring initialText, void delegate(dstring result) handler) {
1657             import dlangui.dialogs.inputbox;
1658             InputBox dlg = new InputBox(title, message, this, initialText, handler);
1659             dlg.show();
1660         }
1661     }
1662 
1663     void showInputBox(dstring title, dstring message, dstring initialText, void delegate(dstring result) handler) {
1664         showInputBox(UIString.fromRaw(title), UIString.fromRaw(message), initialText, handler);
1665     }
1666 
1667     protected TimerQueue _timerQueue;
1668 
1669 
1670     /// schedule timer for interval in milliseconds - call window.onTimer when finished
1671     protected void scheduleSystemTimer(long intervalMillis) {
1672         //debug Log.d("override scheduleSystemTimer to support timers");
1673     }
1674 
1675     /// poll expired timers; returns true if update is needed
1676     bool pollTimers() {
1677         bool res = _timerQueue.notify();
1678         if (res)
1679             update(false);
1680         return res;
1681     }
1682 
1683     /// system timer interval expired - notify queue
1684     protected void onTimer() {
1685         //Log.d("window.onTimer");
1686         bool res = _timerQueue.notify();
1687         //Log.d("window.onTimer after notify");
1688         if (res) {
1689             // check if update needed and redraw if so
1690             //Log.d("before update");
1691             update(false);
1692             //Log.d("after update");
1693         }
1694         //Log.d("schedule next timer");
1695         long nextInterval = _timerQueue.nextIntervalMillis();
1696         if (nextInterval > 0) {
1697             scheduleSystemTimer(nextInterval);
1698         }
1699         //Log.d("schedule next timer done");
1700     }
1701 
1702     /// set timer for destination widget - destination.onTimer() will be called after interval expiration; returns timer id
1703     ulong setTimer(Widget destination, long intervalMillis) {
1704         if (!isChild(destination)) {
1705             Log.e("setTimer() is called not for child widget of window");
1706             return 0;
1707         }
1708         ulong res = _timerQueue.add(destination, intervalMillis);
1709         long nextInterval = _timerQueue.nextIntervalMillis();
1710         if (nextInterval > 0) {
1711             scheduleSystemTimer(intervalMillis);
1712         }
1713         return res;
1714     }
1715 
1716     /// cancel previously scheduled widget timer (for timerId pass value returned from setTimer)
1717     void cancelTimer(ulong timerId) {
1718         _timerQueue.cancelTimer(timerId);
1719     }
1720 
1721     /// timers queue
1722     private class TimerQueue {
1723         protected TimerInfo[] _queue;
1724         /// add new timer
1725         ulong add(Widget destination, long intervalMillis) {
1726             TimerInfo item = new TimerInfo(destination, intervalMillis);
1727             _queue ~= item;
1728             sort(_queue);
1729             return item.id;
1730         }
1731         /// cancel timer
1732         void cancelTimer(ulong timerId) {
1733             if (!_queue.length)
1734                 return;
1735             for (int i = cast(int)_queue.length - 1; i >= 0; i--) {
1736                 if (_queue[i].id == timerId) {
1737                     _queue[i].cancel();
1738                     break;
1739                 }
1740             }
1741         }
1742         /// returns interval if millis of next scheduled event or -1 if no events queued
1743         long nextIntervalMillis() {
1744             if (!_queue.length || !_queue[0].valid)
1745                 return -1;
1746             long delta = _queue[0].nextTimestamp - currentTimeMillis;
1747             if (delta < 1)
1748                 delta = 1;
1749             return delta;
1750         }
1751         private void cleanup() {
1752             if (!_queue.length)
1753                 return;
1754             sort(_queue);
1755             size_t newsize = _queue.length;
1756             for (int i = cast(int)_queue.length - 1; i >= 0; i--) {
1757                 if (!_queue[i].valid) {
1758                     newsize = i;
1759                 }
1760             }
1761             if (_queue.length > newsize)
1762                 _queue.length = newsize;
1763         }
1764         private TimerInfo[] expired() {
1765             if (!_queue.length)
1766                 return null;
1767             long ts = currentTimeMillis;
1768             TimerInfo[] res;
1769             for (int i = 0; i < _queue.length; i++) {
1770                 if (_queue[i].nextTimestamp <= ts)
1771                     res ~= _queue[i];
1772             }
1773             return res;
1774         }
1775         /// returns true if at least one widget was notified
1776         bool notify() {
1777             bool res = false;
1778             checkValidWidgets();
1779             TimerInfo[] list = expired();
1780             if (list) {
1781                 for (int i = 0; i < list.length; i++) {
1782                     if (_queue[i].id == _tooltip.timerId) {
1783                         // special case for tooltip timer
1784                         onTooltipTimer();
1785                         _queue[i].cancel();
1786                         res = true;
1787                     } else {
1788                         Widget w = _queue[i].targetWidget;
1789                         if (w && !isChild(w))
1790                             _queue[i].cancel();
1791                         else {
1792                             _queue[i].notify();
1793                             res = true;
1794                         }
1795                     }
1796                 }
1797             }
1798             cleanup();
1799             return res;
1800         }
1801         private void checkValidWidgets() {
1802             for (int i = 0; i < _queue.length; i++) {
1803                 Widget w = _queue[i].targetWidget;
1804                 if (w && !isChild(w))
1805                     _queue[i].cancel();
1806             }
1807             cleanup();
1808         }
1809     }
1810 
1811 
1812 }
1813 
1814 /**
1815  * Platform abstraction layer.
1816  *
1817  * Represents application.
1818  *
1819  *
1820  *
1821  */
1822 class Platform {
1823     static __gshared Platform _instance;
1824     static void setInstance(Platform instance) {
1825         if (_instance)
1826             destroy(_instance);
1827         _instance = instance;
1828     }
1829     @property static Platform instance() {
1830         return _instance;
1831     }
1832 
1833     /**
1834      * create window
1835      * Args:
1836      *         windowCaption = window caption text
1837      *         parent = parent Window, or null if no parent
1838      *         flags = WindowFlag bit set, combination of Resizable, Modal, Fullscreen
1839      *      width = window width
1840      *      height = window height
1841      *
1842      * Window w/o Resizable nor Fullscreen will be created with size based on measurement of its content widget
1843      */
1844     abstract Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0);
1845 
1846     static if (ENABLE_OPENGL) {
1847         /**
1848          * OpenGL context major version.
1849          * Note: if the version is invalid or not supported, this value will be set to supported one.
1850          */
1851         int GLVersionMajor = 3;
1852         /**
1853          * OpenGL context minor version.
1854          * Note: if the version is invalid or not supported, this value will be set to supported one.
1855          */
1856         int GLVersionMinor = 2;
1857 
1858         /**
1859          * OpenGL multisamples amount.
1860          * Note: 0, 2, 4, 8, or 16 are accepted.
1861          * TODO: Not supported on Linux
1862          */
1863         int multisamples = 0;
1864     }
1865 
1866     /**
1867      * Returns the amount of multisamples. Returns 0 if no OpenGL support
1868      */
1869     int getMultisamples()
1870     {
1871         static if(ENABLE_OPENGL)
1872             return multisamples;
1873         else
1874             return 0;
1875     }
1876     /**
1877      * close window
1878      *
1879      * Closes window earlier created with createWindow()
1880      */
1881     abstract void closeWindow(Window w);
1882     /**
1883      * Starts application message loop.
1884      *
1885      * When returned from this method, application is shutting down.
1886      */
1887     abstract int enterMessageLoop();
1888     /// check has clipboard text
1889     abstract bool hasClipboardText(bool mouseBuffer = false);
1890     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1891     abstract dstring getClipboardText(bool mouseBuffer = false);
1892     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1893     abstract void setClipboardText(dstring text, bool mouseBuffer = false);
1894 
1895     /// calls request layout for all windows
1896     abstract void requestLayout();
1897 
1898     /// returns true if there is some modal window opened above this window, and this window should not process mouse/key input and should not allow closing
1899     bool hasModalWindowsAbove(Window w) {
1900         // override in platform specific class
1901         return false;
1902     }
1903 
1904 
1905     protected string _uiLanguage;
1906     /// returns currently selected UI language code
1907     @property string uiLanguage() {
1908         return _uiLanguage;
1909     }
1910     /// set UI language (e.g. "en", "fr", "ru") - will relayout content of all windows if language has been changed
1911     @property Platform uiLanguage(string langCode) {
1912         if (_uiLanguage.equal(langCode))
1913             return this;
1914         _uiLanguage = langCode;
1915 
1916         Log.v("Loading language file");
1917         if (langCode.equal("en"))
1918             i18n.load("en.ini"); //"ru.ini", "en.ini"
1919         else
1920             i18n.load(langCode ~ ".ini", "en.ini");
1921         Log.v("Calling onThemeChanged");
1922         onThemeChanged();
1923         requestLayout();
1924         return this;
1925     }
1926     protected string _themeId;
1927     @property string uiTheme() {
1928         return _themeId;
1929     }
1930     /// sets application UI theme - will relayout content of all windows if theme has been changed
1931     @property Platform uiTheme(string themeResourceId) {
1932         if (_themeId.equal(themeResourceId))
1933             return this;
1934         Log.v("uiTheme setting new theme ", themeResourceId);
1935         _themeId = themeResourceId;
1936         Theme theme = loadTheme(themeResourceId);
1937         if (!theme) {
1938             Log.e("Cannot load theme from resource ", themeResourceId, " - will use default theme");
1939             theme = createDefaultTheme();
1940         } else {
1941             Log.i("Applying loaded theme ", theme.id);
1942         }
1943         currentTheme = theme;
1944         onThemeChanged();
1945         requestLayout();
1946         return this;
1947     }
1948 
1949     /// to set uiLanguage and themeId to default (en, theme_default) if not set yet
1950     protected void setDefaultLanguageAndThemeIfNecessary() {
1951         if (!_uiLanguage) {
1952             Log.v("setDefaultLanguageAndThemeIfNecessary : setting UI language");
1953             uiLanguage = "en";
1954         }
1955         if (!_themeId) {
1956             Log.v("setDefaultLanguageAndThemeIfNecessary : setting UI theme");
1957             uiTheme = "theme_default";
1958         }
1959     }
1960 
1961     protected ulong _uiDialogDisplayMode = DialogDisplayMode.messageBoxInPopup | DialogDisplayMode.inputBoxInPopup;
1962     /// returns how dialogs should be displayed - as popup or window
1963     @property ulong uiDialogDisplayMode() {
1964         return _uiDialogDisplayMode;
1965     }
1966 
1967     // sets how dialogs should be displayed - as popup or window - use DialogDisplayMode enumeration
1968     @property Platform uiDialogDisplayMode(ulong newDialogDisplayMode) {
1969         _uiDialogDisplayMode = newDialogDisplayMode;
1970         return this;
1971     }
1972 
1973     protected string[] _resourceDirs;
1974     /// returns list of resource directories
1975     @property string[] resourceDirs() { return _resourceDirs; }
1976     /// set list of directories to load resources from
1977     @property Platform resourceDirs(string[] dirs) {
1978         // setup resource directories - will use only existing directories
1979         drawableCache.setResourcePaths(dirs);
1980         _resourceDirs = drawableCache.resourcePaths;
1981         // setup i18n - look for i18n directory inside one of passed directories
1982         i18n.findTranslationsDir(dirs);
1983         return this;
1984     }
1985 
1986     /// open url in external browser
1987     bool openURL(string url) {
1988         import std.process;
1989         browse(url);
1990         return true;
1991     }
1992 
1993     /// show directory or file in OS file manager (explorer, finder, etc...)
1994     bool showInFileManager(string pathName) {
1995         static import dlangui.core.filemanager;
1996         return dlangui.core.filemanager.showInFileManager(pathName);
1997     }
1998 
1999     /// handle theme change: e.g. reload some themed resources
2000     void onThemeChanged() {
2001         // override and call dispatchThemeChange for all windows
2002         drawableCache.clear();
2003         static if (!WIDGET_STYLE_CONSOLE) {
2004             imageCache.checkpoint();
2005             imageCache.cleanup();
2006         }
2007     }
2008 
2009     /// default icon for new created windows
2010     private string _defaultWindowIcon = "dlangui-logo1";
2011 
2012     /// sets default icon for new created windows
2013     @property void defaultWindowIcon(string newIcon) {
2014         _defaultWindowIcon = newIcon;
2015     }
2016 
2017     /// gets default icon for new created windows
2018     @property string defaultWindowIcon() {
2019         return _defaultWindowIcon;
2020     }
2021 
2022     private IconProviderBase _iconProvider;
2023     @property IconProviderBase iconProvider() {
2024         if (_iconProvider is null) {
2025             try {
2026                 _iconProvider = new NativeIconProvider();
2027             } catch(Exception e) {
2028                 Log.e("Error while creating icon provider.", e);
2029                 Log.d("Could not create native icon provider, fallbacking to the dummy one");
2030                 _iconProvider = new DummyIconProvider();
2031             }
2032         }
2033         return _iconProvider;
2034     }
2035 
2036     @property IconProviderBase iconProvider(IconProviderBase provider)
2037     {
2038         _iconProvider = provider;
2039         return _iconProvider;
2040     }
2041 
2042     ~this()
2043     {
2044         if(_iconProvider) {
2045             destroy(_iconProvider);
2046         }
2047     }
2048 }
2049 
2050 /// get current platform object instance
2051 @property Platform platform() {
2052     return Platform.instance;
2053 }
2054 
2055 static if (ENABLE_OPENGL) {
2056     private __gshared bool _OPENGL_ENABLED = true;
2057     /// check if hardware acceleration is enabled
2058     @property bool openglEnabled() { return _OPENGL_ENABLED; }
2059     /// call on app initialization if OpenGL support is detected
2060     void setOpenglEnabled(bool enabled = true) {
2061         _OPENGL_ENABLED = enabled;
2062         if (enabled)
2063             glyphDestroyCallback = &onGlyphDestroyedCallback;
2064         else
2065             glyphDestroyCallback = null;
2066     }
2067 } else {
2068     @property bool openglEnabled() { return false; }
2069     void setOpenglEnabled(bool enabled = true) {
2070         // ignore
2071     }
2072 }
2073 
2074 static if (BACKEND_CONSOLE) {
2075     // to remove import
2076     extern(C) int DLANGUImain(string[] args);
2077 } else {
2078     version (Windows) {
2079         // to remove import
2080         extern(Windows) int DLANGUIWinMain(void* hInstance, void* hPrevInstance,
2081                                            char* lpCmdLine, int nCmdShow);
2082         extern(Windows)
2083             int DLANGUIWinMainProfile(string[] args);
2084     } else {
2085         // to remove import
2086         extern(C) int DLANGUImain(string[] args);
2087     }
2088 }
2089 
2090 /// put "mixin APP_ENTRY_POINT;" to main module of your dlangui based app
2091 mixin template APP_ENTRY_POINT() {
2092     version (unittest) {
2093         // no main in unit tests
2094     } else {
2095         static if (BACKEND_CONSOLE) {
2096             int main(string[] args)
2097             {
2098                 return DLANGUImain(args);
2099             }
2100         } else {
2101             /// workaround for link issue when WinMain is located in library
2102             version(Windows) {
2103                 version (ENABLE_PROFILING) {
2104                     int main(string[] args)
2105                     {
2106                         return DLANGUIWinMainProfile(args);
2107                     }
2108                 } else {
2109                     extern (Windows) int WinMain(void* hInstance, void* hPrevInstance,
2110                                                     char* lpCmdLine, int nCmdShow)
2111                     {
2112                         try {
2113                             int res = DLANGUIWinMain(hInstance, hPrevInstance,
2114                                                         lpCmdLine, nCmdShow);
2115                             return res;
2116                         } catch (Exception e) {
2117                             Log.e("Exception: ", e);
2118                             return 1;
2119                         }
2120                     }
2121                 }
2122             } else {
2123                 version (Android) {
2124                 } else {
2125                     int main(string[] args)
2126                     {
2127                         return DLANGUImain(args);
2128                     }
2129                 }
2130             }
2131         }
2132     }
2133 }
2134 
2135 /// initialize font manager on startup
2136 extern(C) bool initFontManager();
2137 /// initialize logging (for win32 - to file ui.log, for other platforms - stderr; log level is TRACE for debug builds, and WARN for release builds)
2138 extern(C) void initLogs();
2139 /// call this when all resources are supposed to be freed to report counts of non-freed resources by type
2140 extern(C) void releaseResourcesOnAppExit();
2141 /// call this on application initialization
2142 extern(C) void initResourceManagers();
2143 /// call this from shared static this()
2144 extern (C) void initSharedResourceManagers();
2145 
2146