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.

1366 lines
46KB

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