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 }