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.

1406 lines
47KB

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