1 // Written in the D programming language.
2 
3 /**
4 This module contains common Dialog implementation.
5 
6 
7 Use to create custom dialogs.
8 
9 Synopsis:
10 
11 ----
12 import dlangui.dialogs.dialog;
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.dialogs.dialog;
21 
22 import dlangui.core.i18n;
23 import dlangui.core.signals;
24 import dlangui.core.stdaction;
25 import dlangui.widgets.layouts;
26 import dlangui.widgets.controls;
27 import dlangui.widgets.winframe;
28 import dlangui.widgets.popup;
29 import dlangui.platforms.common.platform;
30 
31 import std.conv;
32 
33 /// dialog flag bits
34 enum DialogFlag : uint {
35     /// dialog is modal
36     Modal = 1,
37     /// dialog can be resized
38     Resizable = 2,
39     /// dialog is show in popup widget inside current window instead of separate window
40     Popup = 4,
41 }
42 
43 /// slot to pass dialog result
44 interface DialogResultHandler {
45     public void onDialogResult(Dialog dlg, const Action result);
46 }
47 
48 /// base for all dialogs
49 class Dialog : VerticalLayout {
50     protected Window _window;
51     protected Window _parentWindow;
52     protected PopupWidget _popup;
53     protected UIString _caption;
54     protected uint _flags;
55     protected string _icon;
56     protected int _initialWidth;
57     protected int _initialHeight;
58 
59     Signal!DialogResultHandler dialogResult;
60 
61     this(UIString caption, Window parentWindow = null, uint flags = DialogFlag.Modal, int initialWidth = 0, int initialHeight = 0) {
62         super("dialog-main-widget");
63         _initialWidth = initialWidth;
64         _initialHeight = initialHeight;
65         _caption = caption;
66         _parentWindow = parentWindow;
67         _flags = flags;
68         _icon = "dlangui-logo1";
69     }
70 
71     /** 
72         Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 
73     */
74     override void measure(int parentWidth, int parentHeight) { 
75         super.measure(parentWidth, parentHeight);
76         if ((_flags & DialogFlag.Resizable) && (_flags & DialogFlag.Popup)) {
77             Point sz = Point(_parentWindow.width * 4 / 5, _parentWindow.height * 4 / 5);
78             measuredContent(parentWidth, parentHeight, sz.x, sz.y);
79         }
80     }
81 
82     /// get icon resource id
83     @property string windowIcon() {
84         return _icon;
85     }
86 
87     /// set icon resource id
88     @property Dialog windowIcon(string iconResourceId) {
89         _icon = iconResourceId;
90         static if (BACKEND_GUI) {
91             if (_window && _icon)
92                 _window.windowIcon = drawableCache.getImage(_icon);
93         }
94         return this;
95     }
96 
97     @property UIString windowCaption() {
98         return _caption;
99     }
100 
101     /// set window caption
102     @property Dialog windowCaption(dstring caption) {
103         _caption = caption;
104         if (_window)
105             _window.windowCaption = caption;
106         return this;
107     }
108 
109     /// get window caption
110     @property Dialog windowCaption(UIString caption) {
111         _caption = caption;
112         if (_window)
113             _window.windowCaption = caption;
114         return this;
115     }
116 
117     protected const(Action) [] _buttonActions;
118 
119     protected ImageTextButton _defaultButton;
120     protected ImageTextButton _cancelButton;
121     /// create panel with buttons based on list of actions
122     Widget createButtonsPanel(const(Action) [] actions, int defaultActionIndex, int splitBeforeIndex) {
123         _buttonActions = actions;
124         LinearLayout res = new HorizontalLayout("buttons");
125         res.layoutWidth(FILL_PARENT);
126         res.layoutWeight = 0;
127         for (int i = 0; i < actions.length; i++) {
128             if (splitBeforeIndex == i)
129                 res.addChild(new HSpacer());
130             const Action a = actions[i];
131             string id = "btn" ~ to!string(a.id);
132             ImageTextButton btn = new ImageTextButton(id, a.iconId, a.label);
133             if (defaultActionIndex == i) {
134                 btn.setState(State.Default);
135                 _defaultButton = btn;
136             }
137             if (a.id == StandardAction.Cancel || a.id == StandardAction.No)
138                 _cancelButton = btn;
139             btn.action = a.clone();
140             res.addChild(btn);
141         }
142         return res;
143     }
144 
145     /// map key to action
146     override Action findKeyAction(uint keyCode, uint flags) {
147         foreach(a; _buttonActions) {
148             if (a.checkAccelerator(keyCode, flags))
149                 return a.clone;
150         }
151         return super.findKeyAction(keyCode, flags);
152     }
153 
154     /// Custom handling of actions
155     override bool handleAction(const Action action) {
156         foreach(const Action a; _buttonActions)
157             if (a.id == action.id) {
158                 close(action);
159                 return true;
160             }
161         return false;
162     }
163 
164     /// override to implement creation of dialog controls
165     void initialize() {
166     }
167 
168     /** Notify about dialog result, and then close dialog.
169 
170         If onDialogResult listener is assigned, pass action to it.
171 
172         If no onDialogResult listener, pass to owner window.
173 
174         If action is null, no result dispatching will occur.
175       */
176     void close(const Action action) {
177         if (action) {
178             if (dialogResult.assigned)
179                 dialogResult(this, action);
180             else if (_parentWindow && !_popup)
181                 _parentWindow.dispatchAction(action);
182         }
183         if (_popup)
184             _parentWindow.removePopup(_popup);
185         else
186             window.close();
187     }
188 
189     /// shows dialog
190     void show() {
191         initialize();
192         uint wflags = 0;
193         if (_flags & DialogFlag.Modal)
194             wflags |= WindowFlag.Modal;
195         if (_flags & DialogFlag.Resizable) {
196             wflags |= WindowFlag.Resizable;
197             layoutWidth = FILL_PARENT;
198             layoutHeight = FILL_PARENT;
199         }
200         if (_flags & DialogFlag.Popup) {
201             DialogFrame _frame = new DialogFrame(this, _cancelButton !is null);
202             if (_cancelButton) {
203                 _frame.closeButtonClick = delegate(Widget w) {
204                     close(_cancelButton.action);
205                     return true;
206                 };
207             }
208             _popup = _parentWindow.showPopup(_frame);
209             _popup.flags(PopupFlags.Modal);
210         } else {
211             _window = Platform.instance.createWindow(_caption, _parentWindow, wflags, _initialWidth, _initialHeight);
212             static if (BACKEND_GUI) {
213                 if (_window && _icon)
214                     _window.windowIcon = drawableCache.getImage(_icon);
215             }
216             _window.backgroundColor = currentTheme.customColor("dialog_background");
217             _window.mainWidget = this;
218             _window.show();
219         }
220         onShow();
221     }
222 
223     /// called after window with dialog is shown
224     void onShow() {
225         // override to do something useful
226         if (_defaultButton)
227             _defaultButton.setFocus();
228     }
229 }
230 
231 /// frame with caption for dialog
232 class DialogFrame : WindowFrame {
233     protected Dialog _dialog;
234     this(Dialog dialog, bool enableCloseButton) {
235         super(dialog.id ~ "_frame", enableCloseButton);
236         styleId = STYLE_FLOATING_WINDOW;
237         _dialog = dialog;
238         _caption.text = _dialog.windowCaption.value;
239         bodyWidget = _dialog;
240     }
241 }