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