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 }