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