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.

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