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 protected int _defaultButtonIndex = -1;
59
60 Signal!DialogResultHandler dialogResult;
61
62 this(UIString caption, Window parentWindow = null, uint flags = DialogFlag.Modal, int initialWidth = 0, int initialHeight = 0) {
63 super("dialog-main-widget");
64 _initialWidth = initialWidth;
65 _initialHeight = initialHeight;
66 _caption = caption;
67 _parentWindow = parentWindow;
68 _flags = flags;
69 _icon = "";
70 }
71
72 /**
73 Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
74 */
75 override void measure(int parentWidth, int parentHeight) {
76 super.measure(parentWidth, parentHeight);
77 if ((_flags & DialogFlag.Resizable) && (_flags & DialogFlag.Popup)) {
78 Point sz = Point(_parentWindow.width * 4 / 5, _parentWindow.height * 4 / 5);
79 measuredContent(parentWidth, parentHeight, sz.x, sz.y);
80 }
81 }
82
83 /// get icon resource id
84 @property string windowIcon() {
85 return _icon;
86 }
87
88 /// set icon resource id
89 @property Dialog windowIcon(string iconResourceId) {
90 _icon = iconResourceId;
91 static if (BACKEND_GUI) {
92 if (_window) {
93 if (_icon.length == 0)
94 _window.windowIcon = drawableCache.getImage(Platform.instance.defaultWindowIcon);
95 else
96 _window.windowIcon = drawableCache.getImage(_icon);
97 }
98 }
99 return this;
100 }
101
102 @property UIString windowCaption() {
103 return _caption;
104 }
105
106 /// set window caption
107 @property Dialog windowCaption(dstring caption) {
108 _caption = caption;
109 if (_window)
110 _window.windowCaption = caption;
111 return this;
112 }
113
114 /// get window caption
115 @property Dialog windowCaption(UIString caption) {
116 _caption = caption;
117 if (_window)
118 _window.windowCaption = caption;
119 return this;
120 }
121
122 protected const(Action) [] _buttonActions;
123
124 protected ImageTextButton _defaultButton;
125 protected ImageTextButton _cancelButton;
126 /// create panel with buttons based on list of actions
127 Widget createButtonsPanel(const(Action) [] actions, int defaultActionIndex, int splitBeforeIndex) {
128 _defaultButtonIndex = defaultActionIndex;
129 _buttonActions = actions;
130 LinearLayout res = new HorizontalLayout("buttons");
131 res.layoutWidth(FILL_PARENT);
132 res.layoutWeight = 0;
133 for (int i = 0; i < actions.length; i++) {
134 if (splitBeforeIndex == i)
135 res.addChild(new HSpacer());
136 const Action a = actions[i];
137 string id = "btn" ~ to!string(a.id);
138 ImageTextButton btn = new ImageTextButton(id, a.iconId, a.label);
139 if (defaultActionIndex == i) {
140 btn.setState(State.Default);
141 _defaultButton = btn;
142 }
143 if (a.id == StandardAction.Cancel || a.id == StandardAction.No)
144 _cancelButton = btn;
145 btn.action = a.clone();
146 res.addChild(btn);
147 }
148 return res;
149 }
150
151 /// map key to action
152 override Action findKeyAction(uint keyCode, uint flags) {
153 foreach(a; _buttonActions) {
154 if (a.checkAccelerator(keyCode, flags))
155 return a.clone;
156 }
157 return super.findKeyAction(keyCode, flags);
158 }
159
160 /// Custom handling of actions
161 override bool handleAction(const Action action) {
162 foreach(const Action a; _buttonActions)
163 if (a.id == action.id) {
164 close(action);
165 return true;
166 }
167 return false;
168 }
169
170 /// override to implement creation of dialog controls
171 void initialize() {
172 }
173
174 /** Notify about dialog result, and then close dialog.
175
176 If onDialogResult listener is assigned, pass action to it.
177
178 If no onDialogResult listener, pass to owner window.
179
180 If action is null, no result dispatching will occur.
181 */
182 void close(const Action action) {
183 if (action) {
184 if (dialogResult.assigned)
185 dialogResult(this, action);
186 else if (_parentWindow && !_popup)
187 _parentWindow.dispatchAction(action);
188 }
189 if (_popup)
190 _parentWindow.removePopup(_popup);
191 else
192 window.close();
193 }
194
195 /// shows dialog
196 void show() {
197 initialize();
198 uint wflags = 0;
199 if (_flags & DialogFlag.Modal)
200 wflags |= WindowFlag.Modal;
201 if (_flags & DialogFlag.Resizable) {
202 wflags |= WindowFlag.Resizable;
203 layoutWidth = FILL_PARENT;
204 layoutHeight = FILL_PARENT;
205 }
206 if (_flags & DialogFlag.Popup) {
207 DialogFrame _frame = new DialogFrame(this, _cancelButton !is null);
208 if (_cancelButton) {
209 _frame.closeButtonClick = delegate(Widget w) {
210 close(_cancelButton.action);
211 return true;
212 };
213 }
214 _popup = _parentWindow.showPopup(_frame);
215 _popup.flags(PopupFlags.Modal);
216 } else {
217 if (_initialWidth == 0 && _initialHeight == 0)
218 wflags |= WindowFlag.MeasureSize;
219 _window = Platform.instance.createWindow(_caption, _parentWindow, wflags, _initialWidth, _initialHeight);
220 windowIcon = _icon;
221 _window.backgroundColor = currentTheme.customColor("dialog_background");
222 _window.mainWidget = this;
223 _window.show();
224 }
225 onShow();
226 }
227
228 /// called after window with dialog is shown
229 void onShow() {
230 // override to do something useful
231 if (_defaultButton)
232 _defaultButton.setFocus();
233 }
234
235 /// calls close with default action; returns true if default action is found and invoked
236 protected bool closeWithDefaultAction() {
237 if (_defaultButtonIndex >= 0 && _defaultButtonIndex < _buttonActions.length) {
238 close(_buttonActions[_defaultButtonIndex]);
239 return true;
240 }
241 return false;
242 }
243
244 /// calls close with cancel action (if found); returns true if cancel action is found and invoked
245 protected bool closeWithCancelAction() {
246 for (int i = 0; i < _buttonActions.length; i++) {
247 if (_buttonActions[i].id == StandardAction.Cancel || _buttonActions[i].id == StandardAction.No) {
248 close(_buttonActions[_defaultButtonIndex]);
249 return true;
250 }
251 }
252 return false;
253 }
254
255 /// pass key event here; returns true if search text is updated and you can move selection using it
256 override bool onKeyEvent(KeyEvent event) {
257 if (event.action == KeyAction.KeyUp) {
258 if (event.keyCode == KeyCode.RETURN && event.modifiers == KeyFlag.Control) {
259 // Ctrl+Enter: default action
260 return closeWithDefaultAction();
261 }
262 if (event.keyCode == KeyCode.ESCAPE && event.noModifiers) {
263 // ESC: cancel/no action
264 return closeWithCancelAction();
265 }
266 }
267 return super.onKeyEvent(event);
268 }
269 }
270
271 /// frame with caption for dialog
272 class DialogFrame : WindowFrame {
273 protected Dialog _dialog;
274 this(Dialog dialog, bool enableCloseButton) {
275 super(dialog.id ~ "_frame", enableCloseButton);
276 styleId = STYLE_FLOATING_WINDOW;
277 _dialog = dialog;
278 _caption.text = _dialog.windowCaption.value;
279 bodyWidget = _dialog;
280 }
281 }