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