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