1 module dlangui.dialogs.settingsdialog;
2
3 import dlangui.core.events;
4 import dlangui.core.i18n;
5 import dlangui.core.stdaction;
6 import dlangui.core.files;
7 public import dlangui.core.settings;
8 import dlangui.widgets.controls;
9 import dlangui.widgets.lists;
10 import dlangui.widgets.layouts;
11 import dlangui.widgets.tree;
12 import dlangui.widgets.editors;
13 import dlangui.widgets.menu;
14 import dlangui.widgets.combobox;
15 import dlangui.widgets.styles;
16 import dlangui.platforms.common.platform;
17 import dlangui.dialogs.dialog;
18
19 private import std.algorithm;
20 private import std.file;
21 private import std.path;
22 private import std.utf : toUTF32;
23 private import std.conv : to;
24 private import std.array : split;
25
26 /// item on settings page
27 class SettingsItem {
28 protected string _id;
29 protected UIString _label;
30 protected SettingsPage _page;
31 this(string id, UIString label) {
32 _id = id;
33 _label = label;
34 }
35 /// setting path, e.g. "editor/tabSize"
36 @property string id() { return _id; }
37 @property ref UIString label() { return _label; }
38 /// create setting widget
39 Widget[] createWidgets(Setting settings) {
40 TextWidget res = new TextWidget(_id, _label);
41 return [res];
42 }
43 }
44
45 /// checkbox setting
46 class CheckboxItem : SettingsItem {
47 private bool _inverse;
48 this(string id, UIString label, bool inverse = false) {
49 super(id, label);
50 _inverse = inverse;
51 }
52 /// create setting widget
53 override Widget[] createWidgets(Setting settings) {
54 CheckBox res = new CheckBox(_id, _label);
55 Setting setting = settings.settingByPath(_id, SettingType.FALSE);
56 res.checked = setting.boolean ^ _inverse;
57 res.checkChange = delegate(Widget source, bool checked) {
58 setting.boolean = checked ^ _inverse;
59 return true;
60 };
61 return [res];
62 }
63 }
64
65 /// ComboBox based setting with string keys
66 class StringComboBoxItem : SettingsItem {
67 protected StringListValue[] _items;
68 this(string id, UIString label, StringListValue[] items) {
69 super(id, label);
70 _items = items;
71 }
72 /// create setting widget
73 override Widget[] createWidgets(Setting settings) {
74 TextWidget lbl = new TextWidget(_id ~ "-label", _label);
75 ComboBox cb = new ComboBox(_id, _items);
76 cb.minWidth = 200;
77 Setting setting = settings.settingByPath(_id, SettingType.STRING);
78 string itemId = setting.str;
79 int index = -1;
80 for (int i = 0; i < _items.length; i++) {
81 if (_items[i].stringId.equal(itemId)) {
82 index = i;
83 break;
84 }
85 }
86 if (index >= 0)
87 cb.selectedItemIndex = index;
88 cb.itemClick = delegate(Widget source, int itemIndex) {
89 if (itemIndex >= 0 && itemIndex < _items.length)
90 setting.str = _items[itemIndex].stringId;
91 return true;
92 };
93 return [lbl, cb];
94 }
95 }
96
97 /// ComboBox based setting with int keys
98 class IntComboBoxItem : SettingsItem {
99 protected StringListValue[] _items;
100 this(string id, UIString label, StringListValue[] items) {
101 super(id, label);
102 _items = items;
103 }
104 /// create setting widget
105 override Widget[] createWidgets(Setting settings) {
106 TextWidget lbl = new TextWidget(_id ~ "-label", _label);
107 ComboBox cb = new ComboBox(_id, _items);
108 cb.minWidth = 100;
109 Setting setting = settings.settingByPath(_id, SettingType.INTEGER);
110 long itemId = setting.integer;
111 int index = -1;
112 for (int i = 0; i < _items.length; i++) {
113 if (_items[i].intId == itemId) {
114 index = i;
115 break;
116 }
117 }
118 if (index >= 0)
119 cb.selectedItemIndex = index;
120 cb.itemClick = delegate(Widget source, int itemIndex) {
121 if (itemIndex >= 0 && itemIndex < _items.length)
122 setting.integer = _items[itemIndex].intId;
123 return true;
124 };
125 return [lbl, cb];
126 }
127 }
128
129 /// ComboBox based setting with floating point keys (actualy, fixed point digits after period is specidied by divider constructor parameter)
130 class FloatComboBoxItem : SettingsItem {
131 protected StringListValue[] _items;
132 protected long _divider;
133 this(string id, UIString label, StringListValue[] items, long divider = 1000) {
134 super(id, label);
135 _items = items;
136 _divider = divider;
137 }
138 /// create setting widget
139 override Widget[] createWidgets(Setting settings) {
140 TextWidget lbl = new TextWidget(_id ~ "-label", _label);
141 ComboBox cb = new ComboBox(_id, _items);
142 cb.minWidth = 100;
143 Setting setting = settings.settingByPath(_id, SettingType.FLOAT);
144 long itemId = cast(long)(setting.floating * _divider);
145 int index = -1;
146 for (int i = 0; i < _items.length; i++) {
147 if (_items[i].intId == itemId) {
148 index = i;
149 break;
150 }
151 }
152 if (index >= 0)
153 cb.selectedItemIndex = index;
154 cb.itemClick = delegate(Widget source, int itemIndex) {
155 if (itemIndex >= 0 && itemIndex < _items.length)
156 setting.floating = _items[itemIndex].intId / cast(double)_divider;
157 return true;
158 };
159 return [lbl, cb];
160 }
161 }
162
163 class NumberEditItem : SettingsItem {
164 protected int _minValue;
165 protected int _maxValue;
166 protected int _defaultValue;
167 this(string id, UIString label, int minValue = int.max, int maxValue = int.max, int defaultValue = 0) {
168 super(id, label);
169 _minValue = minValue;
170 _maxValue = maxValue;
171 _defaultValue = defaultValue;
172 }
173 /// create setting widget
174 override Widget[] createWidgets(Setting settings) {
175 TextWidget lbl = new TextWidget(_id ~ "-label", _label);
176 EditLine ed = new EditLine(_id ~ "-edit", _label);
177 ed.minWidth = 100;
178 Setting setting = settings.settingByPath(_id, SettingType.INTEGER);
179 int n = cast(int)setting.integerDef(_defaultValue);
180 if (_minValue != int.max && n < _minValue)
181 n = _minValue;
182 if (_maxValue != int.max && n > _maxValue)
183 n = _maxValue;
184 setting.integer = cast(long)n;
185 ed.text = toUTF32(to!string(n));
186 ed.contentChange = delegate(EditableContent content) {
187 long v = parseLong(toUTF8(content.text), long.max);
188 if (v != long.max) {
189 if ((_minValue == int.max || v >= _minValue) && (_maxValue == int.max || v <= _maxValue)) {
190 setting.integer = v;
191 ed.textColor = 0x000000;
192 } else {
193 ed.textColor = 0xFF0000;
194 }
195 }
196 };
197 return [lbl, ed];
198 }
199 }
200
201 class StringEditItem : SettingsItem {
202 string _defaultValue;
203 this(string id, UIString label, string defaultValue) {
204 super(id, label);
205 _defaultValue = defaultValue;
206 }
207 /// create setting widget
208 override Widget[] createWidgets(Setting settings) {
209 TextWidget lbl = new TextWidget(_id ~ "-label", _label);
210 EditLine ed = new EditLine(_id ~ "-edit");
211 ed.minWidth = 200;
212 Setting setting = settings.settingByPath(_id, SettingType.STRING);
213 string value = setting.strDef(_defaultValue);
214 setting.str = value;
215 ed.text = toUTF32(value);
216 ed.contentChange = delegate(EditableContent content) {
217 string value = toUTF8(content.text);
218 setting.str = value;
219 };
220 return [lbl, ed];
221 }
222 }
223
224 class FileNameEditItem : SettingsItem {
225 string _defaultValue;
226 this(string id, UIString label, string defaultValue) {
227 super(id, label);
228 _defaultValue = defaultValue;
229 }
230 /// create setting widget
231 override Widget[] createWidgets(Setting settings) {
232 import dlangui.dialogs.filedlg;
233 TextWidget lbl = new TextWidget(_id ~ "-label", _label);
234 FileNameEditLine ed = new FileNameEditLine(_id ~ "-filename-edit");
235 ed.minWidth = 200;
236 Setting setting = settings.settingByPath(_id, SettingType.STRING);
237 string value = setting.strDef(_defaultValue);
238 setting.str = value;
239 ed.text = toUTF32(value);
240 ed.contentChange = delegate(EditableContent content) {
241 string value = toUTF8(content.text);
242 setting.str = value;
243 };
244 return [lbl, ed];
245 }
246 }
247
248 class ExecutableFileNameEditItem : SettingsItem {
249 string _defaultValue;
250 this(string id, UIString label, string defaultValue) {
251 super(id, label);
252 _defaultValue = defaultValue;
253 }
254 /// create setting widget
255 override Widget[] createWidgets(Setting settings) {
256 import dlangui.dialogs.filedlg;
257 TextWidget lbl = new TextWidget(_id ~ "-label", _label);
258 FileNameEditLine ed = new FileNameEditLine(_id ~ "-filename-edit");
259 ed.addFilter(FileFilterEntry(UIString.fromRaw("Executable files"d), "*.exe", true));
260 ed.minWidth = 200;
261 Setting setting = settings.settingByPath(_id, SettingType.STRING);
262 string value = setting.strDef(_defaultValue);
263 setting.str = value;
264 ed.text = toUTF32(value);
265 ed.contentChange = delegate(EditableContent content) {
266 string value = toUTF8(content.text);
267 setting.str = value;
268 };
269 return [lbl, ed];
270 }
271 }
272
273 class PathNameEditItem : SettingsItem {
274 string _defaultValue;
275 this(string id, UIString label, string defaultValue) {
276 super(id, label);
277 _defaultValue = defaultValue;
278 }
279 /// create setting widget
280 override Widget[] createWidgets(Setting settings) {
281 import dlangui.dialogs.filedlg;
282 TextWidget lbl = new TextWidget(_id ~ "-label", _label);
283 DirEditLine ed = new DirEditLine(_id ~ "-path-edit");
284 ed.addFilter(FileFilterEntry(UIString.fromRaw("All files"d), "*.*"));
285 ed.minWidth = 200;
286 Setting setting = settings.settingByPath(_id, SettingType.STRING);
287 string value = setting.strDef(_defaultValue);
288 setting.str = value;
289 ed.text = toUTF32(value);
290 ed.contentChange = delegate(EditableContent content) {
291 string value = toUTF8(content.text);
292 setting.str = value;
293 };
294 return [lbl, ed];
295 }
296 }
297
298 /// settings page - item of settings tree, can edit several settings
299 class SettingsPage {
300 protected SettingsPage _parent;
301 protected ObjectList!SettingsPage _children;
302 protected ObjectList!SettingsItem _items;
303 protected string _id;
304 protected UIString _label;
305
306 this(string id, UIString label) {
307 _id = id;
308 _label = label;
309 }
310
311 @property string id() { return _id; }
312 @property ref UIString label() { return _label; }
313
314 @property int childCount() {
315 return _children.count;
316 }
317
318 /// returns child page by index
319 SettingsPage child(int index) {
320 return _children[index];
321 }
322
323 SettingsPage addChild(SettingsPage item) {
324 _children.add(item);
325 item._parent = this;
326 return item;
327 }
328
329 SettingsPage addChild(string id, UIString label) {
330 return addChild(new SettingsPage(id, label));
331 }
332
333 @property int itemCount() {
334 return _items.count;
335 }
336
337 /// returns page item by index
338 SettingsItem item(int index) {
339 return _items[index];
340 }
341
342 SettingsItem addItem(SettingsItem item) {
343 _items.add(item);
344 item._page = this;
345 return item;
346 }
347
348 /// add checkbox (boolean value) for setting
349 CheckboxItem addCheckbox(string id, UIString label, bool inverse = false) {
350 CheckboxItem res = new CheckboxItem(id, label, inverse);
351 addItem(res);
352 return res;
353 }
354
355 /// add EditLine to edit number
356 NumberEditItem addNumberEdit(string id, UIString label, int minValue = int.max, int maxValue = int.max, int defaultValue = 0) {
357 NumberEditItem res = new NumberEditItem(id, label, minValue, maxValue, defaultValue);
358 addItem(res);
359 return res;
360 }
361
362 /// add EditLine to edit string
363 StringEditItem addStringEdit(string id, UIString label, string defaultValue = "") {
364 StringEditItem res = new StringEditItem(id, label, defaultValue);
365 addItem(res);
366 return res;
367 }
368
369 /// add EditLine to edit filename
370 FileNameEditItem addFileNameEdit(string id, UIString label, string defaultValue = "") {
371 FileNameEditItem res = new FileNameEditItem(id, label, defaultValue);
372 addItem(res);
373 return res;
374 }
375
376 /// add EditLine to edit filename
377 PathNameEditItem addDirNameEdit(string id, UIString label, string defaultValue = "") {
378 PathNameEditItem res = new PathNameEditItem(id, label, defaultValue);
379 addItem(res);
380 return res;
381 }
382
383 /// add EditLine to edit executable file name
384 ExecutableFileNameEditItem addExecutableFileNameEdit(string id, UIString label, string defaultValue = "") {
385 ExecutableFileNameEditItem res = new ExecutableFileNameEditItem(id, label, defaultValue);
386 addItem(res);
387 return res;
388 }
389
390 StringComboBoxItem addStringComboBox(string id, UIString label, StringListValue[] items) {
391 StringComboBoxItem res = new StringComboBoxItem(id, label, items);
392 addItem(res);
393 return res;
394 }
395
396 IntComboBoxItem addIntComboBox(string id, UIString label, StringListValue[] items) {
397 IntComboBoxItem res = new IntComboBoxItem(id, label, items);
398 addItem(res);
399 return res;
400 }
401
402 FloatComboBoxItem addFloatComboBox(string id, UIString label, StringListValue[] items, long divider = 1000) {
403 FloatComboBoxItem res = new FloatComboBoxItem(id, label, items, divider);
404 addItem(res);
405 return res;
406 }
407
408 /// create page widget (default implementation creates empty page)
409 Widget createWidget(Setting settings) {
410 VerticalLayout res = new VerticalLayout(_id);
411 res.minWidth(200).minHeight(200).layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
412 if (itemCount > 0) {
413 TextWidget caption = new TextWidget("prop-body-caption-" ~ _id, _label);
414 caption.styleId = STYLE_SETTINGS_PAGE_TITLE;
415 caption.layoutWidth(FILL_PARENT);
416 res.addChild(caption);
417 TableLayout tbl = null;
418 for (int i = 0; i < itemCount; i++) {
419 SettingsItem v = item(i);
420 Widget[] w = v.createWidgets(settings);
421 if (w.length == 1) {
422 tbl = null;
423 res.addChild(w[0]);
424 } else if (w.length == 2) {
425 if (!tbl) {
426 tbl = new TableLayout();
427 tbl.colCount = 2;
428 res.addChild(tbl);
429 }
430 tbl.addChild(w[0]);
431 tbl.addChild(w[1]);
432 }
433
434 }
435 }
436 return res;
437 }
438
439 /// returns true if this page is root page
440 @property bool isRoot() {
441 return !_parent;
442 }
443
444 TreeItem createTreeItem() {
445 return new TreeItem(_id, _label);
446 }
447
448 }
449
450 class SettingsDialog : Dialog {
451 protected TreeWidget _tree;
452 protected FrameLayout _frame;
453 protected Setting _settings;
454 protected SettingsPage _layout;
455
456 this(UIString caption, Window parent, Setting settings, SettingsPage layout, bool popup = ((Platform.instance.uiDialogDisplayMode() & DialogDisplayMode.settingsDialogInPopup) == DialogDisplayMode.settingsDialogInPopup)) {
457 super(caption, parent, DialogFlag.Modal | DialogFlag.Resizable | (popup?DialogFlag.Popup:0));
458 _settings = settings;
459 _layout = layout;
460 }
461
462 void onTreeItemSelected(TreeItems source, TreeItem selectedItem, bool activated) {
463 if (!selectedItem)
464 return;
465 _frame.showChild(selectedItem.id);
466 }
467
468 void createControls(SettingsPage page, TreeItem base) {
469 TreeItem item = base;
470 if (!page.isRoot) {
471 item = page.createTreeItem();
472 base.addChild(item);
473 Widget widget = page.createWidget(_settings);
474 _frame.addChild(widget);
475 }
476 if (page.childCount > 0) {
477 for (int i = 0; i < page.childCount; i++) {
478 createControls(page.child(i), item);
479 }
480 }
481 }
482
483 /// override to implement creation of dialog controls
484 override void initialize() {
485 minWidth(600).minHeight(400);
486 _tree = new TreeWidget("prop_tree");
487 _tree.styleId = STYLE_SETTINGS_TREE;
488 _tree.layoutHeight(FILL_PARENT).layoutHeight(FILL_PARENT).minHeight(200).minWidth(100);
489 _tree.selectionChange = &onTreeItemSelected;
490 _tree.fontSize = 16;
491 _frame = new FrameLayout("prop_pages");
492 _frame.styleId = STYLE_SETTINGS_PAGES;
493 _frame.layoutHeight(FILL_PARENT).layoutHeight(FILL_PARENT).minHeight(200).minWidth(100);
494 createControls(_layout, _tree.items);
495 HorizontalLayout content = new HorizontalLayout("settings_dlg_content");
496 content.addChild(_tree);
497 content.addChild(_frame);
498 content.layoutHeight(FILL_PARENT).layoutHeight(FILL_PARENT);
499 addChild(content);
500 addChild(createButtonsPanel([ACTION_APPLY, ACTION_CANCEL], 0, 0));
501 if (_layout.childCount > 0)
502 _tree.selectItem(_layout.child(0).id);
503 }
504
505 }