1 // Written in the D programming language. 2 3 /** 4 This module contains definition for main widget for usual application - with menu and status bar. 5 6 When you need MainMenu, StatusBar, Toolbars in your app, reuse this class. 7 8 Synopsis: 9 10 ---- 11 import dlangui.widgets.appframe; 12 13 ---- 14 15 Copyright: Vadim Lopatin, 2015 16 License: Boost License 1.0 17 Authors: Vadim Lopatin, coolreader.org@gmail.com 18 */ 19 module dlangui.widgets.appframe; 20 21 import dlangui.widgets.widget; 22 import dlangui.widgets.menu; 23 import dlangui.widgets.layouts; 24 import dlangui.widgets.statusline; 25 import dlangui.widgets.toolbars; 26 import dlangui.core.files; 27 import dlangui.core.settings; 28 import std.path; 29 30 /// to update status for background operation in AppFrame 31 class BackgroundOperationWatcher { 32 33 protected AppFrame _frame; 34 protected bool _cancelRequested; 35 protected bool _finished; 36 37 this(AppFrame frame) { 38 _frame = frame; 39 } 40 41 /// returns cancel status 42 @property bool cancelRequested() { return _cancelRequested; } 43 /// returns description of background operation to show in status line 44 @property dstring description() { return null; } 45 /// returns icon of background operation to show in status line 46 @property string icon() { return null; } 47 /// returns desired update interval 48 @property long updateInterval() { return 100; } 49 /// update background operation status 50 void update() { 51 // do some work here 52 // when task is done or cancelled, finished should return true 53 // either simple update of status or some real work can be done here 54 if (_frame.statusLine) 55 _frame.statusLine.setBackgroundOperationStatus(icon, description); 56 } 57 /// request cancel - once cancelled, finished should return true 58 void cancel() { 59 _cancelRequested = true; 60 } 61 /// return true when task is done - to remove it from AppFrame 62 @property bool finished() { 63 return _finished; 64 } 65 /// will be called by app frame when BackgroundOperationWatcher is to be removed 66 void removing() { 67 // in this handler, you can post new background operation to AppFrame 68 if (_frame.statusLine) 69 _frame.statusLine.setBackgroundOperationStatus(null, null); 70 } 71 } 72 73 /// base class for application frame with main menu, status line, toolbars 74 class AppFrame : VerticalLayout, MenuItemClickHandler, MenuItemActionHandler { 75 protected MainMenu _mainMenu; 76 protected StatusLine _statusLine; 77 protected ToolBarHost _toolbarHost; 78 protected Widget _body; 79 protected BackgroundOperationWatcher _currentBackgroundOperation; 80 protected ulong _currentBackgroundOperationTimer; 81 82 83 this() { 84 super("APP_FRAME"); 85 layoutWidth = FILL_PARENT; 86 layoutHeight = FILL_PARENT; 87 _appName = "dlangui"; 88 initialize(); 89 } 90 91 protected string _appName; 92 /// override to return some identifier for app, e.g. to use as settings directory name 93 @property string appCodeName() { 94 return _appName; 95 } 96 /// override to return some identifier for app, e.g. to use as settings directory name 97 @property AppFrame appCodeName(string name) { 98 _appName = name; 99 return this; 100 } 101 102 protected string _settingsDir; 103 /// Application settings directory; by default, returns .appcodename directory in user's home directory (e.g. /home/user/.appcodename, C:\Users\User\AppData\Roaming\.appcodename); override to change it 104 @property string settingsDir() { 105 if (!_settingsDir) 106 _settingsDir = appDataPath("." ~ appCodeName); 107 return _settingsDir; 108 } 109 110 protected SettingsFile _shortcutSettings; 111 /// returns shortcuts settings object 112 @property SettingsFile shortcutSettings() { 113 if (!_shortcutSettings) { 114 _shortcutSettings = new SettingsFile(buildNormalizedPath(settingsDir, "shortcuts.json")); 115 } 116 return _shortcutSettings; 117 } 118 119 bool applyShortcutsSettings() { 120 if (shortcutSettings.loaded) { 121 foreach(key, value; _shortcutSettings.map) { 122 int actionId = actionNameToId(key); 123 if (actionId == 0) { 124 Log.e("applyShortcutsSettings: Unknown action name: ", key); 125 } else { 126 Accelerator[] accelerators = []; 127 if (value.isArray) { 128 for (int i = 0; i < value.length; i++) { 129 string v = value[i].str; 130 Accelerator a; 131 if (a.parse(v)) { 132 //Log.d("Read accelerator for action ", key, " : ", a.toString); 133 accelerators ~= a; 134 } else 135 Log.e("applyShortcutsSettings: cannot parse accelerator: ", v); 136 } 137 } else { 138 string v = value.str; 139 Accelerator a; 140 if (a.parse(v)) { 141 //Log.d("Read accelerator for action ", key, " : ", a.toString); 142 accelerators ~= a; 143 } else 144 Log.e("applyShortcutsSettings: cannot parse accelerator: ", v); 145 } 146 setActionAccelerators(actionId, accelerators); 147 } 148 } 149 return true; 150 } 151 return false; 152 } 153 154 /// set shortcut settings from actions and save to file - useful for initial settings file version creation 155 bool saveShortcutsSettings(const(Action)[] actions) { 156 shortcutSettings.clear(); 157 foreach(a; actions) { 158 string name = actionIdToName(a.id); 159 if (name) { 160 const(Accelerator)[] acc = a.accelerators; 161 if (acc.length > 0) { 162 if (acc.length == 1) { 163 _shortcutSettings[name] = acc[0].toString; 164 } else { 165 string[] array; 166 foreach(accel; acc) { 167 array ~= accel.toString; 168 } 169 _shortcutSettings[name] = array; 170 } 171 } 172 } 173 } 174 return shortcutSettings.save(); 175 } 176 177 /// timer handler 178 override bool onTimer(ulong timerId) { 179 if (timerId == _currentBackgroundOperationTimer) { 180 if (_currentBackgroundOperation) { 181 _currentBackgroundOperation.update(); 182 if (_currentBackgroundOperation.finished) { 183 _currentBackgroundOperation.removing(); 184 destroy(_currentBackgroundOperation); 185 _currentBackgroundOperation = null; 186 _currentBackgroundOperationTimer = 0; 187 requestActionsUpdate(); 188 return false; 189 } 190 return true; 191 } else { 192 _currentBackgroundOperationTimer = 0; 193 } 194 } 195 return false; // stop timer 196 } 197 198 /// set background operation to show in status 199 void setBackgroundOperation(BackgroundOperationWatcher op) { 200 if (_currentBackgroundOperation) { 201 _currentBackgroundOperation.removing(); 202 destroy(_currentBackgroundOperation); 203 _currentBackgroundOperation = null; 204 } 205 _currentBackgroundOperation = op; 206 if (op) 207 _currentBackgroundOperationTimer = setTimer(op.updateInterval); 208 requestActionsUpdate(); 209 } 210 211 /// main menu widget 212 @property MainMenu mainMenu() { return _mainMenu; } 213 /// status line widget 214 @property StatusLine statusLine() { return _statusLine; } 215 /// tool bar host 216 @property ToolBarHost toolbars() { return _toolbarHost; } 217 /// body widget 218 @property Widget frameBody() { return _body; } 219 220 /// map key to action 221 override Action findKeyAction(uint keyCode, uint flags) { 222 if (_mainMenu) { 223 Action action = _mainMenu.findKeyAction(keyCode, flags); 224 if (action) 225 return action; 226 } 227 return super.findKeyAction(keyCode, flags); 228 } 229 230 protected void initialize() { 231 _mainMenu = createMainMenu(); 232 _toolbarHost = createToolbars(); 233 _statusLine = createStatusLine(); 234 _body = createBody(); 235 _body.focusGroup = true; 236 if (_mainMenu) { 237 _mainMenu.menuItemClick = &onMenuItemClick; 238 addChild(_mainMenu); 239 } 240 if (_toolbarHost) 241 addChild(_toolbarHost); 242 addChild(_body); 243 if (_statusLine) 244 addChild(_statusLine); 245 updateShortcuts(); 246 } 247 248 /// override it 249 protected void updateShortcuts() { 250 } 251 252 /// override to handle main menu commands 253 override bool onMenuItemClick(MenuItem item) { 254 // default handling: call Action handler 255 return onMenuItemAction(item.action); 256 } 257 258 /// override to handle main menu actions 259 override bool onMenuItemAction(const Action action) { 260 // default handling: dispatch action using window (first offered to focused control, then to main widget) 261 return window.dispatchAction(action); 262 } 263 264 /// create main menu 265 protected MainMenu createMainMenu() { 266 return new MainMenu(new MenuItem()); 267 } 268 269 270 /// create app toolbars 271 protected ToolBarHost createToolbars() { 272 ToolBarHost res = new ToolBarHost(); 273 return res; 274 } 275 276 277 /// create app status line widget 278 protected StatusLine createStatusLine() { 279 return new StatusLine(); 280 } 281 282 /// create app body widget 283 protected Widget createBody() { 284 Widget res = new Widget("APP_FRAME_BODY"); 285 res.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); 286 return res; 287 } 288 289 /// override to handle specific actions 290 override bool handleAction(const Action a) { 291 return false; 292 } 293 294 }