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.

1394 lines
47KB

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