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.core.stdaction;
17 import dlangui.core.files;
18 
19 import dlangide.ui.commands;
20 import dlangide.ui.wspanel;
21 import dlangide.ui.outputpanel;
22 import dlangide.ui.dsourceedit;
23 import dlangide.ui.homescreen;
24 import dlangide.tools.d.dcdserver;
25 import dlangide.workspace.workspace;
26 import dlangide.workspace.project;
27 import dlangide.builders.builder;
28 import dlangide.tools.editorTool;
29 
30 import std.conv;
31 import std.utf;
32 import std.algorithm;
33 import std.path;
34 
35 bool isSupportedSourceTextFileFormat(string filename) {
36     return (filename.endsWith(".d") || filename.endsWith(".txt") || filename.endsWith(".cpp") || filename.endsWith(".h") || filename.endsWith(".c")
37         || filename.endsWith(".json") || filename.endsWith(".dd") || filename.endsWith(".ddoc") || filename.endsWith(".xml") || filename.endsWith(".html")
38         || filename.endsWith(".html") || filename.endsWith(".css") || filename.endsWith(".log") || filename.endsWith(".hpp"));
39 }
40 
41 class BackgroundOperationWatcherTest : BackgroundOperationWatcher {
42     this(AppFrame frame) {
43         super(frame);
44     }
45     int _counter;
46     /// returns description of background operation to show in status line
47     override @property dstring description() { return "Test progress: "d ~ to!dstring(_counter); }
48     /// returns icon of background operation to show in status line
49     override @property string icon() { return "folder"; }
50     /// update background operation status
51     override void update() {
52         _counter++;
53         if (_counter >= 100)
54             _finished = true;
55         super.update();
56     }
57 }
58 
59 /// DIDE app frame
60 class IDEFrame : AppFrame {
61 
62     MenuItem mainMenuItems;
63     WorkspacePanel _wsPanel;
64     OutputPanel _logPanel;
65     DockHost _dockHost;
66     TabWidget _tabs;
67     EditorTool _editorTool;
68     DCDServer _dcdServer;
69 
70     dstring frameWindowCaptionSuffix = "DLangIDE"d;
71 
72     this(Window window) {
73         super();
74         window.mainWidget = this;
75         window.onFilesDropped = &onFilesDropped;
76         window.onCanClose = &onCanClose;
77         window.onClose = &onWindowClose;
78     }
79 
80     override protected void init() {
81         _appName = "dlangide";
82         _editorTool = new DEditorTool(this);
83         _dcdServer = new DCDServer();
84 
85         super.init();
86     }
87 
88     /// move focus to editor in currently selected tab
89     void focusEditor(string id) {
90         Widget w = _tabs.tabBody(id);
91         if (w) {
92             if (w.visible)
93                 w.setFocus();
94         }
95     }
96 
97     /// source file selected in workspace tree
98     bool onSourceFileSelected(ProjectSourceFile file, bool activate) {
99         Log.d("onSourceFileSelected ", file.filename);
100         return openSourceFile(file.filename, file, activate);
101     }
102 
103 	///
104 	bool onCompilerLogIssueClick(dstring filename, int line, int column)
105 	{
106 		Log.d("onCompilerLogIssueClick ", filename);
107 
108 		import std.conv:to;
109 		openSourceFile(to!string(filename));
110 
111 		currentEditor().setCaretPos(line-1,column);
112 
113 		return true;
114 	}
115 
116     void onModifiedStateChange(Widget source, bool modified) {
117         //
118         Log.d("onModifiedStateChange ", source.id, " modified=", modified);
119         int index = _tabs.tabIndex(source.id);
120         if (index >= 0) {
121             dstring name = toUTF32((modified ? "* " : "") ~ baseName(source.id));
122             _tabs.renameTab(index, name);
123         }
124     }
125 
126     bool openSourceFile(string filename, ProjectSourceFile file = null, bool activate = true) {
127         if (!file && !filename)
128             return false;
129         if (!file)
130             file = _wsPanel.findSourceFileItem(filename, false);
131 
132 		//if(!file)
133 		//	return false;
134 
135         if (file)
136 		    filename = file.filename;
137 
138 		Log.d("openSourceFile ", filename);
139 		int index = _tabs.tabIndex(filename);
140         if (index >= 0) {
141             // file is already opened in tab
142             _tabs.selectTab(index, true);
143         } else {
144             // open new file
145             DSourceEdit editor = new DSourceEdit(filename);
146             if (file ? editor.load(file) : editor.load(filename)) {
147                 _tabs.addTab(editor, toUTF32(baseName(filename)), null, true);
148                 index = _tabs.tabIndex(filename);
149                 TabItem tab = _tabs.tab(filename);
150                 tab.objectParam = file;
151                 editor.onModifiedStateChangeListener = &onModifiedStateChange;
152                 _tabs.selectTab(index, true);
153             } else {
154                 destroy(editor);
155                 if (window)
156                     window.showMessageBox(UIString("File open error"d), UIString("Failed to open file "d ~ toUTF32(file.filename)));
157                 return false;
158             }
159         }
160         if (activate) {
161             focusEditor(filename);
162         }
163         requestLayout();
164         return true;
165     }
166 
167     static immutable HOME_SCREEN_ID = "HOME_SCREEN";
168     void showHomeScreen() {
169         int index = _tabs.tabIndex(HOME_SCREEN_ID);
170         if (index >= 0) {
171             _tabs.selectTab(index, true);
172         } else {
173             HomeScreen home = new HomeScreen(HOME_SCREEN_ID, this);
174             _tabs.addTab(home, "Home"d, null, true);
175             _tabs.selectTab(HOME_SCREEN_ID, true);
176         }
177     }
178 
179     void onTabChanged(string newActiveTabId, string previousTabId) {
180         int index = _tabs.tabIndex(newActiveTabId);
181         if (index >= 0) {
182             TabItem tab = _tabs.tab(index);
183             ProjectSourceFile file = cast(ProjectSourceFile)tab.objectParam;
184             if (file) {
185                 //setCurrentProject(file.project);
186                 // tab is source file editor
187                 _wsPanel.selectItem(file);
188                 focusEditor(file.filename);
189             }
190             window.windowCaption(tab.text.value ~ " - "d ~ frameWindowCaptionSuffix);
191         }
192     }
193 
194     // returns DSourceEdit from currently active tab (if it's editor), null if current tab is not editor or no tabs open
195     DSourceEdit currentEditor() {
196         return cast(DSourceEdit)_tabs.selectedTabBody();
197     }
198 
199     /// close tab w/o confirmation
200     void closeTab(string tabId) {
201         _wsPanel.selectItem(null);
202         _tabs.removeTab(tabId);
203     }
204 
205     /// close all editor tabs
206     void closeAllDocuments() {
207         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
208             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
209             if (ed) {
210                 closeTab(ed.id);
211             }
212         }
213     }
214 
215     /// returns first unsaved document
216     protected DSourceEdit hasUnsavedEdits() {
217         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
218             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
219             if (ed && ed.content.modified) {
220                 return ed;
221             }
222         }
223         return null;
224     }
225 
226     protected void askForUnsavedEdits(void delegate() onConfirm) {
227         DSourceEdit ed = hasUnsavedEdits();
228         if (!ed) {
229             // no unsaved edits
230             onConfirm();
231             return;
232         }
233         string tabId = ed.id;
234         // tab content is modified - ask for confirmation
235         window.showMessageBox(UIString("Close file "d ~ toUTF32(baseName(tabId))), UIString("Content of this file has been changed."d), 
236                               [ACTION_SAVE, ACTION_SAVE_ALL, ACTION_DISCARD_CHANGES, ACTION_DISCARD_ALL, ACTION_CANCEL], 
237                               0, delegate(const Action result) {
238                                   if (result == StandardAction.Save) {
239                                       // save and close
240                                       ed.save();
241                                       askForUnsavedEdits(onConfirm);
242                                   } else if (result == StandardAction.DiscardChanges) {
243                                       // close, don't save
244                                       closeTab(tabId);
245                                       closeAllDocuments();
246                                       onConfirm();
247                                   } else if (result == StandardAction.SaveAll) {
248                                       ed.save();
249                                       for(;;) {
250                                           DSourceEdit editor = hasUnsavedEdits();
251                                           if (!editor)
252                                               break;
253                                           editor.save();
254                                       }
255                                       closeAllDocuments();
256                                       onConfirm();
257                                   } else if (result == StandardAction.DiscardAll) {
258                                       // close, don't save
259                                       closeAllDocuments();
260                                       onConfirm();
261                                   }
262                                   // else ignore
263                                   return true;
264                               });
265     }
266 
267     protected void onTabClose(string tabId) {
268         Log.d("onTabClose ", tabId);
269         int index = _tabs.tabIndex(tabId);
270         if (index >= 0) {
271             DSourceEdit d = cast(DSourceEdit)_tabs.tabBody(tabId);
272             if (d && d.content.modified) {
273                 // tab content is modified - ask for confirmation
274                 window.showMessageBox(UIString("Close tab"d), UIString("Content of "d ~ toUTF32(baseName(tabId)) ~ " file has been changed."d), 
275                                       [ACTION_SAVE, ACTION_DISCARD_CHANGES, ACTION_CANCEL], 
276                                       0, delegate(const Action result) {
277                                           if (result == StandardAction.Save) {
278                                               // save and close
279                                               d.save();
280                                               closeTab(tabId);
281                                           } else if (result == StandardAction.DiscardChanges) {
282                                               // close, don't save
283                                               closeTab(tabId);
284                                           }
285                                           // else ignore
286                                           return true;
287                                       });
288             } else {
289                 closeTab(tabId);
290             }
291         }
292     }
293 
294     /// create app body widget
295     override protected Widget createBody() {
296         _dockHost = new DockHost();
297 
298         //=============================================================
299         // Create body - Tabs
300 
301         // editor tabs
302         _tabs = new TabWidget("TABS");
303         _tabs.hiddenTabsVisibility = Visibility.Gone;
304         _tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
305         _tabs.onTabChangedListener = &onTabChanged;
306         _tabs.onTabCloseListener = &onTabClose;
307 
308         _dockHost.bodyWidget = _tabs;
309 
310         //=============================================================
311         // Create workspace docked panel
312         _wsPanel = new WorkspacePanel("workspace");
313         _wsPanel.sourceFileSelectionListener = &onSourceFileSelected;
314         _wsPanel.dockAlignment = DockAlignment.Left;
315         _dockHost.addDockedWindow(_wsPanel);
316 
317         _logPanel = new OutputPanel("output");
318 		_logPanel.compilerLogIssueClickHandler = &onCompilerLogIssueClick;
319         _logPanel.appendText(null, "DlangIDE is started\nHINT: Try to open some DUB project\n"d);
320         string dubPath = findExecutablePath("dub");
321         string dmdPath = findExecutablePath("dmd");
322         string ldcPath = findExecutablePath("ldc2");
323         string gdcPath = findExecutablePath("gdc");
324         _logPanel.appendText(null, dubPath ? ("dub path: "d ~ toUTF32(dubPath) ~ "\n"d) : ("dub is not found! cannot build projects without DUB\n"d));
325         _logPanel.appendText(null, dmdPath ? ("dmd path: "d ~ toUTF32(dmdPath) ~ "\n"d) : ("dmd compiler is not found!\n"d));
326         _logPanel.appendText(null, ldcPath ? ("ldc path: "d ~ toUTF32(ldcPath) ~ "\n"d) : ("ldc compiler is not found!\n"d));
327         _logPanel.appendText(null, gdcPath ? ("gdc path: "d ~ toUTF32(gdcPath) ~ "\n"d) : ("gdc compiler is not found!\n"d));
328 
329         if (_dcdServer.start()) {
330             _logPanel.appendText(null, "dcd-server is started on port "d ~ to!dstring(_dcdServer.port) ~ "\n"d);
331         } else {
332             _logPanel.appendText(null, "cannot start dcd-server: code completion for D code will not work"d);
333         }
334 
335         _dockHost.addDockedWindow(_logPanel);
336 
337         return _dockHost;
338     }
339 
340     /// create main menu
341     override protected MainMenu createMainMenu() {
342 
343         mainMenuItems = new MenuItem();
344         MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE"));
345         MenuItem fileNewItem = new MenuItem(new Action(1, "MENU_FILE_NEW"));
346         fileNewItem.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT);
347         fileItem.add(fileNewItem);
348         fileItem.add(ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
349                      ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_EXIT);
350 
351         MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
352 		editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, 
353                      ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO);
354         MenuItem editItemAdvanced = new MenuItem(new Action(221, "MENU_EDIT_ADVANCED"));
355 		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);
356 		editItem.add(editItemAdvanced);
357 
358 		editItem.add(ACTION_EDIT_PREFERENCES);
359 
360         MenuItem navItem = new MenuItem(new Action(21, "MENU_NAVIGATE"));
361         navItem.add(ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS);
362 
363         MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT"));
364         projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS);
365 
366         MenuItem buildItem = new MenuItem(new Action(22, "MENU_BUILD"));
367         buildItem.add(ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
368                      ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN);
369 
370         MenuItem debugItem = new MenuItem(new Action(23, "MENU_DEBUG"));
371         debugItem.add(ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, 
372                       ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE);
373 
374 
375 		MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c));
376         windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES"));
377         windowItem.add(ACTION_WINDOW_CLOSE_ALL_DOCUMENTS);
378         MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c));
379         helpItem.add(new Action(40, "MENU_HELP_VIEW_HELP"));
380         helpItem.add(ACTION_HELP_ABOUT);
381         mainMenuItems.add(fileItem);
382         mainMenuItems.add(editItem);
383         mainMenuItems.add(projectItem);
384         mainMenuItems.add(navItem);
385         mainMenuItems.add(buildItem);
386         mainMenuItems.add(debugItem);
387 		//mainMenuItems.add(viewItem);
388 		mainMenuItems.add(windowItem);
389         mainMenuItems.add(helpItem);
390 
391         MainMenu mainMenu = new MainMenu(mainMenuItems);
392         mainMenu.backgroundColor = 0xd6dbe9;
393         return mainMenu;
394     }
395 
396     /// override it
397     override protected void updateShortcuts() {
398         if (applyShortcutsSettings()) {
399             Log.d("Shortcut actions loaded");
400         } else {
401             Log.d("Saving default shortcuts");
402             const(Action)[] actions;
403             actions ~= [
404                 ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, 
405                 ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, 
406                 ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, 
407                 ACTION_EDIT_PREFERENCES, 
408                 ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT, ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
409                 ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_EXIT, 
410                 ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, 
411                 ACTION_PROJECT_SETTINGS, ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
412                 ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN, ACTION_DEBUG_START, 
413                 ACTION_DEBUG_START_NO_DEBUG, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE, 
414                 ACTION_WINDOW_CLOSE_ALL_DOCUMENTS, ACTION_HELP_ABOUT];
415             actions ~= STD_EDITOR_ACTIONS;
416             saveShortcutsSettings(actions);
417         }
418     }
419 
420     /// create app toolbars
421     override protected ToolBarHost createToolbars() {
422         ToolBarHost res = new ToolBarHost();
423         ToolBar tb;
424         tb = res.getOrAddToolbar("Standard");
425         tb.addButtons(ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR);
426 
427         tb.addButtons(ACTION_DEBUG_START);
428         ToolBarComboBox cbBuildConfiguration = new ToolBarComboBox("buildConfig", ["Debug"d, "Release"d, "Unittest"d]);
429         cbBuildConfiguration.onItemClickListener = delegate(Widget source, int index) {
430             if (currentWorkspace && index < 3) {
431                 currentWorkspace.buildConfiguration = [BuildConfiguration.Debug, BuildConfiguration.Release, BuildConfiguration.Unittest][index];
432             }
433             return true;
434         };
435         cbBuildConfiguration.action = ACTION_BUILD_CONFIGURATIONS;
436         tb.addControl(cbBuildConfiguration);
437         tb.addButtons(ACTION_PROJECT_BUILD);
438 
439         tb = res.getOrAddToolbar("Edit");
440         tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR,
441                       ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT);
442         return res;
443     }
444 
445 	/// override to handle specific actions state (e.g. change enabled state for supported actions)
446 	override bool handleActionStateRequest(const Action a) {
447         switch (a.id) {
448             case IDEActions.FileExit:
449             case IDEActions.FileOpen:
450             case IDEActions.WindowCloseAllDocuments:
451             case IDEActions.FileOpenWorkspace:
452                 // disable when background operation in progress
453                 if (!_currentBackgroundOperation)
454                     a.state = ACTION_STATE_ENABLED;
455                 else
456                     a.state = ACTION_STATE_DISABLE;
457                 return true;
458             case IDEActions.HelpAbout:
459             case StandardAction.OpenUrl:
460                 // always enabled
461                 a.state = ACTION_STATE_ENABLED;
462                 return true;
463             case IDEActions.BuildProject:
464             case IDEActions.BuildWorkspace:
465             case IDEActions.RebuildProject:
466             case IDEActions.RebuildWorkspace:
467             case IDEActions.CleanProject:
468             case IDEActions.CleanWorkspace:
469             case IDEActions.DebugStart:
470             case IDEActions.DebugStartNoDebug:
471             case IDEActions.DebugContinue:
472             case IDEActions.UpdateProjectDependencies:
473             case IDEActions.RefreshProject:
474 			case IDEActions.SetStartupProject:
475 			case IDEActions.ProjectSettings:
476                 // enable when project exists
477                 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
478                     a.state = ACTION_STATE_ENABLED;
479                 else
480                     a.state = ACTION_STATE_DISABLE;
481                 return true;
482             default:
483                 return super.handleActionStateRequest(a);
484         }
485 	}
486 
487     FileDialog createFileDialog(UIString caption) {
488         FileDialog dlg = new FileDialog(caption, window, null);
489         dlg.filetypeIcons[".d"] = "text-d";
490         dlg.filetypeIcons["dub.json"] = "project-d";
491         dlg.filetypeIcons["package.json"] = "project-d";
492         dlg.filetypeIcons[".dlangidews"] = "project-development";
493         return dlg;
494     }
495 
496     /// override to handle specific actions
497 	override bool handleAction(const Action a) {
498         if (a) {
499             switch (a.id) {
500                 case IDEActions.FileExit:
501                     if (onCanClose())
502                         window.close();
503                     return true;
504                 case IDEActions.HelpAbout:
505                     Window wnd = Platform.instance.createWindow("About...", window, WindowFlag.Modal);
506                     wnd.mainWidget = createAboutWidget();
507                     wnd.show();
508                     return true;
509                 case StandardAction.OpenUrl:
510                     platform.openURL(a.stringParam);
511                     return true;
512                 case IDEActions.FileOpen:
513                     UIString caption;
514                     caption = "Open Text File"d;
515                     FileDialog dlg = createFileDialog(caption);
516                     dlg.addFilter(FileFilterEntry(UIString("Source files"d), "*.d;*.dd;*.ddoc;*.dh;*.json;*.xml;*.ini"));
517                     dlg.onDialogResult = delegate(Dialog dlg, const Action result) {
518 						if (result.id == ACTION_OPEN.id) {
519                             string filename = result.stringParam;
520                             if (isSupportedSourceTextFileFormat(filename)) {
521                                 openSourceFile(filename);
522                             }
523                         }
524                     };
525                     dlg.show();
526                     return true;
527                 case IDEActions.BuildProject:
528                 case IDEActions.BuildWorkspace:
529                     buildProject(BuildOperation.Build);
530                     return true;
531                 case IDEActions.RebuildProject:
532                 case IDEActions.RebuildWorkspace:
533                     buildProject(BuildOperation.Rebuild);
534                     return true;
535                 case IDEActions.CleanProject:
536                 case IDEActions.CleanWorkspace:
537                     buildProject(BuildOperation.Clean);
538                     return true;
539                 case IDEActions.DebugStart:
540                 case IDEActions.DebugStartNoDebug:
541                 case IDEActions.DebugContinue:
542                     buildProject(BuildOperation.Run);
543                     return true;
544                 case IDEActions.UpdateProjectDependencies:
545                     buildProject(BuildOperation.Upgrade);
546                     return true;
547                 case IDEActions.RefreshProject:
548                     refreshWorkspace();
549                     return true;
550                 case IDEActions.WindowCloseAllDocuments:
551                     askForUnsavedEdits(delegate() {
552                         closeAllDocuments();
553                     });
554                     return true;
555                 case IDEActions.FileOpenWorkspace:
556                     UIString caption;
557                     caption = "Open Workspace or Project"d;
558                     FileDialog dlg = createFileDialog(caption);
559                     dlg.addFilter(FileFilterEntry(UIString("Workspace and project files"d), "*.dlangidews;dub.json;package.json"));
560                     dlg.onDialogResult = delegate(Dialog dlg, const Action result) {
561 						if (result.id == ACTION_OPEN.id) {
562                             string filename = result.stringParam;
563                             if (filename.length)
564                                 openFileOrWorkspace(filename);
565                         }
566                     };
567                     dlg.show();
568                     return true;
569                 case IDEActions.GoToDefinition:
570                     Log.d("Trying to go to definition.");
571                     _editorTool.goToDefinition(currentEditor(), currentEditor.getCaretPosition());
572                     return true;
573                 case IDEActions.GetCompletionSuggestions:
574                     Log.d("Getting auto completion suggestions.");
575                     auto results = _editorTool.getCompletions(currentEditor, currentEditor.getCaretPosition);
576                     currentEditor.showCompletionPopup(results);
577                     return true;
578                 default:
579                     return super.handleAction(a);
580             }
581         }
582 		return false;
583 	}
584 
585     void openFileOrWorkspace(string filename) {
586         if (filename.isWorkspaceFile) {
587             Workspace ws = new Workspace();
588             if (ws.load(filename)) {
589                 askForUnsavedEdits(delegate() {
590                     setWorkspace(ws);
591                 });
592             } else {
593                 window.showMessageBox(UIString("Cannot open workspace"d), UIString("Error occured while opening workspace"d));
594                 return;
595             }
596         } else if (filename.isProjectFile) {
597             _logPanel.clear();
598             _logPanel.logLine("Trying to open project from " ~ filename);
599             Project project = new Project();
600             if (!project.load(filename)) {
601                 _logPanel.logLine("Cannot read project file " ~ filename);
602                 window.showMessageBox(UIString("Cannot open project"d), UIString("Error occured while opening project"d));
603                 return;
604             }
605             _logPanel.logLine("Project file is opened ok");
606             string defWsFile = project.defWorkspaceFile;
607             if (currentWorkspace) {
608                 Project existing = currentWorkspace.findProject(project.filename);
609                 if (existing) {
610                     _logPanel.logLine("This project already exists in current workspace");
611                     window.showMessageBox(UIString("Open project"d), UIString("Project is already in workspace"d));
612                     return;
613                 }
614                 window.showMessageBox(UIString("Open project"d), UIString("Do you want to create new workspace or use current one?"d),
615                                       [ACTION_ADD_TO_CURRENT_WORKSPACE, ACTION_CREATE_NEW_WORKSPACE, ACTION_CANCEL], 0, delegate(const Action result) {
616                                           if (result.id == IDEActions.CreateNewWorkspace) {
617                                               // new ws
618                                               createNewWorkspaceForExistingProject(project);
619                                           } else if (result.id == IDEActions.AddToCurrentWorkspace) {
620                                               // add to current
621                                               currentWorkspace.addProject(project);
622                                               currentWorkspace.save();
623                                               refreshWorkspace();
624                                           }
625                                           return true;
626                                       });
627             } else {
628                 // new workspace file
629                 createNewWorkspaceForExistingProject(project);
630             }
631         } else {
632             _logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
633             window.showMessageBox(UIString("Invalid workspace file"d), UIString("This file is not a valid workspace or project file"d));
634         }
635     }
636 
637     void refreshWorkspace() {
638         _logPanel.logLine("Refreshing workspace");
639         _wsPanel.reloadItems();
640     }
641 
642     void createNewWorkspaceForExistingProject(Project project) {
643         string defWsFile = project.defWorkspaceFile;
644         _logPanel.logLine("Creating new workspace " ~ defWsFile);
645         // new ws
646         Workspace ws = new Workspace();
647         ws.name = project.name;
648         ws.description = project.description;
649         ws.addProject(project);
650         ws.save(defWsFile);
651         setWorkspace(ws);
652         _logPanel.logLine("Done");
653     }
654 
655     //bool loadWorkspace(string path) {
656     //    // testing workspace loader
657     //    Workspace ws = new Workspace();
658     //    ws.load(path);
659     //    setWorkspace(ws);
660     //    //ws.save(ws.filename ~ ".bak");
661     //    return true;
662     //}
663 
664     void setWorkspace(Workspace ws) {
665         closeAllDocuments();
666         currentWorkspace = ws;
667         _wsPanel.workspace = ws;
668         requestActionsUpdate();
669         if (ws && ws.startupProject && ws.startupProject.mainSourceFile) {
670             openSourceFile(ws.startupProject.mainSourceFile.filename);
671             _tabs.setFocus();
672         }
673     }
674 
675     void buildProject(BuildOperation buildOp) {
676         if (!currentWorkspace || !currentWorkspace.startupProject) {
677             _logPanel.logLine("No project is opened");
678             return;
679         }
680         Builder op = new Builder(this, currentWorkspace.startupProject, _logPanel, currentWorkspace.buildConfiguration, buildOp, false);
681         setBackgroundOperation(op);
682     }
683 
684     /// handle files dropped to application window
685     void onFilesDropped(string[] filenames) {
686         //Log.d("onFilesDropped(", filenames, ")");
687         bool first = true;
688         for (int i = 0; i < filenames.length; i++) {
689             if (isSupportedSourceTextFileFormat(filenames[i])) {
690                 openSourceFile(filenames[i], null, first);
691                 first = false;
692             }
693         }
694     }
695 
696     /// return false to prevent closing
697     bool onCanClose() {
698         askForUnsavedEdits(delegate() {
699             window.close();
700         });
701         return false;
702     }
703     /// called when main window is closing
704     void onWindowClose() {
705         Log.i("onWindowClose()");
706         if (_dcdServer) {
707             if (_dcdServer.isRunning)
708                 _dcdServer.stop();
709             destroy(_dcdServer);
710             _dcdServer = null;
711         }
712     }
713 }
714 
715 Widget createAboutWidget() 
716 {
717 	LinearLayout res = new VerticalLayout();
718 	res.padding(Rect(10,10,10,10));
719 	res.addChild(new TextWidget(null, "DLangIDE"d));
720 	res.addChild(new TextWidget(null, "(C) Vadim Lopatin, 2014"d));
721 	res.addChild(new TextWidget(null, "http://github.com/buggins/dlangide"d));
722 	res.addChild(new TextWidget(null, "IDE for D programming language written in D"d));
723 	res.addChild(new TextWidget(null, "Uses DlangUI library for GUI"d));
724 	Button closeButton = new Button("close", "Close"d);
725 	closeButton.onClickListener = delegate(Widget src) {
726 		Log.i("Closing window");
727 		res.window.close();
728 		return true;
729 	};
730 	res.addChild(closeButton);
731 	return res;
732 }