1 module dlangide.ui.frame;
2 
3 import dlangui.widgets.menu;
4 import dlangui.widgets.tabs;
5 import dlangui.widgets.layouts;
6 import dlangui.widgets.editors;
7 import dlangui.widgets.srcedit;
8 import dlangui.widgets.controls;
9 import dlangui.widgets.appframe;
10 import dlangui.widgets.docks;
11 import dlangui.widgets.toolbars;
12 import dlangui.widgets.combobox;
13 import dlangui.widgets.popup;
14 import dlangui.dialogs.dialog;
15 import dlangui.dialogs.filedlg;
16 import dlangui.dialogs.settingsdialog;
17 import dlangui.core.stdaction;
18 import dlangui.core.files;
19 
20 import dlangide.ui.commands;
21 import dlangide.ui.wspanel;
22 import dlangide.ui.outputpanel;
23 import dlangide.ui.newfile;
24 import dlangide.ui.newproject;
25 import dlangide.ui.dsourceedit;
26 import dlangide.ui.homescreen;
27 import dlangide.ui.settings;
28 import dlangide.ui.debuggerui;
29 
30 import dlangide.workspace.workspace;
31 import dlangide.workspace.project;
32 import dlangide.builders.builder;
33 import dlangide.tools.editorTool;
34 
35 import ddebug.common.execution;
36 import ddebug.common.nodebug;
37 import ddebug.common.debugger;
38 import ddebug.gdb.gdbinterface;
39 
40 import std.conv;
41 import std.utf;
42 import std.algorithm : equal, endsWith;
43 import std.array : empty;
44 import std.string : split;
45 import std.path;
46 
47 immutable string HELP_PAGE_URL = "https://github.com/buggins/dlangide/wiki";
48 // TODO: get version from GIT commit
49 immutable dstring DLANGIDE_VERSION = "v0.6.19"d;
50 
51 bool isSupportedSourceTextFileFormat(string filename) {
52     return (filename.endsWith(".d") || filename.endsWith(".txt") || filename.endsWith(".cpp") || filename.endsWith(".h") || filename.endsWith(".c")
53         || filename.endsWith(".json") || filename.endsWith(".dd") || filename.endsWith(".ddoc") || filename.endsWith(".xml") || filename.endsWith(".html")
54         || filename.endsWith(".html") || filename.endsWith(".css") || filename.endsWith(".log") || filename.endsWith(".hpp"));
55 }
56 
57 class BackgroundOperationWatcherTest : BackgroundOperationWatcher {
58     this(AppFrame frame) {
59         super(frame);
60     }
61     int _counter;
62     /// returns description of background operation to show in status line
63     override @property dstring description() { return "Test progress: "d ~ to!dstring(_counter); }
64     /// returns icon of background operation to show in status line
65     override @property string icon() { return "folder"; }
66     /// update background operation status
67     override void update() {
68         _counter++;
69         if (_counter >= 100)
70             _finished = true;
71         super.update();
72     }
73 }
74 
75 /// DIDE app frame
76 class IDEFrame : AppFrame, ProgramExecutionStatusListener, BreakpointListChangeListener, BookmarkListChangeListener {
77 
78     private ToolBarComboBox projectConfigurationCombo;
79     
80     MenuItem mainMenuItems;
81     WorkspacePanel _wsPanel;
82     OutputPanel _logPanel;
83     DockHost _dockHost;
84     TabWidget _tabs;
85 
86     ///Cache for parsed D files for autocomplete and symbol finding
87     import dlangide.tools.d.dcdinterface;
88     private DCDInterface _dcdInterface;
89     @property DCDInterface dcdInterface() {
90         if (!_dcdInterface)
91             _dcdInterface = new DCDInterface();
92         return _dcdInterface; 
93     }
94 
95     IDESettings _settings;
96     ProgramExecution _execution;
97 
98     dstring frameWindowCaptionSuffix = "DLangIDE"d;
99 
100     this(Window window) {
101         super();
102         window.mainWidget = this;
103         window.onFilesDropped = &onFilesDropped;
104         window.onCanClose = &onCanClose;
105         window.onClose = &onWindowClose;
106         applySettings(_settings);
107     }
108 
109     ~this() {
110         if (_dcdInterface) {
111             destroy(_dcdInterface);
112             _dcdInterface = null;
113         }
114     }
115 
116     @property DockHost dockHost() { return _dockHost; }
117     @property OutputPanel logPanel() { return _logPanel; }
118 
119     /// stop current program execution
120     void stopExecution() {
121         if (_execution) {
122             _logPanel.logLine("Stopping program execution");
123             Log.d("Stopping execution");
124             _execution.stop();
125             //destroy(_execution);
126             _execution = null;
127         }
128     }
129 
130     /// returns true if program execution or debugging is active
131     @property bool isExecutionActive() {
132         return _execution !is null;
133     }
134 
135     /// called when program execution is stopped
136     protected void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
137         executeInUiThread(delegate() {
138             Log.d("onProgramExecutionStatus process: ", process.executableFile, " status: ", status, " exitCode: ", exitCode);
139             _execution = null;
140             // TODO: update state
141             switch(status) {
142                 case ExecutionStatus.Error:
143                     _logPanel.logLine("Cannot run program " ~ process.executableFile);
144                     break;
145                 case ExecutionStatus.Finished:
146                     _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
147                     break;
148                 case ExecutionStatus.Killed:
149                     _logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
150                     break;
151                 default:
152                     _logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
153                     break;
154             }
155             _statusLine.setBackgroundOperationStatus(null, null);
156         });
157     }
158 
159     protected void buildAndDebugProject(Project project) {
160         if (!currentWorkspace)
161             return;
162         if (!project)
163             project = currentWorkspace.startupProject;
164         if (!project) {
165             window.showMessageBox(UIString("Cannot debug project"d), UIString("Startup project is not specified"d));
166             return;
167         }
168         buildProject(BuildOperation.Build, project, delegate(int result) {
169             if (!result) {
170                 Log.i("Build completed successfully. Starting debug for project.");
171                 debugProject(project);
172             }
173         });
174     }
175 
176     void debugFinished(ProgramExecution process, ExecutionStatus status, int exitCode) {
177         _execution = null;
178         _debugHandler = null;
179         switch(status) {
180             case ExecutionStatus.Error:
181                 _logPanel.logLine("Cannot run program " ~ process.executableFile);
182                 _logPanel.activateLogTab();
183                 break;
184             case ExecutionStatus.Finished:
185                 _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
186                 break;
187             case ExecutionStatus.Killed:
188                 _logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
189                 break;
190             default:
191                 _logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
192                 break;
193         }
194         _statusLine.setBackgroundOperationStatus(null, null);
195     }
196 
197     DebuggerUIHandler _debugHandler;
198     protected void debugProject(Project project) {
199         import std.file;
200         stopExecution();
201         if (!project) {
202             window.showMessageBox(UIString("Cannot debug project"d), UIString("Startup project is not specified"d));
203             return;
204         }
205         string executableFileName = project.executableFileName;
206         if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
207             window.showMessageBox(UIString("Cannot debug project"d), UIString("Cannot find executable file"d));
208             return;
209         }
210         string debuggerExecutable = _settings.debuggerExecutable;
211         if (debuggerExecutable.empty) {
212             window.showMessageBox(UIString("Cannot debug project"d), UIString("No debugger executable specified in settings"d));
213             return;
214         }
215 
216         GDBInterface program = new GDBInterface();
217         DebuggerProxy debuggerProxy = new DebuggerProxy(program, &executeInUiThread);
218         debuggerProxy.setDebuggerExecutable(debuggerExecutable);
219         setExecutableParameters(debuggerProxy, project, executableFileName);
220         _execution = debuggerProxy;
221         _debugHandler = new DebuggerUIHandler(this, debuggerProxy);
222         _debugHandler.onBreakpointListUpdated(currentWorkspace.getBreakpoints());
223         _debugHandler.run();
224     }
225 
226     protected void buildAndRunProject(Project project) {
227         if (!currentWorkspace)
228             return;
229         if (!project)
230             project = currentWorkspace.startupProject;
231         if (!project) {
232             window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d));
233             return;
234         }
235         buildProject(BuildOperation.Build, project, delegate(int result) {
236             if (!result) {
237                 Log.i("Build completed successfully. Running program...");
238                 runProject(project);
239             }
240         });
241     }
242 
243     protected void runProject(Project project) {
244         import std.file;
245         stopExecution();
246         if (!project) {
247             window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d));
248             return;
249         }
250         string executableFileName = project.executableFileName;
251         if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
252             window.showMessageBox(UIString("Cannot run project"d), UIString("Cannot find executable file"d));
253             return;
254         }
255         auto program = new ProgramExecutionNoDebug;
256         setExecutableParameters(program, project, executableFileName);
257         program.setProgramExecutionStatusListener(this);
258         _execution = program;
259         program.run();
260     }
261 
262     bool setExecutableParameters(ProgramExecution program, Project project, string executableFileName) {
263         string[] args;
264         string externalConsoleExecutable = null;
265         string workingDirectory = project.workingDirectory;
266         string tty = _logPanel.terminalDeviceName;
267         if (project.runInExternalConsole) {
268             version(Windows) {
269                 if (program.isMagoDebugger)
270                     tty = "external-console";
271             } else {
272                 externalConsoleExecutable = _settings.terminalExecutable;
273             }
274         }
275         if (!program.isDebugger)
276             _logPanel.logLine("Starting " ~ executableFileName);
277         else
278             _logPanel.logLine("Starting debugger for " ~ executableFileName);
279         _statusLine.setBackgroundOperationStatus("debug-run", program.isDebugger ?  "debugging..."d : "running..."d);
280         string[string] env;
281         program.setExecutableParams(executableFileName, args, workingDirectory, env);
282         if (!tty.empty) {
283             Log.d("Terminal window device name: ", tty);
284             program.setTerminalTty(tty);
285             if (tty != "external-console")
286                 _logPanel.activateTerminalTab(true);
287         } else
288             program.setTerminalExecutable(externalConsoleExecutable);
289         return true;
290     }
291 
292     void runWithRdmd(string filename) {
293         stopExecution();
294 
295         string rdmdExecutable = _settings.rdmdExecutable;
296 
297         auto program = new ProgramExecutionNoDebug;
298         string sourceFileName = baseName(filename);
299         string workingDirectory = dirName(filename);
300         string[] args;
301         {
302             string rdmdAdditionalParams = _settings.rdmdAdditionalParams;
303             if (!rdmdAdditionalParams.empty)
304                 args ~= rdmdAdditionalParams.split();
305 
306             auto buildConfig = currentWorkspace ? currentWorkspace.buildConfiguration : BuildConfiguration.Debug;
307             switch (buildConfig) {
308                 default:
309                 case BuildConfiguration.Debug:
310                     args ~= "-debug";
311                     break;
312                 case BuildConfiguration.Release:
313                     args ~= "-release";
314                     break;
315                 case BuildConfiguration.Unittest:
316                     args ~= "-unittest";
317                     break;
318             }
319             args ~= sourceFileName;
320         }
321         string externalConsoleExecutable = null;
322         version(Windows) {
323         } else {
324             externalConsoleExecutable = _settings.terminalExecutable;
325         }
326         _logPanel.logLine("Starting " ~ sourceFileName ~ " with rdmd");
327         _statusLine.setBackgroundOperationStatus("run-rdmd", "running..."d);
328         program.setExecutableParams(rdmdExecutable, args, workingDirectory, null);
329         program.setTerminalExecutable(externalConsoleExecutable);
330         program.setProgramExecutionStatusListener(this);
331         _execution = program;
332         program.run();
333     }
334 
335     override protected void initialize() {
336         _appName = "dlangide";
337         //_editorTool = new DEditorTool(this);
338         _settings = new IDESettings(buildNormalizedPath(settingsDir, "settings.json"));
339         _settings.load();
340         _settings.updateDefaults();
341         _settings.save();
342         super.initialize();
343     }
344 
345     /// move focus to editor in currently selected tab
346     void focusEditor(string id) {
347         Widget w = _tabs.tabBody(id);
348         if (w) {
349             if (w.visible)
350                 w.setFocus();
351         }
352     }
353 
354     /// source file selected in workspace tree
355     bool onSourceFileSelected(ProjectSourceFile file, bool activate) {
356         Log.d("onSourceFileSelected ", file.filename);
357         return openSourceFile(file.filename, file, activate);
358     }
359 
360     /// returns global IDE settings
361     @property IDESettings settings() { return _settings; }
362 
363     ///
364     bool onCompilerLogIssueClick(dstring filename, int line, int column)
365     {
366         Log.d("onCompilerLogIssueClick ", filename);
367 
368         import std.conv:to;
369         openSourceFile(to!string(filename));
370 
371         currentEditor().setCaretPos(line, column);
372 
373         return true;
374     }
375 
376     void onModifiedStateChange(Widget source, bool modified) {
377         //
378         Log.d("onModifiedStateChange ", source.id, " modified=", modified);
379         int index = _tabs.tabIndex(source.id);
380         if (index >= 0) {
381             dstring name = toUTF32((modified ? "* " : "") ~ baseName(source.id));
382             _tabs.renameTab(index, name);
383         }
384     }
385 
386     bool openSourceFile(string filename, ProjectSourceFile file = null, bool activate = true) {
387         if (!file && !filename)
388             return false;
389         if (!file)
390             file = _wsPanel.findSourceFileItem(filename, false);
391 
392         //if(!file)
393         //    return false;
394 
395         if (file)
396             filename = file.filename;
397 
398         Log.d("openSourceFile ", filename);
399         int index = _tabs.tabIndex(filename);
400         if (index >= 0) {
401             // file is already opened in tab
402             _tabs.selectTab(index, true);
403         } else {
404             // open new file
405             DSourceEdit editor = new DSourceEdit(filename);
406             if (file ? editor.load(file) : editor.load(filename)) {
407                 _tabs.addTab(editor, toUTF32(baseName(filename)), null, true);
408                 index = _tabs.tabIndex(filename);
409                 TabItem tab = _tabs.tab(filename);
410                 tab.objectParam = file;
411                 editor.modifiedStateChange = &onModifiedStateChange;
412                 if (file) {
413                     editor.breakpointListChanged = this; //onBreakpointListChanged
414                     editor.bookmarkListChanged = this; //onBreakpointListChanged
415                     editor.setBreakpointList(currentWorkspace.getSourceFileBreakpoints(file));
416                     editor.setBookmarkList(currentWorkspace.getSourceFileBookmarks(file));
417                 }
418                 applySettings(editor, settings);
419                 _tabs.selectTab(index, true);
420                 if( filename.endsWith(".d") )
421                     editor.editorTool = new DEditorTool(this);
422                 else
423                     editor.editorTool = new DefaultEditorTool(this);
424             } else {
425                 destroy(editor);
426                 if (window)
427                     window.showMessageBox(UIString("File open error"d), UIString("Failed to open file "d ~ toUTF32(file.filename)));
428                 return false;
429             }
430         }
431         if (activate) {
432             focusEditor(filename);
433         }
434         requestLayout();
435         return true;
436     }
437 
438     static immutable HOME_SCREEN_ID = "HOME_SCREEN";
439     void showHomeScreen() {
440         int index = _tabs.tabIndex(HOME_SCREEN_ID);
441         if (index >= 0) {
442             _tabs.selectTab(index, true);
443         } else {
444             HomeScreen home = new HomeScreen(HOME_SCREEN_ID, this);
445             _tabs.addTab(home, "DlangIDE Home"d, null, true);
446             _tabs.selectTab(HOME_SCREEN_ID, true);
447         }
448     }
449 
450     void hideHomeScreen() {
451         _tabs.removeTab(HOME_SCREEN_ID);
452     }
453 
454     void onTabChanged(string newActiveTabId, string previousTabId) {
455         int index = _tabs.tabIndex(newActiveTabId);
456         if (index >= 0) {
457             TabItem tab = _tabs.tab(index);
458             ProjectSourceFile file = cast(ProjectSourceFile)tab.objectParam;
459             if (file) {
460                 //setCurrentProject(file.project);
461                 // tab is source file editor
462                 _wsPanel.selectItem(file);
463                 focusEditor(file.filename);
464             }
465             window.windowCaption(tab.text.value ~ " - "d ~ frameWindowCaptionSuffix);
466         }
467         requestActionsUpdate();
468     }
469 
470     // returns DSourceEdit from currently active tab (if it's editor), null if current tab is not editor or no tabs open
471     DSourceEdit currentEditor() {
472         return cast(DSourceEdit)_tabs.selectedTabBody();
473     }
474 
475     /// close tab w/o confirmation
476     void closeTab(string tabId) {
477         _wsPanel.selectItem(null);
478         _tabs.removeTab(tabId);
479     }
480 
481     /// close all editor tabs
482     void closeAllDocuments() {
483         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
484             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
485             if (ed) {
486                 closeTab(ed.id);
487             }
488         }
489     }
490 
491     /// returns array of all opened source editors
492     DSourceEdit[] allOpenedEditors() {
493         DSourceEdit[] res;
494         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
495             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
496             if (ed) {
497                 res ~= ed;
498             }
499         }
500         return res;
501     }
502 
503     /// close editor tabs for which files are removed from filesystem
504     void closeRemovedDocuments() {
505         import std.file;
506         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
507             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
508             if (ed) {
509                 if (!exists(ed.id) || !isFile(ed.id)) {
510                     closeTab(ed.id);
511                 }
512             }
513         }
514     }
515 
516     /// returns first unsaved document
517     protected DSourceEdit hasUnsavedEdits() {
518         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
519             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
520             if (ed && ed.content.modified) {
521                 return ed;
522             }
523         }
524         return null;
525     }
526 
527     protected void askForUnsavedEdits(void delegate() onConfirm) {
528         DSourceEdit ed = hasUnsavedEdits();
529         if (!ed) {
530             // no unsaved edits
531             onConfirm();
532             return;
533         }
534         string tabId = ed.id;
535         // tab content is modified - ask for confirmation
536         window.showMessageBox(UIString("Close file "d ~ toUTF32(baseName(tabId))), UIString("Content of this file has been changed."d), 
537                               [ACTION_SAVE, ACTION_SAVE_ALL, ACTION_DISCARD_CHANGES, ACTION_DISCARD_ALL, ACTION_CANCEL], 
538                               0, delegate(const Action result) {
539                                   if (result == StandardAction.Save) {
540                                       // save and close
541                                       ed.save();
542                                       askForUnsavedEdits(onConfirm);
543                                   } else if (result == StandardAction.DiscardChanges) {
544                                       // close, don't save
545                                       closeTab(tabId);
546                                       closeAllDocuments();
547                                       onConfirm();
548                                   } else if (result == StandardAction.SaveAll) {
549                                       ed.save();
550                                       for(;;) {
551                                           DSourceEdit editor = hasUnsavedEdits();
552                                           if (!editor)
553                                               break;
554                                           editor.save();
555                                       }
556                                       closeAllDocuments();
557                                       onConfirm();
558                                   } else if (result == StandardAction.DiscardAll) {
559                                       // close, don't save
560                                       closeAllDocuments();
561                                       onConfirm();
562                                   }
563                                   // else ignore
564                                   return true;
565                               });
566     }
567 
568     protected void onTabClose(string tabId) {
569         Log.d("onTabClose ", tabId);
570         int index = _tabs.tabIndex(tabId);
571         if (index >= 0) {
572             DSourceEdit d = cast(DSourceEdit)_tabs.tabBody(tabId);
573             if (d && d.content.modified) {
574                 // tab content is modified - ask for confirmation
575                 window.showMessageBox(UIString("Close tab"d), UIString("Content of "d ~ toUTF32(baseName(tabId)) ~ " file has been changed."d), 
576                                       [ACTION_SAVE, ACTION_DISCARD_CHANGES, ACTION_CANCEL], 
577                                       0, delegate(const Action result) {
578                                           if (result == StandardAction.Save) {
579                                               // save and close
580                                               d.save();
581                                               closeTab(tabId);
582                                           } else if (result == StandardAction.DiscardChanges) {
583                                               // close, don't save
584                                               closeTab(tabId);
585                                           }
586                                           // else ignore
587                                           return true;
588                                       });
589             } else {
590                 closeTab(tabId);
591             }
592         }
593         requestActionsUpdate();
594     }
595 
596     /// create app body widget
597     override protected Widget createBody() {
598         _dockHost = new DockHost();
599 
600         //=============================================================
601         // Create body - Tabs
602 
603         // editor tabs
604         _tabs = new TabWidget("TABS");
605         _tabs.hiddenTabsVisibility = Visibility.Gone;
606         //_tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
607         _tabs.setStyles(STYLE_DOCK_WINDOW, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT, STYLE_DOCK_HOST_BODY);
608         _tabs.tabChanged = &onTabChanged;
609         _tabs.tabClose = &onTabClose;
610 
611         _dockHost.bodyWidget = _tabs;
612 
613         //=============================================================
614         // Create workspace docked panel
615         _wsPanel = new WorkspacePanel("workspace");
616         _wsPanel.sourceFileSelectionListener = &onSourceFileSelected;
617         _wsPanel.workspaceActionListener = &handleAction;
618         _wsPanel.dockAlignment = DockAlignment.Left;
619         _dockHost.addDockedWindow(_wsPanel);
620 
621         _logPanel = new OutputPanel("output");
622         _logPanel.compilerLogIssueClickHandler = &onCompilerLogIssueClick;
623         _logPanel.appendText(null, "DlangIDE is started\nHINT: Try to open some DUB project\n"d);
624         string dubPath = findExecutablePath("dub");
625         string rdmdPath = findExecutablePath("rdmd");
626         string dmdPath = findExecutablePath("dmd");
627         string ldcPath = findExecutablePath("ldc2");
628         string gdcPath = findExecutablePath("gdc");
629         _logPanel.appendText(null, dubPath ? ("dub path: "d ~ toUTF32(dubPath) ~ "\n"d) : ("dub is not found! cannot build projects without DUB\n"d));
630         _logPanel.appendText(null, rdmdPath ? ("rdmd path: "d ~ toUTF32(rdmdPath) ~ "\n"d) : ("rdmd is not found!\n"d));
631         _logPanel.appendText(null, dmdPath ? ("dmd path: "d ~ toUTF32(dmdPath) ~ "\n"d) : ("dmd compiler is not found!\n"d));
632         _logPanel.appendText(null, ldcPath ? ("ldc path: "d ~ toUTF32(ldcPath) ~ "\n"d) : ("ldc compiler is not found!\n"d));
633         _logPanel.appendText(null, gdcPath ? ("gdc path: "d ~ toUTF32(gdcPath) ~ "\n"d) : ("gdc compiler is not found!\n"d));
634 
635         _dockHost.addDockedWindow(_logPanel);
636 
637         return _dockHost;
638     }
639 
640     /// create main menu
641     override protected MainMenu createMainMenu() {
642 
643         mainMenuItems = new MenuItem();
644         MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE"));
645         MenuItem fileNewItem = new MenuItem(new Action(1, "MENU_FILE_NEW"));
646         fileNewItem.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT);
647         fileItem.add(fileNewItem);
648         fileItem.add(ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
649                      ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_WORKSPACE_CLOSE, ACTION_FILE_EXIT);
650 
651         MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
652         editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, 
653                      ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_FIND_TEXT, ACTION_EDITOR_TOGGLE_BOOKMARK);
654         MenuItem editItemAdvanced = new MenuItem(new Action(221, "MENU_EDIT_ADVANCED"));
655         editItemAdvanced.add(ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS);
656         editItem.add(editItemAdvanced);
657 
658         editItem.add(ACTION_EDIT_PREFERENCES);
659 
660         MenuItem navItem = new MenuItem(new Action(21, "MENU_NAVIGATE"));
661         navItem.add(ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS, ACTION_GET_DOC_COMMENTS, ACTION_GET_PAREN_COMPLETION, ACTION_EDITOR_GOTO_PREVIOUS_BOOKMARK, ACTION_EDITOR_GOTO_NEXT_BOOKMARK);
662 
663         MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT"));
664         projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS);
665 
666         MenuItem buildItem = new MenuItem(new Action(22, "MENU_BUILD"));
667         buildItem.add(ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
668                      ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN,
669                      ACTION_RUN_WITH_RDMD);
670 
671         MenuItem debugItem = new MenuItem(new Action(23, "MENU_DEBUG"));
672         debugItem.add(ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, 
673                       ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE,
674                       ACTION_DEBUG_RESTART,
675                       ACTION_DEBUG_STEP_INTO,
676                       ACTION_DEBUG_STEP_OVER,
677                       ACTION_DEBUG_STEP_OUT,
678                       ACTION_DEBUG_TOGGLE_BREAKPOINT, ACTION_DEBUG_ENABLE_BREAKPOINT, ACTION_DEBUG_DISABLE_BREAKPOINT
679                       );
680 
681 
682         MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c));
683         //windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES"));
684         windowItem.add(ACTION_WINDOW_CLOSE_DOCUMENT);
685         windowItem.add(ACTION_WINDOW_CLOSE_ALL_DOCUMENTS);
686         MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c));
687         helpItem.add(ACTION_HELP_VIEW_HELP);
688         helpItem.add(ACTION_HELP_ABOUT);
689         mainMenuItems.add(fileItem);
690         mainMenuItems.add(editItem);
691         mainMenuItems.add(projectItem);
692         mainMenuItems.add(navItem);
693         mainMenuItems.add(buildItem);
694         mainMenuItems.add(debugItem);
695         //mainMenuItems.add(viewItem);
696         mainMenuItems.add(windowItem);
697         mainMenuItems.add(helpItem);
698 
699         MainMenu mainMenu = new MainMenu(mainMenuItems);
700         //mainMenu.backgroundColor = 0xd6dbe9;
701         return mainMenu;
702     }
703 
704     /// override it
705     override protected void updateShortcuts() {
706         if (applyShortcutsSettings()) {
707             Log.d("Shortcut actions loaded");
708         } else {
709             Log.d("Saving default shortcuts");
710             const(Action)[] actions;
711             actions ~= [
712                 ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, 
713                 ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, 
714                 ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, 
715                 ACTION_EDIT_PREFERENCES, 
716                 ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT, ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
717                 ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_EXIT, 
718                 ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, 
719                 ACTION_PROJECT_SETTINGS, ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
720                 ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN, ACTION_RUN_WITH_RDMD,
721                 ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE,
722                 ACTION_DEBUG_RESTART,
723                 ACTION_DEBUG_STEP_INTO,
724                 ACTION_DEBUG_STEP_OVER,
725                 ACTION_DEBUG_STEP_OUT,
726                 ACTION_WINDOW_CLOSE_DOCUMENT, ACTION_WINDOW_CLOSE_ALL_DOCUMENTS, ACTION_HELP_ABOUT];
727             actions ~= STD_EDITOR_ACTIONS;
728             saveShortcutsSettings(actions);
729         }
730     }
731 
732     /// create app toolbars
733     override protected ToolBarHost createToolbars() {
734         ToolBarHost res = new ToolBarHost();
735         ToolBar tb;
736         tb = res.getOrAddToolbar("Standard");
737         tb.addButtons(ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR);
738 
739         tb.addButtons(ACTION_DEBUG_START);
740         
741         projectConfigurationCombo = new ToolBarComboBox("projectConfig", [ProjectConfiguration.DEFAULT_NAME.to!dstring]);//Updateable
742         projectConfigurationCombo.itemClick = delegate(Widget source, int index) {
743             if (currentWorkspace) {
744                 currentWorkspace.setStartupProjectConfiguration(projectConfigurationCombo.selectedItem.to!string); 
745             }
746             return true;
747         };
748         projectConfigurationCombo.action = ACTION_PROJECT_CONFIGURATIONS;
749         tb.addControl(projectConfigurationCombo);
750         
751         ToolBarComboBox cbBuildConfiguration = new ToolBarComboBox("buildConfig", ["Debug"d, "Release"d, "Unittest"d]);
752         cbBuildConfiguration.itemClick = delegate(Widget source, int index) {
753             if (currentWorkspace && index < 3) {
754                 currentWorkspace.buildConfiguration = [BuildConfiguration.Debug, BuildConfiguration.Release, BuildConfiguration.Unittest][index];
755             }
756             return true;
757         };
758         cbBuildConfiguration.action = ACTION_BUILD_CONFIGURATIONS;
759         tb.addControl(cbBuildConfiguration);
760         tb.addButtons(ACTION_PROJECT_BUILD, ACTION_SEPARATOR, ACTION_RUN_WITH_RDMD);
761 
762         tb = res.getOrAddToolbar("Edit");
763         tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR,
764                       ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT);
765         tb = res.getOrAddToolbar("Debug");
766         tb.addButtons(ACTION_DEBUG_STOP, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_PAUSE,
767                       ACTION_DEBUG_RESTART,
768                       ACTION_DEBUG_STEP_INTO,
769                       ACTION_DEBUG_STEP_OVER,
770                       ACTION_DEBUG_STEP_OUT,
771                       );
772         return res;
773     }
774 
775     /// override to handle specific actions state (e.g. change enabled state for supported actions)
776     override bool handleActionStateRequest(const Action a) {
777         switch (a.id) {
778             case IDEActions.EditPreferences:
779                 return true;
780             case IDEActions.FileExit:
781             case IDEActions.FileOpen:
782             case IDEActions.WindowCloseDocument:
783             case IDEActions.WindowCloseAllDocuments:
784             case IDEActions.FileOpenWorkspace:
785                 // disable when background operation in progress
786                 if (!_currentBackgroundOperation)
787                     a.state = ACTION_STATE_ENABLED;
788                 else
789                     a.state = ACTION_STATE_DISABLE;
790                 return true;
791             case IDEActions.HelpAbout:
792             case StandardAction.OpenUrl:
793                 // always enabled
794                 a.state = ACTION_STATE_ENABLED;
795                 return true;
796             case IDEActions.BuildProject:
797             case IDEActions.BuildWorkspace:
798             case IDEActions.RebuildProject:
799             case IDEActions.RebuildWorkspace:
800             case IDEActions.CleanProject:
801             case IDEActions.CleanWorkspace:
802             case IDEActions.UpdateProjectDependencies:
803             case IDEActions.RefreshProject:
804             case IDEActions.SetStartupProject:
805             case IDEActions.ProjectSettings:
806             case IDEActions.RevealProjectInExplorer:
807                 // enable when project exists
808                 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
809                     a.state = ACTION_STATE_ENABLED;
810                 else
811                     a.state = ACTION_STATE_DISABLE;
812                 return true;
813             case IDEActions.RunWithRdmd:
814                 // enable when D source file is in current tab
815                 if (currentEditor && !_currentBackgroundOperation && currentEditor.id.endsWith(".d"))
816                     a.state = ACTION_STATE_ENABLED;
817                 else
818                     a.state = ACTION_STATE_DISABLE;
819                 return true;
820             case IDEActions.DebugStop:
821                 a.state = isExecutionActive ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
822                 return true;
823             case IDEActions.DebugStart:
824             case IDEActions.DebugStartNoDebug:
825                 if (!isExecutionActive && currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
826                     a.state = ACTION_STATE_ENABLED;
827                 else
828                     a.state = ACTION_STATE_DISABLE;
829                 return true;
830             case IDEActions.DebugContinue:
831             case IDEActions.DebugPause:
832             case IDEActions.DebugStepInto:
833             case IDEActions.DebugStepOver:
834             case IDEActions.DebugStepOut:
835             case IDEActions.DebugRestart:
836                 if (_debugHandler)
837                     return _debugHandler.handleActionStateRequest(a);
838                 else
839                     a.state = ACTION_STATE_DISABLE;
840                 return true;
841             default:
842                 return super.handleActionStateRequest(a);
843         }
844     }
845 
846     FileDialog createFileDialog(UIString caption) {
847         FileDialog dlg = new FileDialog(caption, window, null);
848         dlg.filetypeIcons[".d"] = "text-d";
849         dlg.filetypeIcons["dub.json"] = "project-d";
850         dlg.filetypeIcons["package.json"] = "project-d";
851         dlg.filetypeIcons[".dlangidews"] = "project-development";
852         return dlg;
853     }
854 
855     /// override to handle specific actions
856     override bool handleAction(const Action a) {
857         if (a) {
858             switch (a.id) {
859                 case IDEActions.FileExit:
860                     if (onCanClose())
861                         window.close();
862                     return true;
863                 case IDEActions.HelpViewHelp:
864                     Platform.instance.openURL(HELP_PAGE_URL);
865                     return true;
866                 case IDEActions.HelpAbout:
867                     window.showMessageBox(UIString("About DlangIDE "d ~ DLANGIDE_VERSION),
868                                           UIString("DLangIDE\n(C) Vadim Lopatin, 2014-2016\nhttp://github.com/buggins/dlangide\nIDE for D programming language written in D\nUses DlangUI library for GUI"d));
869                     return true;
870                 case StandardAction.OpenUrl:
871                     platform.openURL(a.stringParam);
872                     return true;
873                 case IDEActions.FileOpen:
874                     UIString caption;
875                     caption = "Open Text File"d;
876                     FileDialog dlg = createFileDialog(caption);
877                     dlg.addFilter(FileFilterEntry(UIString("Source files"d), "*.d;*.dd;*.ddoc;*.di;*.dh;*.json;*.xml;*.ini"));
878                     dlg.addFilter(FileFilterEntry(UIString("All files"d), "*.*"));
879                     dlg.dialogResult = delegate(Dialog dlg, const Action result) {
880                         if (result.id == ACTION_OPEN.id) {
881                             string filename = result.stringParam;
882                             openSourceFile(filename);
883                         }
884                     };
885                     dlg.show();
886                     return true;
887                 case IDEActions.BuildProject:
888                 case IDEActions.BuildWorkspace:
889                     buildProject(BuildOperation.Build, cast(Project)a.objectParam);
890                     return true;
891                 case IDEActions.RebuildProject:
892                 case IDEActions.RebuildWorkspace:
893                     buildProject(BuildOperation.Rebuild, cast(Project)a.objectParam);
894                     return true;
895                 case IDEActions.CleanProject:
896                 case IDEActions.CleanWorkspace:
897                     buildProject(BuildOperation.Clean, cast(Project)a.objectParam);
898                     return true;
899                 case IDEActions.RunWithRdmd:
900                     runWithRdmd(currentEditor.id);
901                     return true;
902                 case IDEActions.DebugStartNoDebug:
903                     buildAndRunProject(cast(Project)a.objectParam);
904                     return true;
905                 case IDEActions.DebugStart:
906                     buildAndDebugProject(cast(Project)a.objectParam);
907                     return true;
908                 case IDEActions.DebugPause:
909                 case IDEActions.DebugStepInto:
910                 case IDEActions.DebugStepOver:
911                 case IDEActions.DebugStepOut:
912                 case IDEActions.DebugRestart:
913                     if (_debugHandler)
914                         return _debugHandler.handleAction(a);
915                     return true;
916                 case IDEActions.DebugContinue:
917                     if (_debugHandler)
918                         return _debugHandler.handleAction(a);
919                     else
920                         buildAndRunProject(cast(Project)a.objectParam);
921                     return true;
922                 case IDEActions.DebugStop:
923                     if (_debugHandler)
924                         return _debugHandler.handleAction(a);
925                     else
926                         stopExecution();
927                     return true;
928                 case IDEActions.UpdateProjectDependencies:
929                     buildProject(BuildOperation.Upgrade, cast(Project)a.objectParam);
930                     return true;
931                 case IDEActions.RefreshProject:
932                     refreshWorkspace();
933                     return true;
934                 case IDEActions.RevealProjectInExplorer:
935                     revealProjectInExplorer(cast(Project)a.objectParam);
936                     return true;
937                 case IDEActions.WindowCloseDocument:
938                     onTabClose(_tabs.selectedTabId);
939                     return true;
940                 case IDEActions.WindowCloseAllDocuments:
941                     askForUnsavedEdits(delegate() {
942                         closeAllDocuments();
943                     });
944                     return true;
945                 case IDEActions.FileOpenWorkspace:
946                     if (!a.stringParam.empty) {
947                         openFileOrWorkspace(a.stringParam);
948                         return true;
949                     }
950                     UIString caption;
951                     caption = "Open Workspace or Project"d;
952                     FileDialog dlg = createFileDialog(caption);
953                     dlg.addFilter(FileFilterEntry(UIString("Workspace and project files"d), "*.dlangidews;dub.json;package.json"));
954                     dlg.dialogResult = delegate(Dialog dlg, const Action result) {
955                         if (result.id == ACTION_OPEN.id) {
956                             string filename = result.stringParam;
957                             if (filename.length)
958                                 openFileOrWorkspace(filename);
959                         }
960                     };
961                     dlg.show();
962                     return true;
963                 case IDEActions.GoToDefinition:
964                     Log.d("Trying to go to definition.");
965                     currentEditor.editorTool.goToDefinition(currentEditor(), currentEditor.caretPos);
966                     return true;
967                 case IDEActions.GetDocComments:
968                     Log.d("Trying to get doc comments.");
969                     currentEditor.editorTool.getDocComments(currentEditor, currentEditor.caretPos, delegate(string[] results) {
970                         if (results.length)
971                             currentEditor.showDocCommentsPopup(results);
972                     });
973                     return true;
974                 case IDEActions.GetParenCompletion:
975                     Log.d("Trying to get paren completion.");
976                     //auto results = currentEditor.editorTool.getParenCompletion(currentEditor, currentEditor.caretPos);
977                     return true;
978                 case IDEActions.GetCompletionSuggestions:
979                     Log.d("Getting auto completion suggestions.");
980                     currentEditor.editorTool.getCompletions(currentEditor, currentEditor.caretPos, delegate(dstring[] results, string[] icons) {
981                         if (currentEditor)
982                             currentEditor.showCompletionPopup(results, icons);
983                     });
984                     return true;
985                 case IDEActions.EditPreferences:
986                     showPreferences();
987                     return true;
988                 case IDEActions.ProjectSettings:
989                     showProjectSettings(cast(Project)a.objectParam);
990                     return true;
991                 case IDEActions.SetStartupProject:
992                     setStartupProject(cast(Project)a.objectParam);
993                     return true;
994                 case IDEActions.FindInFiles:
995                     Log.d("Opening Search Field");
996                        import dlangide.ui.searchPanel;
997                     int searchPanelIndex = _logPanel.getTabs.tabIndex("search");
998                     SearchWidget searchPanel = null;
999                     if(searchPanelIndex == -1) {
1000                         searchPanel = new SearchWidget("search", this);
1001                         _logPanel.getTabs.addTab( searchPanel, "Search"d, null, true);
1002                     }
1003                     else {
1004                         searchPanel = cast(SearchWidget) _logPanel.getTabs.tabBody(searchPanelIndex);
1005                     }
1006                     _logPanel.getTabs.selectTab("search");
1007                     if(searchPanel !is null) { 
1008                         searchPanel.focus();
1009                         dstring selectedText = currentEditor.getSelectedText();
1010                         searchPanel.setSearchText(selectedText);
1011                     }
1012                     return true;
1013                 case IDEActions.FileNewWorkspace:
1014                     createNewProject(true);
1015                     return true;
1016                 case IDEActions.FileNewProject:
1017                     createNewProject(false);
1018                     return true;
1019                 case IDEActions.FileNew:
1020                     addProjectItem(a.objectParam);
1021                     return true;
1022                 case IDEActions.ProjectFolderRemoveItem:
1023                     removeProjectItem(a.objectParam);
1024                     return true;
1025                 case IDEActions.ProjectFolderRefresh:
1026                     refreshProjectItem(a.objectParam);
1027                     return true;
1028                 case IDEActions.CloseWorkspace:
1029                     closeWorkspace();
1030                     return true;
1031                 default:
1032                     return super.handleAction(a);
1033             }
1034         }
1035         return false;
1036     }
1037 
1038     @property ProjectSourceFile currentEditorSourceFile() {
1039         TabItem tab = _tabs.selectedTab;
1040         if (tab) {
1041             return cast(ProjectSourceFile)tab.objectParam;
1042         }
1043         return null;
1044     }
1045 
1046     void closeWorkspace() {
1047         if (currentWorkspace)
1048             currentWorkspace.save();
1049         askForUnsavedEdits(delegate() {
1050             setWorkspace(null);
1051             showHomeScreen();
1052         });
1053     }
1054 
1055     void onBreakpointListChanged(ProjectSourceFile sourcefile, Breakpoint[] breakpoints) {
1056         if (!currentWorkspace)
1057             return;
1058         if (sourcefile) {
1059             currentWorkspace.setSourceFileBreakpoints(sourcefile, breakpoints);
1060         }
1061         if (_debugHandler)
1062             _debugHandler.onBreakpointListUpdated(currentWorkspace.getBreakpoints());
1063     }
1064 
1065     void onBookmarkListChanged(ProjectSourceFile sourcefile, EditorBookmark[] bookmarks) {
1066         if (!currentWorkspace)
1067             return;
1068         if (sourcefile)
1069             currentWorkspace.setSourceFileBookmarks(sourcefile, bookmarks);
1070     }
1071 
1072     void refreshProjectItem(const Object obj) {
1073         if (currentWorkspace is null)
1074             return;
1075         Project project;
1076         ProjectFolder folder;
1077         if (cast(Workspace)obj) {
1078             Workspace ws = cast(Workspace)obj;
1079             ws.refresh();
1080             refreshWorkspace();
1081         } else if (cast(Project)obj) {
1082             project = cast(Project)obj;
1083         } else if (cast(ProjectFolder)obj) {
1084             folder = cast(ProjectFolder)obj;
1085             project = folder.project;
1086         } else if (cast(ProjectSourceFile)obj) {
1087             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1088             folder = cast(ProjectFolder)srcfile.parent;
1089             project = srcfile.project;
1090         } else {
1091             ProjectSourceFile srcfile = currentEditorSourceFile;
1092             if (srcfile) {
1093                 folder = cast(ProjectFolder)srcfile.parent;
1094                 project = srcfile.project;
1095             }
1096         }
1097         if (project) {
1098             project.refresh();
1099             refreshWorkspace();
1100         }
1101     }
1102 
1103     void removeProjectItem(const Object obj) {
1104         if (currentWorkspace is null)
1105             return;
1106         ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1107         if (!srcfile)
1108             return;
1109         Project project = srcfile.project;
1110         if (!project)
1111             return;
1112         window.showMessageBox(UIString("Remove file"d), 
1113                 UIString("Do you want to remove file "d ~ srcfile.name ~ "?"), 
1114                 [ACTION_YES, ACTION_NO], 
1115                 1, delegate(const Action result) {
1116                     if (result == StandardAction.Yes) {
1117                         // save and close
1118                         try {
1119                             import std.file : remove;
1120                             closeTab(srcfile.filename);
1121                             remove(srcfile.filename);
1122                             project.refresh();
1123                             refreshWorkspace();
1124                         } catch (Exception e) {
1125                             Log.e("Error while removing file");
1126                         }
1127                     }
1128                     // else ignore
1129                     return true;
1130                 });
1131 
1132     }
1133 
1134     void addProjectItem(const Object obj) {
1135         if (currentWorkspace is null)
1136             return;
1137         Project project;
1138         ProjectFolder folder;
1139         if (cast(Project)obj) {
1140             project = cast(Project)obj;
1141         } else if (cast(ProjectFolder)obj) {
1142             folder = cast(ProjectFolder)obj;
1143             project = folder.project;
1144         } else if (cast(ProjectSourceFile)obj) {
1145             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1146             folder = cast(ProjectFolder)srcfile.parent;
1147             project = srcfile.project;
1148         } else {
1149             ProjectSourceFile srcfile = currentEditorSourceFile;
1150             if (srcfile) {
1151                 folder = cast(ProjectFolder)srcfile.parent;
1152                 project = srcfile.project;
1153             }
1154         }
1155         if (project && folder && project.workspace is currentWorkspace) {
1156             NewFileDlg dlg = new NewFileDlg(this, project, folder);
1157             dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1158                 if (result.id == ACTION_FILE_NEW_SOURCE_FILE.id) {
1159                     FileCreationResult res = cast(FileCreationResult)result.objectParam;
1160                     if (res) {
1161                         //res.project.reload();
1162                         res.project.refresh();
1163                         refreshWorkspace();
1164                         if (isSupportedSourceTextFileFormat(res.filename)) {
1165                             openSourceFile(res.filename, null, true);
1166                         }
1167                     }
1168                 }
1169             };
1170             dlg.show();
1171         }
1172     }
1173 
1174     void createNewProject(bool newWorkspace) {
1175         if (currentWorkspace is null)
1176             newWorkspace = true;
1177         NewProjectDlg dlg = new NewProjectDlg(this, newWorkspace, currentWorkspace);
1178         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1179             if (result.id == ACTION_FILE_NEW_PROJECT.id || result.id == ACTION_FILE_NEW_WORKSPACE.id) {
1180                 //Log.d("settings after edit:\n", s.toJSON(true));
1181                 ProjectCreationResult res = cast(ProjectCreationResult)result.objectParam;
1182                 if (res) {
1183                     // open workspace/project
1184                     if (currentWorkspace is null || res.workspace !is currentWorkspace) {
1185                         // open new workspace
1186                         setWorkspace(res.workspace);
1187                         refreshWorkspace();
1188                         hideHomeScreen();
1189                     } else {
1190                         // project added to current workspace
1191                         loadProject(res.project);
1192                         refreshWorkspace();
1193                         hideHomeScreen();
1194                     }
1195                 }
1196             }
1197         };
1198         dlg.show();
1199     }
1200 
1201     void showPreferences() {
1202         //Log.d("settings before copy:\n", _settings.setting.toJSON(true));
1203         Setting s = _settings.copySettings();
1204         //Log.d("settings after copy:\n", s.toJSON(true));
1205         SettingsDialog dlg = new SettingsDialog(UIString("DlangIDE settings"d), window, s, createSettingsPages());
1206         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1207             if (result.id == ACTION_APPLY.id) {
1208                 //Log.d("settings after edit:\n", s.toJSON(true));
1209                 _settings.applySettings(s);
1210                 applySettings(_settings);
1211                 _settings.save();
1212             }
1213         };
1214         dlg.show();
1215     }
1216 
1217     void setStartupProject(Project project) {
1218         if (!currentWorkspace)
1219             return;
1220         if (!project)
1221             return;
1222         currentWorkspace.startupProject = project;
1223         if (_wsPanel)
1224             _wsPanel.updateDefault();
1225     }
1226 
1227     void showProjectSettings(Project project) {
1228         if (!currentWorkspace)
1229             return;
1230         if (!project)
1231             project = currentWorkspace.startupProject;
1232         if (!project)
1233             return;
1234         Setting s = project.settings.copySettings();
1235         SettingsDialog dlg = new SettingsDialog(UIString(project.name ~ " settings"d), window, s, createProjectSettingsPages());
1236         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1237             if (result.id == ACTION_APPLY.id) {
1238                 //Log.d("settings after edit:\n", s.toJSON(true));
1239                 project.settings.applySettings(s);
1240                 project.settings.save();
1241             }
1242         };
1243         dlg.show();
1244     }
1245 
1246     void applySettings(IDESettings settings) {
1247         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
1248             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
1249             if (ed) {
1250                 applySettings(ed, settings);
1251             }
1252         }
1253         FontManager.fontGamma = settings.fontGamma;
1254         FontManager.hintingMode = settings.hintingMode;
1255         FontManager.minAnitialiasedFontSize = settings.minAntialiasedFontSize;
1256         Platform.instance.uiLanguage = settings.uiLanguage;
1257         Platform.instance.uiTheme = settings.uiTheme;
1258         requestLayout();
1259     }
1260 
1261     void applySettings(DSourceEdit editor, IDESettings settings) {
1262         editor.settings(settings).applySettings();
1263     }
1264 
1265     private bool loadProject(Project project) {
1266         if (!project.load()) {
1267             _logPanel.logLine("Cannot read project " ~ project.filename);
1268             window.showMessageBox(UIString("Cannot open project"d), UIString("Error occured while opening project "d ~ toUTF32(project.filename)));
1269             return false;
1270         }
1271         _logPanel.logLine(toUTF32("Project file " ~ project.filename ~  " is opened ok"));
1272         return true;
1273     }
1274 
1275     void openFileOrWorkspace(string filename) {
1276         if (filename.isWorkspaceFile) {
1277             Workspace ws = new Workspace(this);
1278             if (ws.load(filename)) {
1279                     askForUnsavedEdits(delegate() {
1280                     setWorkspace(ws);
1281                     hideHomeScreen();
1282                     _settings.updateRecentWorkspace(filename);
1283                 });
1284             } else {
1285                 window.showMessageBox(UIString("Cannot open workspace"d), UIString("Error occured while opening workspace"d));
1286                 return;
1287             }
1288         } else if (filename.isProjectFile) {
1289             _logPanel.clear();
1290             _logPanel.logLine("Trying to open project from " ~ filename);
1291             Project project = new Project(currentWorkspace, filename);
1292             string defWsFile = project.defWorkspaceFile;
1293             if (currentWorkspace) {
1294                 Project existing = currentWorkspace.findProject(project.filename);
1295                 if (existing) {
1296                     _logPanel.logLine("This project already exists in current workspace");
1297                     window.showMessageBox(UIString("Open project"d), UIString("Project is already in workspace"d));
1298                     return;
1299                 }
1300                 window.showMessageBox(UIString("Open project"d), UIString("Do you want to create new workspace or use current one?"d),
1301                                       [ACTION_ADD_TO_CURRENT_WORKSPACE, ACTION_CREATE_NEW_WORKSPACE, ACTION_CANCEL], 0, delegate(const Action result) {
1302                                           if (result.id == IDEActions.CreateNewWorkspace) {
1303                                               // new ws
1304                                               createNewWorkspaceForExistingProject(project);
1305                                               hideHomeScreen();
1306                                           } else if (result.id == IDEActions.AddToCurrentWorkspace) {
1307                                               // add to current
1308                                               currentWorkspace.addProject(project);
1309                                               loadProject(project);
1310                                               currentWorkspace.save();
1311                                               refreshWorkspace();
1312                                               hideHomeScreen();
1313                                           }
1314                                           return true;
1315                                       });
1316             } else {
1317                 // new workspace file
1318                 createNewWorkspaceForExistingProject(project);
1319             }
1320         } else {
1321             _logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
1322             window.showMessageBox(UIString("Invalid workspace file"d), UIString("This file is not a valid workspace or project file"d));
1323         }
1324     }
1325 
1326     void refreshWorkspace() {
1327         _logPanel.logLine("Refreshing workspace");
1328         _wsPanel.reloadItems();
1329         closeRemovedDocuments();
1330     }
1331 
1332     void createNewWorkspaceForExistingProject(Project project) {
1333         string defWsFile = project.defWorkspaceFile;
1334         _logPanel.logLine("Creating new workspace " ~ defWsFile);
1335         // new ws
1336         Workspace ws = new Workspace(this);
1337         ws.name = project.name;
1338         ws.description = project.description;
1339         ws.addProject(project);
1340         loadProject(project);
1341         ws.save(defWsFile);
1342         setWorkspace(ws);
1343         _logPanel.logLine("Done");
1344     }
1345 
1346     //bool loadWorkspace(string path) {
1347     //    // testing workspace loader
1348     //    Workspace ws = new Workspace();
1349     //    ws.load(path);
1350     //    setWorkspace(ws);
1351     //    //ws.save(ws.filename ~ ".bak");
1352     //    return true;
1353     //}
1354 
1355     void setWorkspace(Workspace ws) {
1356         closeAllDocuments();
1357         currentWorkspace = ws;
1358         _wsPanel.workspace = ws;
1359         requestActionsUpdate();
1360         if (ws && ws.startupProject && ws.startupProject.mainSourceFile) {
1361             openSourceFile(ws.startupProject.mainSourceFile.filename);
1362             _tabs.setFocus();
1363         }
1364     }
1365 
1366     void refreshProject(Project project) {
1367         if (currentWorkspace && project.loadSelections()) {
1368             currentWorkspace.cleanupUnusedDependencies();
1369             refreshWorkspace();
1370         }
1371     }
1372 
1373     void revealProjectInExplorer(Project project) {
1374         Platform.instance.showInFileManager(project.items.filename);
1375     }
1376 
1377     void buildProject(BuildOperation buildOp, Project project, BuildResultListener listener = null) {
1378         if (!currentWorkspace) {
1379             _logPanel.logLine("No workspace is opened");
1380             return;
1381         }
1382         if (!project)
1383             project = currentWorkspace.startupProject;
1384         if (!project) {
1385             _logPanel.logLine("No project is opened");
1386             return;
1387         }
1388         _logPanel.activateLogTab();
1389         if (!listener) {
1390             if (buildOp == BuildOperation.Upgrade || buildOp == BuildOperation.Build || buildOp == BuildOperation.Rebuild) {
1391                 listener = delegate(int result) {
1392                     if (!result) {
1393                         // success: update workspace
1394                         refreshProject(project);
1395                     }
1396                 };
1397             }
1398         }
1399         ProjectSettings projectSettings = project.settings;
1400         string toolchain = projectSettings.getToolchain(_settings);
1401         string arch = projectSettings.getArch(_settings);
1402         string dubExecutable = _settings.dubExecutable;
1403         string dubAdditionalParams = projectSettings.getDubAdditionalParams(_settings);
1404         Builder op = new Builder(this, project, _logPanel, currentWorkspace.projectConfiguration, currentWorkspace.buildConfiguration, buildOp, 
1405                                  dubExecutable, dubAdditionalParams,
1406                                  toolchain,
1407                                  arch,
1408                                  listener);
1409         setBackgroundOperation(op);
1410     }
1411     
1412     /// updates list of available configurations
1413     void setProjectConfigurations(dstring[] items) {
1414         projectConfigurationCombo.items = items;
1415     }
1416     
1417     /// handle files dropped to application window
1418     void onFilesDropped(string[] filenames) {
1419         //Log.d("onFilesDropped(", filenames, ")");
1420         bool first = true;
1421         for (int i = 0; i < filenames.length; i++) {
1422             openSourceFile(filenames[i], null, first);
1423             first = false;
1424         }
1425     }
1426 
1427     /// return false to prevent closing
1428     bool onCanClose() {
1429         askForUnsavedEdits(delegate() {
1430             if (currentWorkspace)
1431                 currentWorkspace.save();
1432             window.close();
1433         });
1434         return false;
1435     }
1436     /// called when main window is closing
1437     void onWindowClose() {
1438         Log.i("onWindowClose()");
1439         stopExecution();
1440     }
1441 }
1442