The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1412 lines
48KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include "../../Application/jucer_Headers.h"
  19. #include "jucer_ProjectContentComponent.h"
  20. #include "../../LiveBuildEngine/jucer_DownloadCompileEngineThread.h"
  21. #include "../../LiveBuildEngine/jucer_CompileEngineSettings.h"
  22. #include "Sidebar/jucer_TabComponents.h"
  23. #include "Sidebar/jucer_ProjectTab.h"
  24. #include "Sidebar/jucer_LiveBuildTab.h"
  25. NewFileWizard::Type* createGUIComponentWizard();
  26. //==============================================================================
  27. ProjectContentComponent::LogoComponent::LogoComponent()
  28. {
  29. if (auto svg = parseXML (BinaryData::background_logo_svg))
  30. logo = Drawable::createFromSVG (*svg);
  31. }
  32. void ProjectContentComponent::LogoComponent::paint (Graphics& g)
  33. {
  34. g.setColour (findColour (defaultTextColourId));
  35. auto r = getLocalBounds();
  36. g.setFont (15.0f);
  37. g.drawFittedText (getVersionInfo(), r.removeFromBottom (50), Justification::centredBottom, 3);
  38. logo->drawWithin (g, r.withTrimmedBottom (r.getHeight() / 4).toFloat(),
  39. RectanglePlacement (RectanglePlacement::centred), 1.0f);
  40. }
  41. String ProjectContentComponent::LogoComponent::getVersionInfo()
  42. {
  43. return SystemStats::getJUCEVersion()
  44. + newLine
  45. + ProjucerApplication::getApp().getVersionDescription();
  46. }
  47. //==============================================================================
  48. ProjectContentComponent::ContentViewport::ContentViewport (Component* content)
  49. {
  50. addAndMakeVisible (viewport);
  51. viewport.setViewedComponent (content, true);
  52. }
  53. void ProjectContentComponent::ContentViewport::resized()
  54. {
  55. viewport.setBounds (getLocalBounds());
  56. }
  57. //==============================================================================
  58. ProjectContentComponent::ProjectContentComponent()
  59. {
  60. setOpaque (true);
  61. setWantsKeyboardFocus (true);
  62. addAndMakeVisible (logoComponent);
  63. addAndMakeVisible (headerComponent);
  64. addAndMakeVisible (projectMessagesComponent);
  65. addAndMakeVisible (fileNameLabel);
  66. fileNameLabel.setJustificationType (Justification::centred);
  67. sidebarSizeConstrainer.setMinimumWidth (200);
  68. sidebarSizeConstrainer.setMaximumWidth (500);
  69. sidebarTabs.setOutline (0);
  70. sidebarTabs.getTabbedButtonBar().setMinimumTabScaleFactor (0.5);
  71. ProjucerApplication::getApp().openDocumentManager.addListener (this);
  72. isLiveBuildEnabled = getGlobalProperties().getBoolValue (Ids::liveBuildEnabled);
  73. getGlobalProperties().addChangeListener (this);
  74. liveBuildEnablementChanged (isLiveBuildEnabled);
  75. Desktop::getInstance().addFocusChangeListener (this);
  76. }
  77. ProjectContentComponent::~ProjectContentComponent()
  78. {
  79. Desktop::getInstance().removeFocusChangeListener (this);
  80. killChildProcess();
  81. getGlobalProperties().removeChangeListener (this);
  82. ProjucerApplication::getApp().openDocumentManager.removeListener (this);
  83. setProject (nullptr);
  84. removeChildComponent (&bubbleMessage);
  85. }
  86. void ProjectContentComponent::paint (Graphics& g)
  87. {
  88. g.fillAll (findColour (backgroundColourId));
  89. }
  90. void ProjectContentComponent::resized()
  91. {
  92. auto r = getLocalBounds();
  93. r.removeFromRight (10);
  94. r.removeFromLeft (15);
  95. r.removeFromTop (5);
  96. projectMessagesComponent.setBounds (r.removeFromBottom (40).withWidth (100).reduced (0, 5));
  97. headerComponent.setBounds (r.removeFromTop (40));
  98. r.removeFromTop (10);
  99. auto sidebarArea = r.removeFromLeft (sidebarTabs.getWidth() != 0 ? sidebarTabs.getWidth()
  100. : r.getWidth() / 4);
  101. if (sidebarTabs.isVisible())
  102. sidebarTabs.setBounds (sidebarArea);
  103. if (resizerBar != nullptr)
  104. resizerBar->setBounds (r.withWidth (4));
  105. headerComponent.sidebarTabsWidthChanged (sidebarTabs.getWidth());
  106. if (contentView != nullptr)
  107. {
  108. fileNameLabel.setBounds (r.removeFromTop (15));
  109. contentView->setBounds (r);
  110. }
  111. logoComponent.setBounds (r.reduced (r.getWidth() / 6, r.getHeight() / 6));
  112. }
  113. void ProjectContentComponent::lookAndFeelChanged()
  114. {
  115. repaint();
  116. if (translationTool != nullptr)
  117. translationTool->repaint();
  118. }
  119. void ProjectContentComponent::childBoundsChanged (Component* child)
  120. {
  121. if (child == &sidebarTabs)
  122. resized();
  123. }
  124. void ProjectContentComponent::setProject (Project* newProject)
  125. {
  126. if (project != newProject)
  127. {
  128. lastCrashMessage = {};
  129. killChildProcess();
  130. if (project != nullptr)
  131. project->removeChangeListener (this);
  132. contentView.reset();
  133. resizerBar.reset();
  134. deleteProjectTabs();
  135. project = newProject;
  136. rebuildProjectUI();
  137. }
  138. }
  139. //==============================================================================
  140. static LiveBuildTab* findBuildTab (const TabbedComponent& tabs)
  141. {
  142. return dynamic_cast<LiveBuildTab*> (tabs.getTabContentComponent (1));
  143. }
  144. bool ProjectContentComponent::isBuildTabEnabled() const
  145. {
  146. auto bt = findBuildTab (sidebarTabs);
  147. return bt != nullptr && bt->isEnabled;
  148. }
  149. void ProjectContentComponent::createProjectTabs()
  150. {
  151. jassert (project != nullptr);
  152. auto tabColour = Colours::transparentBlack;
  153. sidebarTabs.addTab (getProjectTabName(), tabColour, new ProjectTab (project), true);
  154. if (isLiveBuildEnabled)
  155. {
  156. CompileEngineChildProcess::Ptr childProc (getChildProcess());
  157. sidebarTabs.addTab (getBuildTabName(), tabColour, new LiveBuildTab (childProc, lastCrashMessage), true);
  158. if (childProc != nullptr)
  159. {
  160. childProc->crashHandler = [this] (const String& m) { this->handleCrash (m); };
  161. sidebarTabs.getTabbedButtonBar().getTabButton (1)->setExtraComponent (new BuildStatusTabComp (childProc->errorList,
  162. childProc->activityList),
  163. TabBarButton::afterText);
  164. }
  165. }
  166. }
  167. void ProjectContentComponent::deleteProjectTabs()
  168. {
  169. if (project != nullptr && sidebarTabs.getNumTabs() > 0)
  170. {
  171. auto& settings = project->getStoredProperties();
  172. if (sidebarTabs.getWidth() > 0)
  173. settings.setValue ("projectPanelWidth", sidebarTabs.getWidth());
  174. settings.setValue ("lastViewedTabIndex", sidebarTabs.getCurrentTabIndex());
  175. for (int i = 0; i < 3; ++i)
  176. settings.setValue ("projectTabPanelHeight" + String (i),
  177. getProjectTab()->getPanelHeightProportion (i));
  178. }
  179. sidebarTabs.clearTabs();
  180. }
  181. void ProjectContentComponent::rebuildProjectUI()
  182. {
  183. deleteProjectTabs();
  184. if (project != nullptr)
  185. {
  186. addAndMakeVisible (sidebarTabs);
  187. createProjectTabs();
  188. //==============================================================================
  189. auto& settings = project->getStoredProperties();
  190. auto lastTreeWidth = settings.getValue ("projectPanelWidth").getIntValue();
  191. if (lastTreeWidth < 150)
  192. lastTreeWidth = 240;
  193. sidebarTabs.setBounds (0, 0, lastTreeWidth, getHeight());
  194. auto lastTabIndex = settings.getValue ("lastViewedTabIndex", "0").getIntValue();
  195. if (lastTabIndex >= sidebarTabs.getNumTabs())
  196. lastTabIndex = 0;
  197. sidebarTabs.setCurrentTabIndex (lastTabIndex);
  198. auto* projectTab = getProjectTab();
  199. for (int i = 2; i >= 0; --i)
  200. projectTab->setPanelHeightProportion (i, settings.getValue ("projectTabPanelHeight" + String (i), "1")
  201. .getFloatValue());
  202. //==============================================================================
  203. resizerBar.reset (new ResizableEdgeComponent (&sidebarTabs, &sidebarSizeConstrainer,
  204. ResizableEdgeComponent::rightEdge));
  205. addAndMakeVisible (resizerBar.get());
  206. resizerBar->setAlwaysOnTop (true);
  207. project->addChangeListener (this);
  208. updateMissingFileStatuses();
  209. headerComponent.setVisible (true);
  210. headerComponent.setCurrentProject (project);
  211. projectMessagesComponent.setVisible (true);
  212. }
  213. else
  214. {
  215. sidebarTabs.setVisible (false);
  216. headerComponent.setVisible (false);
  217. projectMessagesComponent.setVisible (false);
  218. }
  219. projectMessagesComponent.setProject (project);
  220. resized();
  221. }
  222. void ProjectContentComponent::saveTreeViewState()
  223. {
  224. }
  225. void ProjectContentComponent::saveOpenDocumentList()
  226. {
  227. if (project != nullptr)
  228. {
  229. std::unique_ptr<XmlElement> xml (recentDocumentList.createXML());
  230. if (xml != nullptr)
  231. project->getStoredProperties().setValue ("lastDocs", xml.get());
  232. }
  233. }
  234. void ProjectContentComponent::reloadLastOpenDocuments()
  235. {
  236. if (project != nullptr)
  237. {
  238. if (auto xml = project->getStoredProperties().getXmlValue ("lastDocs"))
  239. {
  240. recentDocumentList.restoreFromXML (*project, *xml);
  241. showDocument (recentDocumentList.getCurrentDocument(), true);
  242. }
  243. }
  244. }
  245. bool ProjectContentComponent::documentAboutToClose (OpenDocumentManager::Document* document)
  246. {
  247. hideDocument (document);
  248. return true;
  249. }
  250. void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster* broadcaster)
  251. {
  252. if (broadcaster == project)
  253. {
  254. updateMissingFileStatuses();
  255. }
  256. else if (broadcaster == &getGlobalProperties())
  257. {
  258. auto isEnabled = ProjucerApplication::getApp().isLiveBuildEnabled();
  259. if (isLiveBuildEnabled != isEnabled)
  260. liveBuildEnablementChanged (isEnabled);
  261. }
  262. }
  263. void ProjectContentComponent::refreshProjectTreeFileStatuses()
  264. {
  265. if (auto* projectTab = getProjectTab())
  266. if (auto* fileTree = projectTab->getFileTreePanel())
  267. fileTree->repaint();
  268. }
  269. void ProjectContentComponent::updateMissingFileStatuses()
  270. {
  271. if (auto* pTab = getProjectTab())
  272. if (auto* tree = pTab->getFileTreePanel())
  273. tree->updateMissingFileStatuses();
  274. }
  275. bool ProjectContentComponent::showEditorForFile (const File& f, bool grabFocus)
  276. {
  277. if (getCurrentFile() == f
  278. || showDocument (ProjucerApplication::getApp().openDocumentManager.openFile (project, f), grabFocus))
  279. {
  280. fileNameLabel.setText (f.getFileName(), dontSendNotification);
  281. return true;
  282. }
  283. return false;
  284. }
  285. bool ProjectContentComponent::hasFileInRecentList (const File& f) const
  286. {
  287. return recentDocumentList.contains (f);
  288. }
  289. File ProjectContentComponent::getCurrentFile() const
  290. {
  291. return currentDocument != nullptr ? currentDocument->getFile()
  292. : File();
  293. }
  294. bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  295. {
  296. if (doc == nullptr)
  297. return false;
  298. if (doc->hasFileBeenModifiedExternally())
  299. doc->reloadFromFile();
  300. if (doc == getCurrentDocument() && contentView != nullptr)
  301. {
  302. if (grabFocus)
  303. contentView->grabKeyboardFocus();
  304. return true;
  305. }
  306. recentDocumentList.newDocumentOpened (doc);
  307. auto opened = setEditorComponent (doc->createEditor(), doc);
  308. if (opened && grabFocus && isShowing())
  309. contentView->grabKeyboardFocus();
  310. return opened;
  311. }
  312. void ProjectContentComponent::hideEditor()
  313. {
  314. currentDocument = nullptr;
  315. contentView.reset();
  316. fileNameLabel.setVisible (false);
  317. ProjucerApplication::getCommandManager().commandStatusChanged();
  318. resized();
  319. }
  320. void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
  321. {
  322. if (doc == currentDocument)
  323. {
  324. if (auto* replacement = recentDocumentList.getClosestPreviousDocOtherThan (doc))
  325. showDocument (replacement, true);
  326. else
  327. hideEditor();
  328. }
  329. }
  330. bool ProjectContentComponent::setEditorComponent (Component* editor,
  331. OpenDocumentManager::Document* doc)
  332. {
  333. if (editor != nullptr)
  334. {
  335. contentView.reset();
  336. if (doc == nullptr)
  337. {
  338. auto* viewport = new ContentViewport (editor);
  339. contentView.reset (viewport);
  340. currentDocument = nullptr;
  341. fileNameLabel.setVisible (false);
  342. addAndMakeVisible (viewport);
  343. }
  344. else
  345. {
  346. contentView.reset (editor);
  347. currentDocument = doc;
  348. fileNameLabel.setText (doc->getFile().getFileName(), dontSendNotification);
  349. fileNameLabel.setVisible (true);
  350. addAndMakeVisible (editor);
  351. }
  352. resized();
  353. ProjucerApplication::getCommandManager().commandStatusChanged();
  354. return true;
  355. }
  356. return false;
  357. }
  358. Component* ProjectContentComponent::getEditorComponentContent() const
  359. {
  360. if (contentView != nullptr)
  361. if (auto* vp = dynamic_cast<ContentViewport*> (contentView.get()))
  362. return vp->viewport.getViewedComponent();
  363. return nullptr;
  364. }
  365. void ProjectContentComponent::closeDocument()
  366. {
  367. if (currentDocument != nullptr)
  368. ProjucerApplication::getApp().openDocumentManager
  369. .closeDocument (currentDocument, OpenDocumentManager::SaveIfNeeded::yes);
  370. else if (contentView != nullptr)
  371. if (! goToPreviousFile())
  372. hideEditor();
  373. }
  374. static void showSaveWarning (OpenDocumentManager::Document* currentDocument)
  375. {
  376. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  377. TRANS("Save failed!"),
  378. TRANS("Couldn't save the file:")
  379. + "\n" + currentDocument->getFile().getFullPathName());
  380. }
  381. void ProjectContentComponent::saveDocument()
  382. {
  383. if (currentDocument != nullptr)
  384. {
  385. if (! currentDocument->save())
  386. showSaveWarning (currentDocument);
  387. refreshProjectTreeFileStatuses();
  388. }
  389. else
  390. {
  391. saveProject();
  392. }
  393. }
  394. void ProjectContentComponent::saveAs()
  395. {
  396. if (currentDocument != nullptr)
  397. {
  398. if (! currentDocument->saveAs())
  399. showSaveWarning (currentDocument);
  400. refreshProjectTreeFileStatuses();
  401. }
  402. }
  403. bool ProjectContentComponent::goToPreviousFile()
  404. {
  405. auto* doc = recentDocumentList.getCurrentDocument();
  406. if (doc == nullptr || doc == getCurrentDocument())
  407. doc = recentDocumentList.getPrevious();
  408. return showDocument (doc, true);
  409. }
  410. bool ProjectContentComponent::goToNextFile()
  411. {
  412. return showDocument (recentDocumentList.getNext(), true);
  413. }
  414. bool ProjectContentComponent::canGoToCounterpart() const
  415. {
  416. return currentDocument != nullptr
  417. && currentDocument->getCounterpartFile().exists();
  418. }
  419. bool ProjectContentComponent::goToCounterpart()
  420. {
  421. if (currentDocument != nullptr)
  422. {
  423. auto file = currentDocument->getCounterpartFile();
  424. if (file.exists())
  425. return showEditorForFile (file, true);
  426. }
  427. return false;
  428. }
  429. bool ProjectContentComponent::saveProject()
  430. {
  431. if (project != nullptr)
  432. return (project->save (true, true) == FileBasedDocument::savedOk);
  433. return false;
  434. }
  435. void ProjectContentComponent::closeProject()
  436. {
  437. if (auto* mw = findParentComponentOfClass<MainWindow>())
  438. mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes);
  439. }
  440. void ProjectContentComponent::showProjectSettings()
  441. {
  442. setEditorComponent (new ProjectSettingsComponent (*project), nullptr);
  443. }
  444. void ProjectContentComponent::showCurrentExporterSettings()
  445. {
  446. if (auto selected = headerComponent.getSelectedExporter())
  447. showExporterSettings (selected->getUniqueName());
  448. }
  449. void ProjectContentComponent::showExporterSettings (const String& exporterName)
  450. {
  451. if (exporterName.isEmpty())
  452. return;
  453. showExportersPanel();
  454. if (auto* exportersPanel = getProjectTab()->getExportersTreePanel())
  455. {
  456. if (auto* exporters = dynamic_cast<TreeItemTypes::ExportersTreeRoot*> (exportersPanel->rootItem.get()))
  457. {
  458. for (auto i = exporters->getNumSubItems(); i >= 0; --i)
  459. {
  460. if (auto* e = dynamic_cast<TreeItemTypes::ExporterItem*> (exporters->getSubItem (i)))
  461. {
  462. if (e->getDisplayName() == exporterName)
  463. {
  464. if (e->isSelected())
  465. e->setSelected (false, true);
  466. e->setSelected (true, true);
  467. }
  468. }
  469. }
  470. }
  471. }
  472. }
  473. void ProjectContentComponent::showModule (const String& moduleID)
  474. {
  475. showModulesPanel();
  476. if (auto* modsPanel = getProjectTab()->getModuleTreePanel())
  477. {
  478. if (auto* mods = dynamic_cast<TreeItemTypes::EnabledModulesItem*> (modsPanel->rootItem.get()))
  479. {
  480. for (auto i = mods->getNumSubItems(); --i >= 0;)
  481. {
  482. if (auto* m = dynamic_cast<TreeItemTypes::ModuleItem*> (mods->getSubItem (i)))
  483. {
  484. if (m->moduleID == moduleID)
  485. {
  486. if (m->isSelected())
  487. m->setSelected (false, true);
  488. m->setSelected (true, true);
  489. }
  490. }
  491. }
  492. }
  493. }
  494. }
  495. void ProjectContentComponent::showLiveBuildSettings()
  496. {
  497. setEditorComponent (new LiveBuildSettingsComponent (*project), nullptr);
  498. }
  499. StringArray ProjectContentComponent::getExportersWhichCanLaunch() const
  500. {
  501. StringArray s;
  502. if (project != nullptr)
  503. for (Project::ExporterIterator exporter (*project); exporter.next();)
  504. if (exporter->canLaunchProject())
  505. s.add (exporter->getUniqueName());
  506. return s;
  507. }
  508. void ProjectContentComponent::openInSelectedIDE (bool saveFirst)
  509. {
  510. if (project != nullptr)
  511. if (auto selectedExporter = headerComponent.getSelectedExporter())
  512. project->openProjectInIDE (*selectedExporter, saveFirst);
  513. }
  514. void ProjectContentComponent::showNewExporterMenu()
  515. {
  516. if (project != nullptr)
  517. {
  518. PopupMenu menu;
  519. menu.addSectionHeader ("Create a new export target:");
  520. SafePointer<ProjectContentComponent> safeThis (this);
  521. for (auto& exporterInfo : ProjectExporter::getExporterTypeInfos())
  522. {
  523. PopupMenu::Item item;
  524. item.itemID = -1;
  525. item.text = exporterInfo.displayName;
  526. item.image = [exporterInfo]
  527. {
  528. auto drawableImage = std::make_unique<DrawableImage>();
  529. drawableImage->setImage (exporterInfo.icon);
  530. return drawableImage;
  531. }();
  532. item.action = [safeThis, exporterInfo]
  533. {
  534. if (safeThis != nullptr)
  535. if (auto* p = safeThis->getProject())
  536. p->addNewExporter (exporterInfo.identifier);
  537. };
  538. menu.addItem (item);
  539. }
  540. menu.showMenuAsync ({});
  541. }
  542. }
  543. void ProjectContentComponent::deleteSelectedTreeItems()
  544. {
  545. if (auto* tree = getProjectTab()->getTreeWithSelectedItems())
  546. tree->deleteSelectedItems();
  547. }
  548. void ProjectContentComponent::showBubbleMessage (Rectangle<int> pos, const String& text)
  549. {
  550. addChildComponent (bubbleMessage);
  551. bubbleMessage.setColour (BubbleComponent::backgroundColourId, Colours::white.withAlpha (0.7f));
  552. bubbleMessage.setColour (BubbleComponent::outlineColourId, Colours::black.withAlpha (0.8f));
  553. bubbleMessage.setAlwaysOnTop (true);
  554. bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false);
  555. }
  556. //==============================================================================
  557. void ProjectContentComponent::showTranslationTool()
  558. {
  559. if (translationTool != nullptr)
  560. {
  561. translationTool->toFront (true);
  562. }
  563. else if (project != nullptr)
  564. {
  565. new FloatingToolWindow ("Translation File Builder",
  566. "transToolWindowPos",
  567. new TranslationToolComponent(),
  568. translationTool, true,
  569. 600, 700,
  570. 600, 400, 10000, 10000);
  571. }
  572. }
  573. //==============================================================================
  574. struct AsyncCommandRetrier : public Timer
  575. {
  576. AsyncCommandRetrier (const ApplicationCommandTarget::InvocationInfo& i) : info (i)
  577. {
  578. info.originatingComponent = nullptr;
  579. startTimer (500);
  580. }
  581. void timerCallback() override
  582. {
  583. stopTimer();
  584. ProjucerApplication::getCommandManager().invoke (info, true);
  585. delete this;
  586. }
  587. ApplicationCommandTarget::InvocationInfo info;
  588. JUCE_DECLARE_NON_COPYABLE (AsyncCommandRetrier)
  589. };
  590. static bool reinvokeCommandAfterCancellingModalComps (const ApplicationCommandTarget::InvocationInfo& info)
  591. {
  592. if (ModalComponentManager::getInstance()->cancelAllModalComponents())
  593. {
  594. new AsyncCommandRetrier (info);
  595. return true;
  596. }
  597. return false;
  598. }
  599. //==============================================================================
  600. ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
  601. {
  602. return findFirstTargetParentComponent();
  603. }
  604. void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
  605. {
  606. commands.addArray ({ CommandIDs::saveProject,
  607. CommandIDs::closeProject,
  608. CommandIDs::saveDocument,
  609. CommandIDs::saveDocumentAs,
  610. CommandIDs::closeDocument,
  611. CommandIDs::goToPreviousDoc,
  612. CommandIDs::goToNextDoc,
  613. CommandIDs::goToCounterpart,
  614. CommandIDs::showProjectSettings,
  615. CommandIDs::showProjectTab,
  616. CommandIDs::showBuildTab,
  617. CommandIDs::showFileExplorerPanel,
  618. CommandIDs::showModulesPanel,
  619. CommandIDs::showExportersPanel,
  620. CommandIDs::showExporterSettings,
  621. CommandIDs::openInIDE,
  622. CommandIDs::saveAndOpenInIDE,
  623. CommandIDs::createNewExporter,
  624. CommandIDs::deleteSelectedItem,
  625. CommandIDs::showTranslationTool,
  626. CommandIDs::cleanAll,
  627. CommandIDs::toggleBuildEnabled,
  628. CommandIDs::buildNow,
  629. CommandIDs::toggleContinuousBuild,
  630. CommandIDs::launchApp,
  631. CommandIDs::killApp,
  632. CommandIDs::reinstantiateComp,
  633. CommandIDs::showWarnings,
  634. CommandIDs::nextError,
  635. CommandIDs::prevError,
  636. CommandIDs::addNewGUIFile });
  637. }
  638. void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  639. {
  640. String documentName;
  641. if (currentDocument != nullptr)
  642. documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
  643. #if JUCE_MAC
  644. auto cmdCtrl = (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier);
  645. #else
  646. auto cmdCtrl = (ModifierKeys::ctrlModifier | ModifierKeys::altModifier);
  647. #endif
  648. switch (commandID)
  649. {
  650. case CommandIDs::saveProject:
  651. result.setInfo ("Save Project",
  652. "Saves the current project",
  653. CommandCategories::general, 0);
  654. result.setActive (project != nullptr && ! project->isSaveAndExportDisabled() && ! project->isCurrentlySaving());
  655. result.defaultKeypresses.add ({ 'p', ModifierKeys::commandModifier, 0 });
  656. break;
  657. case CommandIDs::closeProject:
  658. result.setInfo ("Close Project",
  659. "Closes the current project",
  660. CommandCategories::general, 0);
  661. result.setActive (project != nullptr);
  662. break;
  663. case CommandIDs::saveDocument:
  664. result.setInfo ("Save" + documentName,
  665. "Saves the current document",
  666. CommandCategories::general, 0);
  667. result.setActive (currentDocument != nullptr || (project != nullptr && ! project->isCurrentlySaving()));
  668. result.defaultKeypresses.add ({ 's', ModifierKeys::commandModifier, 0 });
  669. break;
  670. case CommandIDs::saveDocumentAs:
  671. result.setInfo ("Save As...",
  672. "Saves the current document to a new location",
  673. CommandCategories::general, 0);
  674. result.setActive (currentDocument != nullptr);
  675. result.defaultKeypresses.add ({ 's', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
  676. break;
  677. case CommandIDs::closeDocument:
  678. result.setInfo ("Close" + documentName,
  679. "Closes the current document",
  680. CommandCategories::general, 0);
  681. result.setActive (contentView != nullptr);
  682. result.defaultKeypresses.add ({ 'w', cmdCtrl, 0 });
  683. break;
  684. case CommandIDs::goToPreviousDoc:
  685. result.setInfo ("Previous Document",
  686. "Go to previous document",
  687. CommandCategories::general, 0);
  688. result.setActive (recentDocumentList.canGoToPrevious());
  689. result.defaultKeypresses.add ({ KeyPress::leftKey, cmdCtrl, 0 });
  690. break;
  691. case CommandIDs::goToNextDoc:
  692. result.setInfo ("Next Document",
  693. "Go to next document",
  694. CommandCategories::general, 0);
  695. result.setActive (recentDocumentList.canGoToNext());
  696. result.defaultKeypresses.add ({ KeyPress::rightKey, cmdCtrl, 0 });
  697. break;
  698. case CommandIDs::goToCounterpart:
  699. result.setInfo ("Open Counterpart File",
  700. "Open corresponding header or cpp file",
  701. CommandCategories::general, 0);
  702. result.setActive (canGoToCounterpart());
  703. result.defaultKeypresses.add ({ KeyPress::upKey, cmdCtrl, 0 });
  704. break;
  705. case CommandIDs::showProjectSettings:
  706. result.setInfo ("Show Project Settings",
  707. "Shows the main project options page",
  708. CommandCategories::general, 0);
  709. result.setActive (project != nullptr);
  710. result.defaultKeypresses.add ({ 'x', cmdCtrl, 0 });
  711. break;
  712. case CommandIDs::showProjectTab:
  713. result.setInfo ("Show Project Tab",
  714. "Shows the tab containing the project information",
  715. CommandCategories::general, 0);
  716. result.setActive (project != nullptr);
  717. result.defaultKeypresses.add ({ 'p', cmdCtrl, 0 });
  718. break;
  719. case CommandIDs::showBuildTab:
  720. result.setInfo ("Show Build Tab",
  721. "Shows the tab containing the build panel",
  722. CommandCategories::general, 0);
  723. result.setActive (project != nullptr && isLiveBuildEnabled);
  724. result.defaultKeypresses.add ({ 'b', cmdCtrl, 0 });
  725. break;
  726. case CommandIDs::showFileExplorerPanel:
  727. result.setInfo ("Show File Explorer Panel",
  728. "Shows the panel containing the tree of files for this project",
  729. CommandCategories::general, 0);
  730. result.setActive (project != nullptr);
  731. result.defaultKeypresses.add ({ 'f', cmdCtrl, 0 });
  732. break;
  733. case CommandIDs::showModulesPanel:
  734. result.setInfo ("Show Modules Panel",
  735. "Shows the panel containing the project's list of modules",
  736. CommandCategories::general, 0);
  737. result.setActive (project != nullptr);
  738. result.defaultKeypresses.add ({ 'm', cmdCtrl, 0 });
  739. break;
  740. case CommandIDs::showExportersPanel:
  741. result.setInfo ("Show Exporters Panel",
  742. "Shows the panel containing the project's list of exporters",
  743. CommandCategories::general, 0);
  744. result.setActive (project != nullptr);
  745. result.defaultKeypresses.add ({ 'e', cmdCtrl, 0 });
  746. break;
  747. case CommandIDs::showExporterSettings:
  748. result.setInfo ("Show Exporter Settings",
  749. "Shows the settings page for the currently selected exporter",
  750. CommandCategories::general, 0);
  751. result.setActive (project != nullptr);
  752. result.defaultKeypresses.add ({ 'e', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
  753. break;
  754. case CommandIDs::openInIDE:
  755. result.setInfo ("Open in IDE...",
  756. "Launches the project in an external IDE",
  757. CommandCategories::general, 0);
  758. result.setActive (ProjectExporter::canProjectBeLaunched (project) && ! project->isSaveAndExportDisabled());
  759. break;
  760. case CommandIDs::saveAndOpenInIDE:
  761. result.setInfo ("Save Project and Open in IDE...",
  762. "Saves the project and launches it in an external IDE",
  763. CommandCategories::general, 0);
  764. result.setActive (ProjectExporter::canProjectBeLaunched (project) && ! project->isSaveAndExportDisabled() && ! project->isCurrentlySaving());
  765. result.defaultKeypresses.add ({ 'l', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
  766. break;
  767. case CommandIDs::createNewExporter:
  768. result.setInfo ("Create New Exporter...",
  769. "Creates a new exporter for a compiler type",
  770. CommandCategories::general, 0);
  771. result.setActive (project != nullptr);
  772. break;
  773. case CommandIDs::deleteSelectedItem:
  774. result.setInfo ("Delete Selected File",
  775. String(),
  776. CommandCategories::general, 0);
  777. result.defaultKeypresses.add ({ KeyPress::deleteKey, 0, 0 });
  778. result.defaultKeypresses.add ({ KeyPress::backspaceKey, 0, 0 });
  779. result.setActive (sidebarTabs.getCurrentTabIndex() == 0);
  780. break;
  781. case CommandIDs::showTranslationTool:
  782. result.setInfo ("Translation File Builder",
  783. "Shows the translation file helper tool",
  784. CommandCategories::general, 0);
  785. break;
  786. case CommandIDs::cleanAll:
  787. result.setInfo ("Clean All",
  788. "Cleans all intermediate files",
  789. CommandCategories::general, 0);
  790. result.defaultKeypresses.add ({ 'k', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
  791. result.setActive (project != nullptr);
  792. break;
  793. case CommandIDs::toggleBuildEnabled:
  794. result.setInfo ("Enable Compilation",
  795. "Enables/disables the compiler",
  796. CommandCategories::general, 0);
  797. result.defaultKeypresses.add ({ 'b', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
  798. result.setActive (project != nullptr);
  799. result.setTicked (childProcess != nullptr);
  800. break;
  801. case CommandIDs::buildNow:
  802. result.setInfo ("Build Now",
  803. "Recompiles any out-of-date files and updates the JIT engine",
  804. CommandCategories::general, 0);
  805. result.defaultKeypresses.add ({ 'b', ModifierKeys::commandModifier, 0 });
  806. result.setActive (childProcess != nullptr);
  807. break;
  808. case CommandIDs::toggleContinuousBuild:
  809. result.setInfo ("Enable Continuous Recompiling",
  810. "Continuously recompiles any changes made in code editors",
  811. CommandCategories::general, 0);
  812. result.setActive (childProcess != nullptr);
  813. result.setTicked (isContinuousRebuildEnabled());
  814. break;
  815. case CommandIDs::launchApp:
  816. result.setInfo ("Launch Application",
  817. "Invokes the app's main() function",
  818. CommandCategories::general, 0);
  819. result.defaultKeypresses.add ({ 'r', ModifierKeys::commandModifier, 0 });
  820. result.setActive (childProcess != nullptr && childProcess->canLaunchApp());
  821. break;
  822. case CommandIDs::killApp:
  823. result.setInfo ("Stop Application",
  824. "Kills the app if it's running",
  825. CommandCategories::general, 0);
  826. result.defaultKeypresses.add ({ '.', ModifierKeys::commandModifier, 0 });
  827. result.setActive (childProcess != nullptr && childProcess->canKillApp());
  828. break;
  829. case CommandIDs::reinstantiateComp:
  830. result.setInfo ("Re-instantiate Components",
  831. "Re-loads any component editors that are open",
  832. CommandCategories::general, 0);
  833. result.defaultKeypresses.add ({ 'r', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
  834. result.setActive (childProcess != nullptr);
  835. break;
  836. case CommandIDs::showWarnings:
  837. result.setInfo ("Show Warnings",
  838. "Shows or hides compilation warnings",
  839. CommandCategories::general, 0);
  840. result.setActive (project != nullptr);
  841. result.setTicked (areWarningsEnabled());
  842. break;
  843. case CommandIDs::nextError:
  844. result.setInfo ("Highlight next error",
  845. "Jumps to the next error or warning",
  846. CommandCategories::general, 0);
  847. result.defaultKeypresses.add ({ '\'', ModifierKeys::commandModifier, 0 });
  848. result.setActive (childProcess != nullptr && ! childProcess->errorList.isEmpty());
  849. break;
  850. case CommandIDs::prevError:
  851. result.setInfo ("Highlight previous error",
  852. "Jumps to the last error or warning",
  853. CommandCategories::general, 0);
  854. result.defaultKeypresses.add ({ '\"', ModifierKeys::commandModifier, 0 });
  855. result.setActive (childProcess != nullptr && ! childProcess->errorList.isEmpty());
  856. break;
  857. case CommandIDs::addNewGUIFile:
  858. result.setInfo ("Add new GUI Component...",
  859. "Adds a new GUI Component file to the project",
  860. CommandCategories::general,
  861. (! ProjucerApplication::getApp().isGUIEditorEnabled() ? ApplicationCommandInfo::isDisabled : 0));
  862. break;
  863. default:
  864. break;
  865. }
  866. }
  867. bool ProjectContentComponent::perform (const InvocationInfo& info)
  868. {
  869. // don't allow the project to be saved again if it's currently saving
  870. if (isSaveCommand (info.commandID) && (project != nullptr && project->isCurrentlySaving()))
  871. return false;
  872. switch (info.commandID)
  873. {
  874. case CommandIDs::saveProject:
  875. case CommandIDs::closeProject:
  876. case CommandIDs::saveDocument:
  877. case CommandIDs::saveDocumentAs:
  878. case CommandIDs::closeDocument:
  879. case CommandIDs::goToPreviousDoc:
  880. case CommandIDs::goToNextDoc:
  881. case CommandIDs::goToCounterpart:
  882. case CommandIDs::saveAndOpenInIDE:
  883. if (reinvokeCommandAfterCancellingModalComps (info))
  884. {
  885. grabKeyboardFocus(); // to force any open labels to close their text editors
  886. return true;
  887. }
  888. break;
  889. default:
  890. break;
  891. }
  892. if (isCurrentlyBlockedByAnotherModalComponent())
  893. return false;
  894. switch (info.commandID)
  895. {
  896. case CommandIDs::saveProject: saveProject(); break;
  897. case CommandIDs::closeProject: closeProject(); break;
  898. case CommandIDs::saveDocument: saveDocument(); break;
  899. case CommandIDs::saveDocumentAs: saveAs(); break;
  900. case CommandIDs::closeDocument: closeDocument(); break;
  901. case CommandIDs::goToPreviousDoc: goToPreviousFile(); break;
  902. case CommandIDs::goToNextDoc: goToNextFile(); break;
  903. case CommandIDs::goToCounterpart: goToCounterpart(); break;
  904. case CommandIDs::showProjectSettings: showProjectSettings(); break;
  905. case CommandIDs::showProjectTab: showProjectTab(); break;
  906. case CommandIDs::showBuildTab: showBuildTab(); break;
  907. case CommandIDs::showFileExplorerPanel: showFilesPanel(); break;
  908. case CommandIDs::showModulesPanel: showModulesPanel(); break;
  909. case CommandIDs::showExportersPanel: showExportersPanel(); break;
  910. case CommandIDs::showExporterSettings: showCurrentExporterSettings(); break;
  911. case CommandIDs::openInIDE: openInSelectedIDE (false); break;
  912. case CommandIDs::saveAndOpenInIDE: openInSelectedIDE (true); break;
  913. case CommandIDs::createNewExporter: showNewExporterMenu(); break;
  914. case CommandIDs::deleteSelectedItem: deleteSelectedTreeItems(); break;
  915. case CommandIDs::showTranslationTool: showTranslationTool(); break;
  916. case CommandIDs::cleanAll: cleanAll(); break;
  917. case CommandIDs::toggleBuildEnabled: setBuildEnabled (! isBuildEnabled()); break;
  918. case CommandIDs::buildNow: rebuildNow(); break;
  919. case CommandIDs::toggleContinuousBuild: setContinuousRebuildEnabled (! isContinuousRebuildEnabled()); break;
  920. case CommandIDs::launchApp: launchApp(); break;
  921. case CommandIDs::killApp: killApp(); break;
  922. case CommandIDs::reinstantiateComp: reinstantiateLivePreviewWindows(); break;
  923. case CommandIDs::showWarnings: toggleWarnings(); break;
  924. case CommandIDs::nextError: showNextError(); break;
  925. case CommandIDs::prevError: showPreviousError(); break;
  926. case CommandIDs::addNewGUIFile: addNewGUIFile(); break;
  927. default:
  928. return false;
  929. }
  930. return true;
  931. }
  932. bool ProjectContentComponent::isSaveCommand (const CommandID id)
  933. {
  934. return (id == CommandIDs::saveProject || id == CommandIDs::saveDocument || id == CommandIDs::saveAndOpenInIDE);
  935. }
  936. void ProjectContentComponent::getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
  937. OwnedArray<Project::Item>& selectedNodes)
  938. {
  939. TreeItemTypes::FileTreeItemBase::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  940. }
  941. //==============================================================================
  942. void ProjectContentComponent::killChildProcess()
  943. {
  944. if (childProcess != nullptr)
  945. {
  946. deleteProjectTabs();
  947. childProcess = nullptr;
  948. ProjucerApplication::getApp().childProcessCache->removeOrphans();
  949. }
  950. }
  951. void ProjectContentComponent::setBuildEnabled (bool isEnabled, bool displayError)
  952. {
  953. if (project != nullptr && isEnabled != isBuildEnabled())
  954. {
  955. if (! displayError)
  956. lastCrashMessage = {};
  957. project->getCompileEngineSettings().setBuildEnabled (isEnabled);
  958. killChildProcess();
  959. refreshTabsIfBuildStatusChanged();
  960. }
  961. }
  962. void ProjectContentComponent::cleanAll()
  963. {
  964. lastCrashMessage = {};
  965. if (childProcess != nullptr)
  966. childProcess->cleanAll();
  967. else if (auto* p = getProject())
  968. CompileEngineChildProcess::cleanAllCachedFilesForProject (*p);
  969. }
  970. void ProjectContentComponent::handleCrash (const String& message)
  971. {
  972. lastCrashMessage = message.isEmpty() ? TRANS("JIT process stopped responding!")
  973. : (TRANS("JIT process crashed!") + ":\n\n" + message);
  974. if (project != nullptr)
  975. {
  976. setBuildEnabled (false, true);
  977. showBuildTab();
  978. }
  979. }
  980. bool ProjectContentComponent::isBuildEnabled() const
  981. {
  982. return isLiveBuildEnabled
  983. && project != nullptr
  984. && project->getCompileEngineSettings().isBuildEnabled()
  985. && CompileEngineDLL::getInstance()->isLoaded();
  986. }
  987. void ProjectContentComponent::refreshTabsIfBuildStatusChanged()
  988. {
  989. if (project != nullptr
  990. && isLiveBuildEnabled
  991. && (sidebarTabs.getNumTabs() < 2 || isBuildEnabled() != isBuildTabEnabled()))
  992. {
  993. rebuildProjectUI();
  994. }
  995. }
  996. bool ProjectContentComponent::areWarningsEnabled() const
  997. {
  998. return project != nullptr && project->getCompileEngineSettings().areWarningsEnabled();
  999. }
  1000. void ProjectContentComponent::updateWarningState()
  1001. {
  1002. if (childProcess != nullptr)
  1003. childProcess->errorList.setWarningsEnabled (areWarningsEnabled());
  1004. }
  1005. void ProjectContentComponent::toggleWarnings()
  1006. {
  1007. if (project != nullptr)
  1008. {
  1009. project->getCompileEngineSettings().setWarningsEnabled (! areWarningsEnabled());
  1010. updateWarningState();
  1011. }
  1012. }
  1013. static ProjucerAppClasses::ErrorListComp* findErrorListComp (const TabbedComponent& tabs)
  1014. {
  1015. if (auto* bt = findBuildTab (tabs))
  1016. return bt->errorListComp;
  1017. return nullptr;
  1018. }
  1019. void ProjectContentComponent::showNextError()
  1020. {
  1021. if (auto* el = findErrorListComp (sidebarTabs))
  1022. {
  1023. showBuildTab();
  1024. el->showNext();
  1025. }
  1026. }
  1027. void ProjectContentComponent::showPreviousError()
  1028. {
  1029. if (auto* el = findErrorListComp (sidebarTabs))
  1030. {
  1031. showBuildTab();
  1032. el->showPrevious();
  1033. }
  1034. }
  1035. void ProjectContentComponent::reinstantiateLivePreviewWindows()
  1036. {
  1037. if (childProcess != nullptr)
  1038. childProcess->reinstantiatePreviews();
  1039. }
  1040. void ProjectContentComponent::addNewGUIFile()
  1041. {
  1042. if (project != nullptr)
  1043. {
  1044. std::unique_ptr<NewFileWizard::Type> wizard (createGUIComponentWizard());
  1045. wizard->createNewFile (*project, project->getMainGroup());
  1046. }
  1047. }
  1048. void ProjectContentComponent::launchApp()
  1049. {
  1050. if (childProcess != nullptr)
  1051. childProcess->launchApp();
  1052. }
  1053. void ProjectContentComponent::killApp()
  1054. {
  1055. if (childProcess != nullptr)
  1056. childProcess->killApp();
  1057. }
  1058. void ProjectContentComponent::rebuildNow()
  1059. {
  1060. if (childProcess != nullptr)
  1061. childProcess->flushEditorChanges();
  1062. }
  1063. void ProjectContentComponent::globalFocusChanged (Component* focusedComponent)
  1064. {
  1065. auto nowForeground = (Process::isForegroundProcess()
  1066. && (focusedComponent == this || isParentOf (focusedComponent)));
  1067. if (nowForeground != isForeground)
  1068. {
  1069. isForeground = nowForeground;
  1070. if (childProcess != nullptr)
  1071. childProcess->processActivationChanged (isForeground);
  1072. }
  1073. }
  1074. void ProjectContentComponent::timerCallback()
  1075. {
  1076. if (! isBuildEnabled())
  1077. killChildProcess();
  1078. refreshTabsIfBuildStatusChanged();
  1079. }
  1080. void ProjectContentComponent::liveBuildEnablementChanged (bool isEnabled)
  1081. {
  1082. isLiveBuildEnabled = isEnabled;
  1083. if (isLiveBuildEnabled)
  1084. {
  1085. startTimer (1600);
  1086. }
  1087. else
  1088. {
  1089. stopTimer();
  1090. killChildProcess();
  1091. }
  1092. rebuildProjectUI();
  1093. headerComponent.liveBuildEnablementChanged (isLiveBuildEnabled);
  1094. }
  1095. bool ProjectContentComponent::isContinuousRebuildEnabled()
  1096. {
  1097. return project != nullptr && project->getCompileEngineSettings().isContinuousRebuildEnabled();
  1098. }
  1099. void ProjectContentComponent::setContinuousRebuildEnabled (bool b)
  1100. {
  1101. if (project != nullptr && childProcess != nullptr)
  1102. {
  1103. project->getCompileEngineSettings().setContinuousRebuildEnabled (b);
  1104. ProjucerApplication::getCommandManager().commandStatusChanged();
  1105. }
  1106. }
  1107. ReferenceCountedObjectPtr<CompileEngineChildProcess> ProjectContentComponent::getChildProcess()
  1108. {
  1109. if (childProcess == nullptr && isBuildEnabled())
  1110. childProcess = ProjucerApplication::getApp().childProcessCache->getOrCreate (*project);
  1111. return childProcess;
  1112. }
  1113. void ProjectContentComponent::handleMissingSystemHeaders()
  1114. {
  1115. #if JUCE_MAC
  1116. String tabMessage ("Compiler not available due to missing system headers\nPlease install a recent version of Xcode");
  1117. String alertWindowMessage ("Missing system headers\nPlease install a recent version of Xcode");
  1118. #elif JUCE_WINDOWS
  1119. String tabMessage ("Compiler not available due to missing system headers\nPlease install a recent version of Visual Studio and the Windows Desktop SDK");
  1120. String alertWindowMessage ("Missing system headers\nPlease install a recent version of Visual Studio and the Windows Desktop SDK");
  1121. #elif JUCE_LINUX
  1122. String tabMessage ("Compiler not available due to missing system headers\nPlease do a sudo apt-get install ...");
  1123. String alertWindowMessage ("Missing system headers\nPlease do sudo apt-get install ...");
  1124. #endif
  1125. setBuildEnabled (false, true);
  1126. deleteProjectTabs();
  1127. createProjectTabs();
  1128. if (auto* bt = getLiveBuildTab())
  1129. {
  1130. bt->isEnabled = false;
  1131. bt->errorMessage = tabMessage;
  1132. }
  1133. showBuildTab();
  1134. AlertWindow::showMessageBox (AlertWindow::AlertIconType::WarningIcon,
  1135. "Missing system headers", alertWindowMessage);
  1136. }
  1137. //==============================================================================
  1138. void ProjectContentComponent::showProjectPanel (const int index)
  1139. {
  1140. showProjectTab();
  1141. if (auto* pTab = getProjectTab())
  1142. pTab->showPanel (index);
  1143. }
  1144. ProjectTab* ProjectContentComponent::getProjectTab()
  1145. {
  1146. return dynamic_cast<ProjectTab*> (sidebarTabs.getTabContentComponent (0));
  1147. }
  1148. LiveBuildTab* ProjectContentComponent::getLiveBuildTab()
  1149. {
  1150. return dynamic_cast<LiveBuildTab*> (sidebarTabs.getTabContentComponent (1));
  1151. }