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.

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