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.

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