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 }