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 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 }