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