1 module dmledit; 2 3 import dlangui; 4 import dlangui.dialogs.filedlg; 5 import dlangui.dialogs.dialog; 6 import dlangui.dml.dmlhighlight; 7 import dlangui.widgets.metadata; 8 import std.array : replaceFirst; 9 import std.algorithm; 10 import std.stdio; 11 import std.array; 12 import std.file; 13 14 mixin APP_ENTRY_POINT; 15 16 // action codes 17 enum IDEActions : int { 18 //ProjectOpen = 1010000, 19 FileNew = 1010000, 20 FileOpen, 21 FileSave, 22 FileSaveAs, 23 FileSaveAll, 24 FileClose, 25 FileExit, 26 EditPreferences, 27 DebugStart, 28 HelpAbout, 29 } 30 31 // actions 32 const Action ACTION_FILE_NEW = new Action(IDEActions.FileNew, "MENU_FILE_NEW"c, "document-new", KeyCode.KEY_N, KeyFlag.Control); 33 const Action ACTION_FILE_SAVE = (new Action(IDEActions.FileSave, "MENU_FILE_SAVE"c, "document-save", KeyCode.KEY_S, KeyFlag.Control)).disableByDefault(); 34 const Action ACTION_FILE_SAVE_AS = (new Action(IDEActions.FileSaveAs, "MENU_FILE_SAVE_AS"c)).disableByDefault(); 35 const Action ACTION_FILE_OPEN = new Action(IDEActions.FileOpen, "MENU_FILE_OPEN"c, "document-open", KeyCode.KEY_O, KeyFlag.Control); 36 const Action ACTION_FILE_EXIT = new Action(IDEActions.FileExit, "MENU_FILE_EXIT"c, "document-close"c, KeyCode.KEY_X, KeyFlag.Alt); 37 const Action ACTION_EDIT_COPY = (new Action(EditorActions.Copy, "MENU_EDIT_COPY"c, "edit-copy"c, KeyCode.KEY_C, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Control).disableByDefault(); 38 const Action ACTION_EDIT_PASTE = (new Action(EditorActions.Paste, "MENU_EDIT_PASTE"c, "edit-paste"c, KeyCode.KEY_V, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Shift).disableByDefault(); 39 const Action ACTION_EDIT_CUT = (new Action(EditorActions.Cut, "MENU_EDIT_CUT"c, "edit-cut"c, KeyCode.KEY_X, KeyFlag.Control)).addAccelerator(KeyCode.DEL, KeyFlag.Shift).disableByDefault(); 40 const Action ACTION_EDIT_UNDO = (new Action(EditorActions.Undo, "MENU_EDIT_UNDO"c, "edit-undo"c, KeyCode.KEY_Z, KeyFlag.Control)).disableByDefault(); 41 const Action ACTION_EDIT_REDO = (new Action(EditorActions.Redo, "MENU_EDIT_REDO"c, "edit-redo"c, KeyCode.KEY_Y, KeyFlag.Control)).addAccelerator(KeyCode.KEY_Z, KeyFlag.Control|KeyFlag.Shift).disableByDefault(); 42 const Action ACTION_EDIT_INDENT = (new Action(EditorActions.Indent, "MENU_EDIT_INDENT"c, "edit-indent"c, KeyCode.TAB, 0)).addAccelerator(KeyCode.KEY_BRACKETCLOSE, KeyFlag.Control).disableByDefault(); 43 const Action ACTION_EDIT_UNINDENT = (new Action(EditorActions.Unindent, "MENU_EDIT_UNINDENT"c, "edit-unindent", KeyCode.TAB, KeyFlag.Shift)).addAccelerator(KeyCode.KEY_BRACKETOPEN, KeyFlag.Control).disableByDefault(); 44 const Action ACTION_EDIT_TOGGLE_LINE_COMMENT = (new Action(EditorActions.ToggleLineComment, "MENU_EDIT_TOGGLE_LINE_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control)).disableByDefault(); 45 const Action ACTION_EDIT_TOGGLE_BLOCK_COMMENT = (new Action(EditorActions.ToggleBlockComment, "MENU_EDIT_TOGGLE_BLOCK_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control|KeyFlag.Shift)).disableByDefault(); 46 const Action ACTION_EDIT_PREFERENCES = (new Action(IDEActions.EditPreferences, "MENU_EDIT_PREFERENCES"c, null)).disableByDefault(); 47 const Action ACTION_DEBUG_START = new Action(IDEActions.DebugStart, "MENU_DEBUG_UPDATE_PREVIEW"c, "debug-run"c, KeyCode.F5, 0); 48 const Action ACTION_HELP_ABOUT = new Action(IDEActions.HelpAbout, "MENU_HELP_ABOUT"c); 49 50 /// DIDE source file editor 51 class DMLSourceEdit : SourceEdit { 52 this(string ID) { 53 super(ID); 54 MenuItem editPopupItem = new MenuItem(null); 55 editPopupItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_DEBUG_START); 56 popupMenu = editPopupItem; 57 content.syntaxSupport = new DMLSyntaxSupport(""); 58 setTokenHightlightColor(TokenCategory.Comment, 0x008000); // green 59 setTokenHightlightColor(TokenCategory.Keyword, 0x0000FF); // blue 60 setTokenHightlightColor(TokenCategory.String, 0xa31515); // brown 61 setTokenHightlightColor(TokenCategory.Integer, 0xa315C0); // 62 setTokenHightlightColor(TokenCategory.Float, 0xa315C0); // 63 setTokenHightlightColor(TokenCategory.Error, 0xFF0000); // red 64 setTokenHightlightColor(TokenCategory.Op, 0x503000); 65 setTokenHightlightColor(TokenCategory.Identifier_Class, 0x000080); // blue 66 67 } 68 this() { 69 this("DMLEDIT"); 70 } 71 } 72 73 immutable dstring SAMPLE_SOURCE_CODE = 74 q{VerticalLayout { 75 id: vlayout 76 margins: Rect { left: 5; right: 3; top: 2; bottom: 4 } 77 padding: Rect { 5, 4, 3, 2 } // same as Rect { left: 5; top: 4; right: 3; bottom: 2 } 78 TextWidget { 79 /* this widget can be accessed via id myLabel1 80 e.g. w.childById!TextWidget("myLabel1") 81 */ 82 id: myLabel1 83 text: "Some text"; padding: 5 84 enabled: false 85 } 86 TextWidget { 87 id: myLabel2 88 text: "More text"; margins: 5 89 enabled: true 90 } 91 CheckBox{ id: cb1; text: "Some checkbox" } 92 HorizontalLayout { 93 RadioButton { id: rb1; text: "Radio Button 1" } 94 RadioButton { id: rb1; text: "Radio Button 2" } 95 } 96 } 97 }; 98 99 // used to generate property lists once, then simply swap 100 StringListAdapter[string] propListsAdapters; 101 102 class EditFrame : AppFrame { 103 104 MenuItem mainMenuItems; 105 106 override protected void initialize() { 107 _appName = "DMLEdit"; 108 super.initialize(); 109 updatePreview(); 110 } 111 112 /// create main menu 113 override protected MainMenu createMainMenu() { 114 mainMenuItems = new MenuItem(); 115 MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE")); 116 fileItem.add(ACTION_FILE_NEW, ACTION_FILE_OPEN, 117 ACTION_FILE_EXIT); 118 mainMenuItems.add(fileItem); 119 MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT")); 120 editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, 121 ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, 122 ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, ACTION_DEBUG_START); 123 124 editItem.add(ACTION_EDIT_PREFERENCES); 125 mainMenuItems.add(editItem); 126 MainMenu mainMenu = new MainMenu(mainMenuItems); 127 return mainMenu; 128 } 129 130 131 /// create app toolbars 132 override protected ToolBarHost createToolbars() { 133 ToolBarHost res = new ToolBarHost(); 134 ToolBar tb; 135 tb = res.getOrAddToolbar("Standard"); 136 tb.addButtons(ACTION_FILE_NEW, ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR, ACTION_DEBUG_START); 137 138 tb = res.getOrAddToolbar("Edit"); 139 tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR, 140 ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT); 141 return res; 142 } 143 144 string _filename; 145 void openSourceFile(string filename) { 146 import std.file; 147 // TODO 148 if (exists(filename)) { 149 _filename = filename; 150 window.windowCaption = toUTF32(filename); 151 _editor.load(filename); 152 updatePreview(); 153 } 154 } 155 156 void saveSourceFile(string filename) { 157 if (filename.length == 0) 158 filename = _filename; 159 import std.file; 160 _filename = filename; 161 window.windowCaption = toUTF32(filename); 162 _editor.save(filename); 163 } 164 165 bool onCanClose() { 166 // todo 167 return true; 168 } 169 170 FileDialog createFileDialog(UIString caption, bool fileMustExist = true) { 171 uint flags = DialogFlag.Modal | DialogFlag.Resizable; 172 if (fileMustExist) 173 flags |= FileDialogFlag.FileMustExist; 174 FileDialog dlg = new FileDialog(caption, window, null, flags); 175 dlg.filetypeIcons[".d"] = "text-dml"; 176 return dlg; 177 } 178 179 void saveAs() { 180 } 181 182 /// override to handle specific actions 183 override bool handleAction(const Action a) { 184 if (a) { 185 switch (a.id) { 186 case IDEActions.FileExit: 187 if (onCanClose()) 188 window.close(); 189 return true; 190 case IDEActions.HelpAbout: 191 window.showMessageBox(UIString.fromRaw("About DlangUI ML Editor"d), 192 UIString.fromRaw("DLangIDE\n(C) Vadim Lopatin, 2015\nhttp://github.com/buggins/dlangui\nSimple editor for DML code"d)); 193 return true; 194 case IDEActions.FileNew: 195 UIString caption; 196 caption = "Create new DML file"d; 197 FileDialog dlg = createFileDialog(caption, false); 198 dlg.addFilter(FileFilterEntry(UIString.fromRaw("DML files"d), "*.dml")); 199 dlg.addFilter(FileFilterEntry(UIString.fromRaw("All files"d), "*.*")); 200 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 201 if (result.id == ACTION_OPEN.id) { 202 string filename = result.stringParam; 203 _editor.text=""d; 204 saveSourceFile(filename); 205 } 206 }; 207 dlg.show(); 208 return true; 209 case IDEActions.FileSave: 210 if (_filename.length) { 211 saveSourceFile(_filename); 212 return true; 213 } 214 UIString caption; 215 caption = "Save DML File as"d; 216 FileDialog dlg = createFileDialog(caption, false); 217 dlg.addFilter(FileFilterEntry(UIString.fromRaw("DML files"d), "*.dml")); 218 dlg.addFilter(FileFilterEntry(UIString.fromRaw("All files"d), "*.*")); 219 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 220 if (result.id == ACTION_OPEN.id) { 221 string filename = result.stringParam; 222 saveSourceFile(filename); 223 } 224 }; 225 dlg.show(); 226 return true; 227 case IDEActions.FileOpen: 228 UIString caption; 229 caption = "Open DML File"d; 230 FileDialog dlg = createFileDialog(caption); 231 dlg.addFilter(FileFilterEntry(UIString.fromRaw("DML files"d), "*.dml")); 232 dlg.addFilter(FileFilterEntry(UIString.fromRaw("All files"d), "*.*")); 233 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 234 if (result.id == ACTION_OPEN.id) { 235 string filename = result.stringParam; 236 openSourceFile(filename); 237 } 238 }; 239 dlg.show(); 240 return true; 241 case IDEActions.DebugStart: 242 updatePreview(); 243 return true; 244 case IDEActions.EditPreferences: 245 //showPreferences(); 246 return true; 247 default: 248 return super.handleAction(a); 249 } 250 } 251 return false; 252 } 253 254 /// override to handle specific actions state (e.g. change enabled state for supported actions) 255 override bool handleActionStateRequest(const Action a) { 256 switch (a.id) { 257 case IDEActions.HelpAbout: 258 case IDEActions.FileNew: 259 case IDEActions.FileOpen: 260 case IDEActions.DebugStart: 261 case IDEActions.EditPreferences: 262 case IDEActions.FileSaveAs: 263 a.state = ACTION_STATE_ENABLED; 264 return true; 265 case IDEActions.FileSave: 266 if (_editor.content.modified) 267 a.state = ACTION_STATE_ENABLED; 268 else 269 a.state = ACTION_STATE_DISABLE; 270 return true; 271 default: 272 return super.handleActionStateRequest(a); 273 } 274 } 275 276 void updatePreview() { 277 dstring dsource = _editor.text; 278 string source = toUTF8(dsource); 279 try { 280 Widget w = parseML(source); 281 if (statusLine) 282 statusLine.setStatusText("No errors"d); 283 if (_fillHorizontal) 284 w.layoutWidth = FILL_PARENT; 285 if (_fillVertical) 286 w.layoutHeight = FILL_PARENT; 287 if (_highlightBackground) 288 w.backgroundColor = 0xC0C0C0C0; 289 _preview.contentWidget = w; 290 } catch (ParserException e) { 291 if (statusLine) 292 statusLine.setStatusText(toUTF32("ERROR: " ~ e.msg)); 293 _editor.setCaretPos(e.line, e.pos); 294 string msg = "\n" ~ e.msg ~ "\n"; 295 msg = replaceFirst(msg, " near `", "\nnear `"); 296 TextWidget w = new MultilineTextWidget(null, toUTF32(msg)); 297 w.padding = 10; 298 w.margins = 10; 299 w.maxLines = 10; 300 w.backgroundColor = 0xC0FF8080; 301 _preview.contentWidget = w; 302 } 303 } 304 305 protected bool _fillHorizontal; 306 protected bool _fillVertical; 307 protected bool _highlightBackground; 308 protected DMLSourceEdit _editor; 309 protected ScrollWidget _preview; 310 /// create app body widget 311 override protected Widget createBody() 312 { 313 314 DockHost dockHost = new DockHost(); 315 316 dockHost.layoutWidth = FILL_PARENT; 317 dockHost.layoutHeight = FILL_PARENT; 318 319 WidgetsList widgetsList = new WidgetsList(); 320 StringListWidget propList = new StringListWidget(); 321 322 auto sla = new StringListAdapter(); 323 324 auto registeredWidgetList = getRegisteredWidgetsList(); 325 registeredWidgetList.sort!("a < b"); 326 327 foreach(const ref widget; registeredWidgetList) 328 { 329 auto propertyListAdapter = new StringListAdapter(); 330 if ( auto meta = findWidgetMetadata(widget) ) 331 { 332 auto mp = meta.properties; 333 mp.sort!("a.name < b.name"); 334 foreach(const ref prop; mp) 335 { 336 propertyListAdapter.add(UIString.fromRaw(prop.name ~ " [" ~ to!string(prop.type) ~ "]" )); 337 propListsAdapters[widget] = propertyListAdapter; 338 } 339 } 340 sla.add(UIString.fromRaw(widget)); 341 } 342 343 widgetsList.adapter = sla; 344 345 auto leftPanel = new VerticalLayout(); 346 leftPanel.layoutHeight = FILL_PARENT; 347 348 widgetsList.minHeight=800; 349 propList.minHeight=600; 350 351 leftPanel.addChild(new TextWidget().text("Widgets").backgroundColor(0xdddddd).minHeight(50) ); 352 leftPanel.addChild(widgetsList); 353 leftPanel.addChild(new TextWidget().text("Widget properties").backgroundColor(0xdddddd).minHeight(50)); 354 leftPanel.addChild(propList); 355 356 auto leftDockWin = new DockWindow("left dock"); 357 358 leftDockWin.bodyWidget = leftPanel; 359 leftDockWin.dockAlignment = DockAlignment.Left; 360 leftDockWin.layoutWidth = makePercentSize(25); 361 leftDockWin.layoutHeight = FILL_PARENT; 362 dockHost.addDockedWindow(leftDockWin); 363 364 _editor = new DMLSourceEdit(); 365 _editor.text = SAMPLE_SOURCE_CODE; 366 367 auto editorDockWin = new DockWindow("editor"); 368 editorDockWin.layoutWidth = makePercentSize(50); 369 editorDockWin.bodyWidget = _editor; 370 editorDockWin.dockAlignment = DockAlignment.Left; 371 editorDockWin.layoutHeight = FILL_PARENT; 372 dockHost.addDockedWindow(editorDockWin); 373 374 VerticalLayout previewLayout = new VerticalLayout(); 375 previewLayout.layoutHeight = FILL_PARENT; 376 377 auto previewControls = new HorizontalLayout(); 378 auto cbFillHorizontal = new CheckBox(null, "Fill Horizontal"d); 379 auto cbFillVertical = new CheckBox(null, "Fill Vertical"d); 380 auto cbHighlightBackground = new CheckBox(null, "Background"d); 381 cbFillHorizontal.checkChange = delegate(Widget source, bool checked) { 382 _fillHorizontal = checked; 383 updatePreview(); 384 return true; 385 }; 386 cbFillVertical.checkChange = delegate(Widget source, bool checked) { 387 _fillVertical = checked; 388 updatePreview(); 389 return true; 390 }; 391 cbHighlightBackground.checkChange = delegate(Widget source, bool checked) { 392 _highlightBackground = checked; 393 updatePreview(); 394 return true; 395 }; 396 widgetsList.itemClick = delegate (Widget source, int itemIndex){ 397 propList.adapter = propListsAdapters[to!string(widgetsList.selectedItem)]; 398 return true; 399 }; 400 widgetsList.onItemDoubleClick = delegate (Widget source, int itemIndex) { 401 auto caret = _editor.caretPos; 402 auto widgetClassName = widgetsList.selectedItem; 403 EditOperation op = new EditOperation(EditAction.Replace, caret, widgetClassName); 404 _editor.content.performOperation(op, this); 405 }; 406 407 previewControls.addChild(cbFillHorizontal); 408 previewControls.addChild(cbFillVertical); 409 previewControls.addChild(cbHighlightBackground); 410 411 _preview = new ScrollWidget(); 412 _preview.layoutWidth = FILL_PARENT; 413 _preview.layoutHeight = FILL_PARENT; 414 _preview.backgroundImageId = "tx_fabric.tiled"; 415 previewLayout.addChild(previewControls); 416 previewLayout.addChild(_preview); 417 418 auto previewDockWin = new DockWindow("preview"); 419 previewDockWin.layoutWidth = makePercentSize(25); 420 previewDockWin.bodyWidget = previewLayout; 421 previewDockWin.dockAlignment = DockAlignment.Right; 422 423 previewDockWin.layoutHeight = FILL_PARENT; 424 dockHost.bodyWidget = editorDockWin; 425 dockHost.addDockedWindow(previewDockWin); 426 427 return dockHost; 428 } 429 } 430 431 alias onItemDoubleClickHandler = void delegate (Widget source, int itemIndex); 432 433 class WidgetsList : StringListWidget 434 { 435 onItemDoubleClickHandler onItemDoubleClick; 436 437 override bool onMouseEvent(MouseEvent event) { 438 bool result = super.onMouseEvent(event); 439 if (event.doubleClick) { 440 if (onItemDoubleClick !is null) 441 onItemDoubleClick(this, selectedItemIndex); 442 } 443 return result; 444 } 445 } 446 447 /// entry point for dlangui based application 448 extern (C) int UIAppMain(string[] args) { 449 450 // embed non-standard resources listed in views/resources.list into executable 451 embeddedResourceList.addResources(embedResourcesFromList!("resources.list")()); 452 453 /// set font gamma (1.0 is neutral, < 1.0 makes glyphs lighter, >1.0 makes glyphs bolder) 454 FontManager.fontGamma = 0.8; 455 FontManager.hintingMode = HintingMode.Normal; 456 457 // create window 458 Window window = Platform.instance.createWindow("DlangUI ML editor"d, null, WindowFlag.Resizable, 700, 470); 459 460 // create some widget to show in window 461 window.windowIcon = drawableCache.getImage("dlangui-logo1"); 462 463 // create some widget to show in window 464 window.mainWidget = new EditFrame(); 465 466 // show window 467 window.show(); 468 469 // run message loop 470 return Platform.instance.enterMessageLoop(); 471 }