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