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.executeInUiThread( (){ _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 }