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 }