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 }