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 }