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 }