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