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 }