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.tools.d.dcdserver;
29 import dlangide.workspace.workspace;
30 import dlangide.workspace.project;
31 import dlangide.builders.builder;
32 import dlangide.tools.editorTool;
33 
34 import ddebug.common.execution;
35 import ddebug.common.nodebug;
36 
37 import std.conv;
38 import std.utf;
39 import std.algorithm;
40 import std.path;
41 
42 bool isSupportedSourceTextFileFormat(string filename) {
43     return (filename.endsWith(".d") || filename.endsWith(".txt") || filename.endsWith(".cpp") || filename.endsWith(".h") || filename.endsWith(".c")
44         || filename.endsWith(".json") || filename.endsWith(".dd") || filename.endsWith(".ddoc") || filename.endsWith(".xml") || filename.endsWith(".html")
45         || filename.endsWith(".html") || filename.endsWith(".css") || filename.endsWith(".log") || filename.endsWith(".hpp"));
46 }
47 
48 class BackgroundOperationWatcherTest : BackgroundOperationWatcher {
49     this(AppFrame frame) {
50         super(frame);
51     }
52     int _counter;
53     /// returns description of background operation to show in status line
54     override @property dstring description() { return "Test progress: "d ~ to!dstring(_counter); }
55     /// returns icon of background operation to show in status line
56     override @property string icon() { return "folder"; }
57     /// update background operation status
58     override void update() {
59         _counter++;
60         if (_counter >= 100)
61             _finished = true;
62         super.update();
63     }
64 }
65 
66 /// DIDE app frame
67 class IDEFrame : AppFrame, ProgramExecutionStatusListener {
68 
69 	private ToolBarComboBox projectConfigurationCombo;
70 	
71     MenuItem mainMenuItems;
72     WorkspacePanel _wsPanel;
73     OutputPanel _logPanel;
74     DockHost _dockHost;
75     TabWidget _tabs;
76     DCDServer _dcdServer;
77     IDESettings _settings;
78     ProgramExecution _execution;
79 
80     dstring frameWindowCaptionSuffix = "DLangIDE"d;
81 
82     this(Window window) {
83         super();
84         window.mainWidget = this;
85         window.onFilesDropped = &onFilesDropped;
86         window.onCanClose = &onCanClose;
87         window.onClose = &onWindowClose;
88         applySettings(_settings);
89     }
90 
91     /// stop current program execution
92     void stopExecution() {
93         if (_execution) {
94             _logPanel.logLine("Stopping program execution");
95             Log.d("Stopping execution");
96             _execution.stop();
97             //destroy(_execution);
98             _execution = null;
99         }
100     }
101 
102     /// returns true if program execution or debugging is active
103     @property bool isExecutionActive() {
104         return _execution !is null;
105     }
106 
107     /// called when program execution is stopped
108     protected void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
109         executeInUiThread(delegate() {
110             Log.d("onProgramExecutionStatus process: ", process.executableFile, " status: ", status, " exitCode: ", exitCode);
111             _execution = null;
112             // TODO: update state
113             switch(status) {
114                 case ExecutionStatus.Error:
115                     _logPanel.logLine("Cannot run program " ~ process.executableFile);
116                     break;
117                 case ExecutionStatus.Finished:
118                     _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
119                     break;
120                 case ExecutionStatus.Killed:
121                     _logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
122                     break;
123                 default:
124                     _logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
125                     break;
126             }
127             _statusLine.setBackgroundOperationStatus(null, null);
128         });
129     }
130 
131     protected void buildAndRunProject(Project project) {
132         if (!currentWorkspace)
133             return;
134         if (!project)
135             project = currentWorkspace.startupProject;
136         if (!project) {
137             window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d));
138             return;
139         }
140         buildProject(BuildOperation.Build, project, delegate(int result) {
141             if (!result) {
142                 runProject();
143             }
144         });
145     }
146 
147     protected void runProject() {
148         import std.file;
149         stopExecution();
150         if (!currentWorkspace)
151             return;
152         Project project = currentWorkspace.startupProject;
153         if (!project) {
154             window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d));
155             return;
156         }
157         // build project
158         // TODO
159         string executableFileName = project.executableFileName;
160         if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
161             window.showMessageBox(UIString("Cannot run project"d), UIString("Cannot find executable"d));
162             return;
163         }
164         string[] args;
165         string externalConsoleExecutable = null;
166         string workingDirectory = project.workingDirectory;
167         if (project.runInExternalConsole) {
168             version(Windows) {
169             } else {
170                 externalConsoleExecutable = _settings.terminalExecutable;
171             }
172         }
173         // TODO: provide thread safe listener
174         _logPanel.logLine("Starting " ~ executableFileName);
175         _statusLine.setBackgroundOperationStatus("debug-run", "running..."d);
176         _execution = new ProgramExecutionNoDebug(executableFileName, args, workingDirectory, externalConsoleExecutable, this);
177         _execution.run();
178     }
179 
180     override protected void init() {
181         _appName = "dlangide";
182         //_editorTool = new DEditorTool(this);
183         _dcdServer = new DCDServer();
184         _settings = new IDESettings(buildNormalizedPath(settingsDir, "settings.json"));
185         _settings.load();
186         _settings.updateDefaults();
187         _settings.save();
188         super.init();
189     }
190 
191     /// move focus to editor in currently selected tab
192     void focusEditor(string id) {
193         Widget w = _tabs.tabBody(id);
194         if (w) {
195             if (w.visible)
196                 w.setFocus();
197         }
198     }
199 
200     /// source file selected in workspace tree
201     bool onSourceFileSelected(ProjectSourceFile file, bool activate) {
202         Log.d("onSourceFileSelected ", file.filename);
203         return openSourceFile(file.filename, file, activate);
204     }
205 
206     /// returns global IDE settings
207     @property IDESettings settings() { return _settings; }
208 
209 	///
210 	bool onCompilerLogIssueClick(dstring filename, int line, int column)
211 	{
212 		Log.d("onCompilerLogIssueClick ", filename);
213 
214 		import std.conv:to;
215 		openSourceFile(to!string(filename));
216 
217 		currentEditor().setCaretPos(line-1,column);
218 
219 		return true;
220 	}
221 
222     void onModifiedStateChange(Widget source, bool modified) {
223         //
224         Log.d("onModifiedStateChange ", source.id, " modified=", modified);
225         int index = _tabs.tabIndex(source.id);
226         if (index >= 0) {
227             dstring name = toUTF32((modified ? "* " : "") ~ baseName(source.id));
228             _tabs.renameTab(index, name);
229         }
230     }
231 
232     bool openSourceFile(string filename, ProjectSourceFile file = null, bool activate = true) {
233         if (!file && !filename)
234             return false;
235         if (!file)
236             file = _wsPanel.findSourceFileItem(filename, false);
237 
238 		//if(!file)
239 		//	return false;
240 
241         if (file)
242 		    filename = file.filename;
243 
244 		Log.d("openSourceFile ", filename);
245 		int index = _tabs.tabIndex(filename);
246         if (index >= 0) {
247             // file is already opened in tab
248             _tabs.selectTab(index, true);
249         } else {
250             // open new file
251             DSourceEdit editor = new DSourceEdit(filename);
252             if (file ? editor.load(file) : editor.load(filename)) {
253                 _tabs.addTab(editor, toUTF32(baseName(filename)), null, true);
254                 index = _tabs.tabIndex(filename);
255                 TabItem tab = _tabs.tab(filename);
256                 tab.objectParam = file;
257                 editor.modifiedStateChange = &onModifiedStateChange;
258                 applySettings(editor, settings);
259                 _tabs.selectTab(index, true);
260                 if( filename.endsWith(".d") )
261                     editor.editorTool = new DEditorTool(this);
262                 else
263                     editor.editorTool = new DefaultEditorTool(this);
264             } else {
265                 destroy(editor);
266                 if (window)
267                     window.showMessageBox(UIString("File open error"d), UIString("Failed to open file "d ~ toUTF32(file.filename)));
268                 return false;
269             }
270         }
271         if (activate) {
272             focusEditor(filename);
273         }
274         requestLayout();
275         return true;
276     }
277 
278     static immutable HOME_SCREEN_ID = "HOME_SCREEN";
279     void showHomeScreen() {
280         int index = _tabs.tabIndex(HOME_SCREEN_ID);
281         if (index >= 0) {
282             _tabs.selectTab(index, true);
283         } else {
284             HomeScreen home = new HomeScreen(HOME_SCREEN_ID, this);
285             _tabs.addTab(home, "Home"d, null, true);
286             _tabs.selectTab(HOME_SCREEN_ID, true);
287         }
288     }
289 
290     void hideHomeScreen() {
291         _tabs.removeTab(HOME_SCREEN_ID);
292     }
293 
294     void onTabChanged(string newActiveTabId, string previousTabId) {
295         int index = _tabs.tabIndex(newActiveTabId);
296         if (index >= 0) {
297             TabItem tab = _tabs.tab(index);
298             ProjectSourceFile file = cast(ProjectSourceFile)tab.objectParam;
299             if (file) {
300                 //setCurrentProject(file.project);
301                 // tab is source file editor
302                 _wsPanel.selectItem(file);
303                 focusEditor(file.filename);
304             }
305             window.windowCaption(tab.text.value ~ " - "d ~ frameWindowCaptionSuffix);
306         }
307     }
308 
309     // returns DSourceEdit from currently active tab (if it's editor), null if current tab is not editor or no tabs open
310     DSourceEdit currentEditor() {
311         return cast(DSourceEdit)_tabs.selectedTabBody();
312     }
313 
314     /// close tab w/o confirmation
315     void closeTab(string tabId) {
316         _wsPanel.selectItem(null);
317         _tabs.removeTab(tabId);
318     }
319 
320     /// close all editor tabs
321     void closeAllDocuments() {
322         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
323             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
324             if (ed) {
325                 closeTab(ed.id);
326             }
327         }
328     }
329 
330     /// close editor tabs for which files are removed from filesystem
331     void closeRemovedDocuments() {
332         import std.file;
333         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
334             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
335             if (ed) {
336                 if (!exists(ed.id) || !isFile(ed.id)) {
337                     closeTab(ed.id);
338                 }
339             }
340         }
341     }
342 
343     /// returns first unsaved document
344     protected DSourceEdit hasUnsavedEdits() {
345         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
346             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
347             if (ed && ed.content.modified) {
348                 return ed;
349             }
350         }
351         return null;
352     }
353 
354     protected void askForUnsavedEdits(void delegate() onConfirm) {
355         DSourceEdit ed = hasUnsavedEdits();
356         if (!ed) {
357             // no unsaved edits
358             onConfirm();
359             return;
360         }
361         string tabId = ed.id;
362         // tab content is modified - ask for confirmation
363         window.showMessageBox(UIString("Close file "d ~ toUTF32(baseName(tabId))), UIString("Content of this file has been changed."d), 
364                               [ACTION_SAVE, ACTION_SAVE_ALL, ACTION_DISCARD_CHANGES, ACTION_DISCARD_ALL, ACTION_CANCEL], 
365                               0, delegate(const Action result) {
366                                   if (result == StandardAction.Save) {
367                                       // save and close
368                                       ed.save();
369                                       askForUnsavedEdits(onConfirm);
370                                   } else if (result == StandardAction.DiscardChanges) {
371                                       // close, don't save
372                                       closeTab(tabId);
373                                       closeAllDocuments();
374                                       onConfirm();
375                                   } else if (result == StandardAction.SaveAll) {
376                                       ed.save();
377                                       for(;;) {
378                                           DSourceEdit editor = hasUnsavedEdits();
379                                           if (!editor)
380                                               break;
381                                           editor.save();
382                                       }
383                                       closeAllDocuments();
384                                       onConfirm();
385                                   } else if (result == StandardAction.DiscardAll) {
386                                       // close, don't save
387                                       closeAllDocuments();
388                                       onConfirm();
389                                   }
390                                   // else ignore
391                                   return true;
392                               });
393     }
394 
395     protected void onTabClose(string tabId) {
396         Log.d("onTabClose ", tabId);
397         int index = _tabs.tabIndex(tabId);
398         if (index >= 0) {
399             DSourceEdit d = cast(DSourceEdit)_tabs.tabBody(tabId);
400             if (d && d.content.modified) {
401                 // tab content is modified - ask for confirmation
402                 window.showMessageBox(UIString("Close tab"d), UIString("Content of "d ~ toUTF32(baseName(tabId)) ~ " file has been changed."d), 
403                                       [ACTION_SAVE, ACTION_DISCARD_CHANGES, ACTION_CANCEL], 
404                                       0, delegate(const Action result) {
405                                           if (result == StandardAction.Save) {
406                                               // save and close
407                                               d.save();
408                                               closeTab(tabId);
409                                           } else if (result == StandardAction.DiscardChanges) {
410                                               // close, don't save
411                                               closeTab(tabId);
412                                           }
413                                           // else ignore
414                                           return true;
415                                       });
416             } else {
417                 closeTab(tabId);
418             }
419         }
420     }
421 
422     /// create app body widget
423     override protected Widget createBody() {
424         _dockHost = new DockHost();
425 
426         //=============================================================
427         // Create body - Tabs
428 
429         // editor tabs
430         _tabs = new TabWidget("TABS");
431         _tabs.hiddenTabsVisibility = Visibility.Gone;
432         _tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
433         _tabs.tabChanged = &onTabChanged;
434         _tabs.tabClose = &onTabClose;
435 
436         _dockHost.bodyWidget = _tabs;
437 
438         //=============================================================
439         // Create workspace docked panel
440         _wsPanel = new WorkspacePanel("workspace");
441         _wsPanel.sourceFileSelectionListener = &onSourceFileSelected;
442         _wsPanel.workspaceActionListener = &handleAction;
443         _wsPanel.dockAlignment = DockAlignment.Left;
444         _dockHost.addDockedWindow(_wsPanel);
445 
446         _logPanel = new OutputPanel("output");
447 		_logPanel.compilerLogIssueClickHandler = &onCompilerLogIssueClick;
448         _logPanel.appendText(null, "DlangIDE is started\nHINT: Try to open some DUB project\n"d);
449         string dubPath = findExecutablePath("dub");
450         string dmdPath = findExecutablePath("dmd");
451         string ldcPath = findExecutablePath("ldc2");
452         string gdcPath = findExecutablePath("gdc");
453         _logPanel.appendText(null, dubPath ? ("dub path: "d ~ toUTF32(dubPath) ~ "\n"d) : ("dub is not found! cannot build projects without DUB\n"d));
454         _logPanel.appendText(null, dmdPath ? ("dmd path: "d ~ toUTF32(dmdPath) ~ "\n"d) : ("dmd compiler is not found!\n"d));
455         _logPanel.appendText(null, ldcPath ? ("ldc path: "d ~ toUTF32(ldcPath) ~ "\n"d) : ("ldc compiler is not found!\n"d));
456         _logPanel.appendText(null, gdcPath ? ("gdc path: "d ~ toUTF32(gdcPath) ~ "\n"d) : ("gdc compiler is not found!\n"d));
457 
458         if (_dcdServer.start()) {
459             _logPanel.appendText(null, "dcd-server is started on port "d ~ to!dstring(_dcdServer.port) ~ "\n"d);
460         } else {
461             _logPanel.appendText(null, "cannot start dcd-server: code completion for D code will not work"d);
462         }
463 
464         _dockHost.addDockedWindow(_logPanel);
465 
466         return _dockHost;
467     }
468 
469     /// create main menu
470     override protected MainMenu createMainMenu() {
471 
472         mainMenuItems = new MenuItem();
473         MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE"));
474         MenuItem fileNewItem = new MenuItem(new Action(1, "MENU_FILE_NEW"));
475         fileNewItem.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT);
476         fileItem.add(fileNewItem);
477         fileItem.add(ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
478                      ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_WORKSPACE_CLOSE, ACTION_FILE_EXIT);
479 
480         MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
481 		editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, 
482                      ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_FIND_TEXT);
483         MenuItem editItemAdvanced = new MenuItem(new Action(221, "MENU_EDIT_ADVANCED"));
484 		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);
485 		editItem.add(editItemAdvanced);
486 
487 		editItem.add(ACTION_EDIT_PREFERENCES);
488 
489         MenuItem navItem = new MenuItem(new Action(21, "MENU_NAVIGATE"));
490         navItem.add(ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS);
491 
492         MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT"));
493         projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS);
494 
495         MenuItem buildItem = new MenuItem(new Action(22, "MENU_BUILD"));
496         buildItem.add(ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
497                      ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN);
498 
499         MenuItem debugItem = new MenuItem(new Action(23, "MENU_DEBUG"));
500         debugItem.add(ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, 
501                       ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE);
502 
503 
504 		MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c));
505         windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES"));
506         windowItem.add(ACTION_WINDOW_CLOSE_ALL_DOCUMENTS);
507         MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c));
508         helpItem.add(new Action(40, "MENU_HELP_VIEW_HELP"));
509         helpItem.add(ACTION_HELP_ABOUT);
510         mainMenuItems.add(fileItem);
511         mainMenuItems.add(editItem);
512         mainMenuItems.add(projectItem);
513         mainMenuItems.add(navItem);
514         mainMenuItems.add(buildItem);
515         mainMenuItems.add(debugItem);
516 		//mainMenuItems.add(viewItem);
517 		mainMenuItems.add(windowItem);
518         mainMenuItems.add(helpItem);
519 
520         MainMenu mainMenu = new MainMenu(mainMenuItems);
521         //mainMenu.backgroundColor = 0xd6dbe9;
522         return mainMenu;
523     }
524 
525     /// override it
526     override protected void updateShortcuts() {
527         if (applyShortcutsSettings()) {
528             Log.d("Shortcut actions loaded");
529         } else {
530             Log.d("Saving default shortcuts");
531             const(Action)[] actions;
532             actions ~= [
533                 ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, 
534                 ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, 
535                 ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, 
536                 ACTION_EDIT_PREFERENCES, 
537                 ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT, ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
538                 ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_EXIT, 
539                 ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, 
540                 ACTION_PROJECT_SETTINGS, ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
541                 ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN, ACTION_DEBUG_START, 
542                 ACTION_DEBUG_START_NO_DEBUG, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE, 
543                 ACTION_WINDOW_CLOSE_ALL_DOCUMENTS, ACTION_HELP_ABOUT];
544             actions ~= STD_EDITOR_ACTIONS;
545             saveShortcutsSettings(actions);
546         }
547     }
548 
549     /// create app toolbars
550     override protected ToolBarHost createToolbars() {
551         ToolBarHost res = new ToolBarHost();
552         ToolBar tb;
553         tb = res.getOrAddToolbar("Standard");
554         tb.addButtons(ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR);
555 
556         tb.addButtons(ACTION_DEBUG_START);
557         
558         projectConfigurationCombo = new ToolBarComboBox("projectConfig", [ProjectConfiguration.DEFAULT_NAME.to!dstring]);//Updateable
559         projectConfigurationCombo.itemClick = delegate(Widget source, int index) {
560             if (currentWorkspace) {
561                 currentWorkspace.setStartupProjectConfiguration(projectConfigurationCombo.selectedItem.to!string); 
562             }
563             return true;
564         };
565         projectConfigurationCombo.action = ACTION_PROJECT_CONFIGURATIONS;
566         tb.addControl(projectConfigurationCombo);
567         
568         ToolBarComboBox cbBuildConfiguration = new ToolBarComboBox("buildConfig", ["Debug"d, "Release"d, "Unittest"d]);
569         cbBuildConfiguration.itemClick = delegate(Widget source, int index) {
570             if (currentWorkspace && index < 3) {
571                 currentWorkspace.buildConfiguration = [BuildConfiguration.Debug, BuildConfiguration.Release, BuildConfiguration.Unittest][index];
572             }
573             return true;
574         };
575         cbBuildConfiguration.action = ACTION_BUILD_CONFIGURATIONS;
576         tb.addControl(cbBuildConfiguration);
577         tb.addButtons(ACTION_PROJECT_BUILD);
578 
579         tb = res.getOrAddToolbar("Edit");
580         tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR,
581                       ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT);
582         tb = res.getOrAddToolbar("Debug");
583         tb.addButtons(ACTION_DEBUG_STOP, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_PAUSE);
584         return res;
585     }
586 
587 	/// override to handle specific actions state (e.g. change enabled state for supported actions)
588 	override bool handleActionStateRequest(const Action a) {
589         switch (a.id) {
590             case IDEActions.EditPreferences:
591                 return true;
592             case IDEActions.FileExit:
593             case IDEActions.FileOpen:
594             case IDEActions.WindowCloseAllDocuments:
595             case IDEActions.FileOpenWorkspace:
596                 // disable when background operation in progress
597                 if (!_currentBackgroundOperation)
598                     a.state = ACTION_STATE_ENABLED;
599                 else
600                     a.state = ACTION_STATE_DISABLE;
601                 return true;
602             case IDEActions.HelpAbout:
603             case StandardAction.OpenUrl:
604                 // always enabled
605                 a.state = ACTION_STATE_ENABLED;
606                 return true;
607             case IDEActions.BuildProject:
608             case IDEActions.BuildWorkspace:
609             case IDEActions.RebuildProject:
610             case IDEActions.RebuildWorkspace:
611             case IDEActions.CleanProject:
612             case IDEActions.CleanWorkspace:
613             case IDEActions.UpdateProjectDependencies:
614             case IDEActions.RefreshProject:
615 			case IDEActions.SetStartupProject:
616 			case IDEActions.ProjectSettings:
617                 // enable when project exists
618                 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
619                     a.state = ACTION_STATE_ENABLED;
620                 else
621                     a.state = ACTION_STATE_DISABLE;
622                 return true;
623             case IDEActions.DebugStop:
624                 a.state = isExecutionActive ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
625                 return true;
626             case IDEActions.DebugStart:
627             case IDEActions.DebugStartNoDebug:
628                 if (!isExecutionActive && currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
629                     a.state = ACTION_STATE_ENABLED;
630                 else
631                     a.state = ACTION_STATE_DISABLE;
632                 return true;
633             case IDEActions.DebugContinue:
634             case IDEActions.DebugPause:
635                 a.state = isExecutionActive && _execution.isDebugger ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
636                 return true;
637             default:
638                 return super.handleActionStateRequest(a);
639         }
640 	}
641 
642     FileDialog createFileDialog(UIString caption) {
643         FileDialog dlg = new FileDialog(caption, window, null);
644         dlg.filetypeIcons[".d"] = "text-d";
645         dlg.filetypeIcons["dub.json"] = "project-d";
646         dlg.filetypeIcons["package.json"] = "project-d";
647         dlg.filetypeIcons[".dlangidews"] = "project-development";
648         return dlg;
649     }
650 
651     /// override to handle specific actions
652 	override bool handleAction(const Action a) {
653         if (a) {
654             switch (a.id) {
655                 case IDEActions.FileExit:
656                     if (onCanClose())
657                         window.close();
658                     return true;
659                 case IDEActions.HelpAbout:
660                     window.showMessageBox(UIString("About DlangIDE"d), 
661                                           UIString("DLangIDE\n(C) Vadim Lopatin, 2014\nhttp://github.com/buggins/dlangide\nIDE for D programming language written in D\nUses DlangUI library for GUI"d));
662                     return true;
663                 case StandardAction.OpenUrl:
664                     platform.openURL(a.stringParam);
665                     return true;
666                 case IDEActions.FileOpen:
667                     UIString caption;
668                     caption = "Open Text File"d;
669                     FileDialog dlg = createFileDialog(caption);
670                     dlg.addFilter(FileFilterEntry(UIString("Source files"d), "*.d;*.dd;*.ddoc;*.di;*.dh;*.json;*.xml;*.ini"));
671                     dlg.addFilter(FileFilterEntry(UIString("All files"d), "*.*"));
672                     dlg.dialogResult = delegate(Dialog dlg, const Action result) {
673 						if (result.id == ACTION_OPEN.id) {
674                             string filename = result.stringParam;
675                             openSourceFile(filename);
676                         }
677                     };
678                     dlg.show();
679                     return true;
680                 case IDEActions.BuildProject:
681                 case IDEActions.BuildWorkspace:
682                     buildProject(BuildOperation.Build, cast(Project)a.objectParam);
683                     return true;
684                 case IDEActions.RebuildProject:
685                 case IDEActions.RebuildWorkspace:
686                     buildProject(BuildOperation.Rebuild, cast(Project)a.objectParam);
687                     return true;
688                 case IDEActions.CleanProject:
689                 case IDEActions.CleanWorkspace:
690                     buildProject(BuildOperation.Clean, cast(Project)a.objectParam);
691                     return true;
692                 case IDEActions.DebugStart:
693                 case IDEActions.DebugStartNoDebug:
694                 case IDEActions.DebugContinue:
695                     buildAndRunProject(cast(Project)a.objectParam);
696                     return true;
697                 case IDEActions.DebugStop:
698                     stopExecution();
699                     return true;
700                 case IDEActions.UpdateProjectDependencies:
701                     buildProject(BuildOperation.Upgrade, cast(Project)a.objectParam);
702                     return true;
703                 case IDEActions.RefreshProject:
704                     refreshWorkspace();
705                     return true;
706                 case IDEActions.WindowCloseAllDocuments:
707                     askForUnsavedEdits(delegate() {
708                         closeAllDocuments();
709                     });
710                     return true;
711                 case IDEActions.FileOpenWorkspace:
712                     UIString caption;
713                     caption = "Open Workspace or Project"d;
714                     FileDialog dlg = createFileDialog(caption);
715                     dlg.addFilter(FileFilterEntry(UIString("Workspace and project files"d), "*.dlangidews;dub.json;package.json"));
716                     dlg.dialogResult = delegate(Dialog dlg, const Action result) {
717 						if (result.id == ACTION_OPEN.id) {
718                             string filename = result.stringParam;
719                             if (filename.length)
720                                 openFileOrWorkspace(filename);
721                         }
722                     };
723                     dlg.show();
724                     return true;
725                 case IDEActions.GoToDefinition:
726                     Log.d("Trying to go to definition.");
727                     currentEditor.editorTool.goToDefinition(currentEditor(), currentEditor.caretPos);
728                     return true;
729                 case IDEActions.GetCompletionSuggestions:
730                     Log.d("Getting auto completion suggestions.");
731                     auto results = currentEditor.editorTool.getCompletions(currentEditor, currentEditor.caretPos);
732                     currentEditor.showCompletionPopup(results);
733                     return true;
734                 case IDEActions.EditPreferences:
735                     showPreferences();
736                     return true;
737                 case IDEActions.ProjectSettings:
738                     showProjectSettings();
739                     return true;
740                 case IDEActions.FindText:
741                     Log.d("Opening Search Field");
742                		import dlangide.ui.searchPanel;
743                     int searchPanelIndex = _logPanel.getTabs.tabIndex("search");
744                     SearchWidget searchPanel = null;
745                     if(searchPanelIndex == -1) {
746                         searchPanel = new SearchWidget("search", this);
747                         _logPanel.getTabs.addTab( searchPanel, "Search"d, null, true);
748                     }
749                     else {
750                         searchPanel = cast(SearchWidget) _logPanel.getTabs.tabBody(searchPanelIndex);
751                     }
752                     _logPanel.getTabs.selectTab("search");
753                     if(searchPanel !is null) { 
754                         searchPanel.focus();
755                     }
756                     return true;
757 				case IDEActions.FileNewWorkspace:
758 					createNewProject(true);
759 					return true;
760 				case IDEActions.FileNewProject:
761 					createNewProject(false);
762 					return true;
763                 case IDEActions.FileNew:
764                     addProjectItem(a.objectParam);
765                     return true;
766                 case IDEActions.ProjectFolderRemoveItem:
767                     removeProjectItem(a.objectParam);
768                     return true;
769                 case IDEActions.ProjectFolderRefresh:
770                     refreshProjectItem(a.objectParam);
771                     return true;
772                 case IDEActions.CloseWorkspace:
773                     closeWorkspace();
774                     return true;
775 				default:
776                     return super.handleAction(a);
777             }
778         }
779 		return false;
780 	}
781 
782     @property ProjectSourceFile currentEditorSourceFile() {
783         TabItem tab = _tabs.selectedTab;
784         if (tab) {
785             return cast(ProjectSourceFile)tab.objectParam;
786         }
787         return null;
788     }
789 
790     void closeWorkspace() {
791         askForUnsavedEdits(delegate() {
792             setWorkspace(null);
793             showHomeScreen();
794         });
795     }
796 
797     void refreshProjectItem(const Object obj) {
798         if (currentWorkspace is null)
799             return;
800         Project project;
801         ProjectFolder folder;
802         if (cast(Workspace)obj) {
803             Workspace ws = cast(Workspace)obj;
804             ws.refresh();
805             refreshWorkspace();
806         } else if (cast(Project)obj) {
807             project = cast(Project)obj;
808         } else if (cast(ProjectFolder)obj) {
809             folder = cast(ProjectFolder)obj;
810             project = folder.project;
811         } else if (cast(ProjectSourceFile)obj) {
812             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
813             folder = cast(ProjectFolder)srcfile.parent;
814             project = srcfile.project;
815         } else {
816             ProjectSourceFile srcfile = currentEditorSourceFile;
817             if (srcfile) {
818                 folder = cast(ProjectFolder)srcfile.parent;
819                 project = srcfile.project;
820             }
821         }
822         if (project) {
823             project.refresh();
824             refreshWorkspace();
825         }
826     }
827 
828     void removeProjectItem(const Object obj) {
829         if (currentWorkspace is null)
830             return;
831         ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
832         if (!srcfile)
833             return;
834         Project project = srcfile.project;
835         if (!project)
836             return;
837         window.showMessageBox(UIString("Remove file"d), 
838                 UIString("Do you want to remove file "d ~ srcfile.name ~ "?"), 
839                 [ACTION_YES, ACTION_NO], 
840                 1, delegate(const Action result) {
841                     if (result == StandardAction.Yes) {
842                         // save and close
843                         try {
844                             import std.file : remove;
845                             closeTab(srcfile.filename);
846                             remove(srcfile.filename);
847                             project.refresh();
848                             refreshWorkspace();
849                         } catch (Exception e) {
850                             Log.e("Error while removing file");
851                         }
852                     }
853                     // else ignore
854                     return true;
855                 });
856 
857     }
858 
859     void addProjectItem(const Object obj) {
860         if (currentWorkspace is null)
861             return;
862         Project project;
863         ProjectFolder folder;
864         if (cast(Project)obj) {
865             project = cast(Project)obj;
866         } else if (cast(ProjectFolder)obj) {
867             folder = cast(ProjectFolder)obj;
868             project = folder.project;
869         } else if (cast(ProjectSourceFile)obj) {
870             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
871             folder = cast(ProjectFolder)srcfile.parent;
872             project = srcfile.project;
873         } else {
874             ProjectSourceFile srcfile = currentEditorSourceFile;
875             if (srcfile) {
876                 folder = cast(ProjectFolder)srcfile.parent;
877                 project = srcfile.project;
878             }
879         }
880         if (project && folder && project.workspace is currentWorkspace) {
881     		NewFileDlg dlg = new NewFileDlg(this, project, folder);
882     		dlg.dialogResult = delegate(Dialog dlg, const Action result) {
883 			    if (result.id == ACTION_FILE_NEW_SOURCE_FILE.id) {
884                     FileCreationResult res = cast(FileCreationResult)result.objectParam;
885                     if (res) {
886                         //res.project.reload();
887                         res.project.refresh();
888                         refreshWorkspace();
889                         if (isSupportedSourceTextFileFormat(res.filename)) {
890                             openSourceFile(res.filename, null, true);
891                         }
892                     }
893 			    }
894 		    };
895 		    dlg.show();
896         }
897     }
898 
899 	void createNewProject(bool newWorkspace) {
900         if (currentWorkspace is null)
901             newWorkspace = true;
902 		NewProjectDlg dlg = new NewProjectDlg(this, newWorkspace, currentWorkspace);
903 		dlg.dialogResult = delegate(Dialog dlg, const Action result) {
904 			if (result.id == ACTION_FILE_NEW_PROJECT.id || result.id == ACTION_FILE_NEW_WORKSPACE.id) {
905 				//Log.d("settings after edit:\n", s.toJSON(true));
906                 ProjectCreationResult res = cast(ProjectCreationResult)result.objectParam;
907                 if (res) {
908                     // open workspace/project
909                     if (currentWorkspace is null || res.workspace !is currentWorkspace) {
910                         // open new workspace
911                         setWorkspace(res.workspace);
912                         refreshWorkspace();
913                         hideHomeScreen();
914                     } else {
915                         // project added to current workspace
916                         loadProject(res.project);
917                         refreshWorkspace();
918                         hideHomeScreen();
919                     }
920                 }
921 			}
922 		};
923 		dlg.show();
924 	}
925 
926     void showPreferences() {
927         //Log.d("settings before copy:\n", _settings.setting.toJSON(true));
928         Setting s = _settings.copySettings();
929         //Log.d("settings after copy:\n", s.toJSON(true));
930         SettingsDialog dlg = new SettingsDialog(UIString("DlangIDE settings"d), window, s, createSettingsPages());
931         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
932 			if (result.id == ACTION_APPLY.id) {
933                 //Log.d("settings after edit:\n", s.toJSON(true));
934                 _settings.applySettings(s);
935                 applySettings(_settings);
936                 _settings.save();
937             }
938         };
939         dlg.show();
940     }
941 
942     void showProjectSettings() {
943         if (!currentWorkspace)
944             return;
945         Project project = currentWorkspace.startupProject;
946         if (!project)
947             return;
948         Setting s = project.settings.copySettings();
949         SettingsDialog dlg = new SettingsDialog(UIString(project.name ~ " settings"d), window, s, createProjectSettingsPages());
950         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
951 			if (result.id == ACTION_APPLY.id) {
952                 //Log.d("settings after edit:\n", s.toJSON(true));
953                 project.settings.applySettings(s);
954                 project.settings.save();
955             }
956         };
957         dlg.show();
958     }
959 
960     void applySettings(IDESettings settings) {
961         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
962             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
963             if (ed) {
964                 applySettings(ed, settings);
965             }
966         }
967         FontManager.fontGamma = settings.fontGamma;
968         FontManager.hintingMode = settings.hintingMode;
969         FontManager.minAnitialiasedFontSize = settings.minAntialiasedFontSize;
970 	    Platform.instance.uiLanguage = settings.uiLanguage;
971 	    Platform.instance.uiTheme = settings.uiTheme;
972         requestLayout();
973     }
974 
975     void applySettings(DSourceEdit editor, IDESettings settings) {
976         editor.settings(settings).applySettings();
977     }
978 
979     private bool loadProject(Project project) {
980         if (!project.load()) {
981             _logPanel.logLine("Cannot read project " ~ project.filename);
982             window.showMessageBox(UIString("Cannot open project"d), UIString("Error occured while opening project "d ~ toUTF32(project.filename)));
983             return false;
984         }
985         _logPanel.logLine(toUTF32("Project file " ~ project.filename ~  " is opened ok"));
986         return true;
987     }
988 
989     void openFileOrWorkspace(string filename) {
990         if (filename.isWorkspaceFile) {
991             Workspace ws = new Workspace(this);
992             if (ws.load(filename)) {
993                     askForUnsavedEdits(delegate() {
994                     setWorkspace(ws);
995                     hideHomeScreen();
996                 });
997             } else {
998                 window.showMessageBox(UIString("Cannot open workspace"d), UIString("Error occured while opening workspace"d));
999                 return;
1000             }
1001         } else if (filename.isProjectFile) {
1002             _logPanel.clear();
1003             _logPanel.logLine("Trying to open project from " ~ filename);
1004             Project project = new Project(currentWorkspace, filename);
1005             string defWsFile = project.defWorkspaceFile;
1006             if (currentWorkspace) {
1007                 Project existing = currentWorkspace.findProject(project.filename);
1008                 if (existing) {
1009                     _logPanel.logLine("This project already exists in current workspace");
1010                     window.showMessageBox(UIString("Open project"d), UIString("Project is already in workspace"d));
1011                     return;
1012                 }
1013                 window.showMessageBox(UIString("Open project"d), UIString("Do you want to create new workspace or use current one?"d),
1014                                       [ACTION_ADD_TO_CURRENT_WORKSPACE, ACTION_CREATE_NEW_WORKSPACE, ACTION_CANCEL], 0, delegate(const Action result) {
1015                                           if (result.id == IDEActions.CreateNewWorkspace) {
1016                                               // new ws
1017                                               createNewWorkspaceForExistingProject(project);
1018                                               hideHomeScreen();
1019                                           } else if (result.id == IDEActions.AddToCurrentWorkspace) {
1020                                               // add to current
1021                                               currentWorkspace.addProject(project);
1022                                               loadProject(project);
1023                                               currentWorkspace.save();
1024                                               refreshWorkspace();
1025                                               hideHomeScreen();
1026                                           }
1027                                           return true;
1028                                       });
1029             } else {
1030                 // new workspace file
1031                 createNewWorkspaceForExistingProject(project);
1032             }
1033         } else {
1034             _logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
1035             window.showMessageBox(UIString("Invalid workspace file"d), UIString("This file is not a valid workspace or project file"d));
1036         }
1037     }
1038 
1039     void refreshWorkspace() {
1040         _logPanel.logLine("Refreshing workspace");
1041         _wsPanel.reloadItems();
1042         closeRemovedDocuments();
1043     }
1044 
1045     void createNewWorkspaceForExistingProject(Project project) {
1046         string defWsFile = project.defWorkspaceFile;
1047         _logPanel.logLine("Creating new workspace " ~ defWsFile);
1048         // new ws
1049         Workspace ws = new Workspace(this);
1050         ws.name = project.name;
1051         ws.description = project.description;
1052         ws.addProject(project);
1053         loadProject(project);
1054         ws.save(defWsFile);
1055         setWorkspace(ws);
1056         _logPanel.logLine("Done");
1057     }
1058 
1059     //bool loadWorkspace(string path) {
1060     //    // testing workspace loader
1061     //    Workspace ws = new Workspace();
1062     //    ws.load(path);
1063     //    setWorkspace(ws);
1064     //    //ws.save(ws.filename ~ ".bak");
1065     //    return true;
1066     //}
1067 
1068     void setWorkspace(Workspace ws) {
1069         closeAllDocuments();
1070         currentWorkspace = ws;
1071         _wsPanel.workspace = ws;
1072         requestActionsUpdate();
1073         if (ws && ws.startupProject && ws.startupProject.mainSourceFile) {
1074             openSourceFile(ws.startupProject.mainSourceFile.filename);
1075             _tabs.setFocus();
1076         }
1077     }
1078 
1079     void buildProject(BuildOperation buildOp, Project project, BuildResultListener listener = null) {
1080         if (!currentWorkspace) {
1081             _logPanel.logLine("No workspace is opened");
1082             return;
1083         }
1084         if (!project)
1085             project = currentWorkspace.startupProject;
1086         if (!project) {
1087             _logPanel.logLine("No project is opened");
1088             return;
1089         }
1090         ProjectSettings projectSettings = project.settings;
1091         string toolchain = projectSettings.getToolchain(_settings);
1092         string arch = projectSettings.getArch(_settings);
1093         bool verbose = projectSettings.buildVerbose;
1094         Builder op = new Builder(this, project, _logPanel, currentWorkspace.projectConfiguration, currentWorkspace.buildConfiguration, buildOp, 
1095                                  verbose, 
1096                                  toolchain,
1097                                  arch,
1098                                  listener);
1099         setBackgroundOperation(op);
1100     }
1101     
1102     /// updates list of available configurations
1103     void setProjectConfigurations(dstring[] items) {
1104         projectConfigurationCombo.items = items;
1105     }
1106     
1107     /// handle files dropped to application window
1108     void onFilesDropped(string[] filenames) {
1109         //Log.d("onFilesDropped(", filenames, ")");
1110         bool first = true;
1111         for (int i = 0; i < filenames.length; i++) {
1112             openSourceFile(filenames[i], null, first);
1113             first = false;
1114         }
1115     }
1116 
1117     /// return false to prevent closing
1118     bool onCanClose() {
1119         askForUnsavedEdits(delegate() {
1120             window.close();
1121         });
1122         return false;
1123     }
1124     /// called when main window is closing
1125     void onWindowClose() {
1126         Log.i("onWindowClose()");
1127         stopExecution();
1128         if (_dcdServer) {
1129             if (_dcdServer.isRunning)
1130                 _dcdServer.stop();
1131             destroy(_dcdServer);
1132             _dcdServer = null;
1133         }
1134     }
1135 }
1136