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.

1378 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. 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. 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 (int 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 (int 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. if (saveFirst && ! saveProject (exporter->isXcode()))
  506. return;
  507. exporter->launchProject();
  508. break;
  509. }
  510. }
  511. }
  512. }
  513. }
  514. static void newExporterMenuCallback (int result, ProjectContentComponent* comp)
  515. {
  516. if (comp != nullptr && result > 0)
  517. {
  518. if (Project* p = comp->getProject())
  519. {
  520. String exporterName (ProjectExporter::getExporterNames() [result - 1]);
  521. if (exporterName.isNotEmpty())
  522. p->addNewExporter (exporterName);
  523. }
  524. }
  525. }
  526. void ProjectContentComponent::showNewExporterMenu()
  527. {
  528. if (project != nullptr)
  529. {
  530. PopupMenu menu;
  531. menu.addSectionHeader ("Create a new export target:");
  532. Array<ProjectExporter::ExporterTypeInfo> exporters (ProjectExporter::getExporterTypes());
  533. for (int i = 0; i < exporters.size(); ++i)
  534. {
  535. const ProjectExporter::ExporterTypeInfo& type = exporters.getReference(i);
  536. menu.addItem (i + 1, type.name, true, false, type.getIcon());
  537. }
  538. menu.showMenuAsync (PopupMenu::Options(),
  539. ModalCallbackFunction::forComponent (newExporterMenuCallback, this));
  540. }
  541. }
  542. void ProjectContentComponent::deleteSelectedTreeItems()
  543. {
  544. if (auto* const tree = getProjectTab()->getTreeWithSelectedItems())
  545. tree->deleteSelectedItems();
  546. }
  547. void ProjectContentComponent::showBubbleMessage (Rectangle<int> pos, const String& text)
  548. {
  549. addChildComponent (bubbleMessage);
  550. bubbleMessage.setColour (BubbleComponent::backgroundColourId, Colours::white.withAlpha (0.7f));
  551. bubbleMessage.setColour (BubbleComponent::outlineColourId, Colours::black.withAlpha (0.8f));
  552. bubbleMessage.setAlwaysOnTop (true);
  553. bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false);
  554. }
  555. //==============================================================================
  556. void ProjectContentComponent::showTranslationTool()
  557. {
  558. if (translationTool != nullptr)
  559. {
  560. translationTool->toFront (true);
  561. }
  562. else if (project != nullptr)
  563. {
  564. new FloatingToolWindow ("Translation File Builder",
  565. "transToolWindowPos",
  566. new TranslationToolComponent(),
  567. translationTool, true,
  568. 600, 700,
  569. 600, 400, 10000, 10000);
  570. }
  571. }
  572. //==============================================================================
  573. struct AsyncCommandRetrier : public Timer
  574. {
  575. AsyncCommandRetrier (const ApplicationCommandTarget::InvocationInfo& i) : info (i)
  576. {
  577. info.originatingComponent = nullptr;
  578. startTimer (500);
  579. }
  580. void timerCallback() override
  581. {
  582. stopTimer();
  583. ProjucerApplication::getCommandManager().invoke (info, true);
  584. delete this;
  585. }
  586. ApplicationCommandTarget::InvocationInfo info;
  587. JUCE_DECLARE_NON_COPYABLE (AsyncCommandRetrier)
  588. };
  589. bool reinvokeCommandAfterCancellingModalComps (const ApplicationCommandTarget::InvocationInfo& info)
  590. {
  591. if (ModalComponentManager::getInstance()->cancelAllModalComponents())
  592. {
  593. new AsyncCommandRetrier (info);
  594. return true;
  595. }
  596. return false;
  597. }
  598. //==============================================================================
  599. ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
  600. {
  601. return findFirstTargetParentComponent();
  602. }
  603. void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
  604. {
  605. const CommandID ids[] = { CommandIDs::saveProject,
  606. CommandIDs::closeProject,
  607. CommandIDs::saveDocument,
  608. CommandIDs::saveDocumentAs,
  609. CommandIDs::closeDocument,
  610. CommandIDs::goToPreviousDoc,
  611. CommandIDs::goToNextDoc,
  612. CommandIDs::goToCounterpart,
  613. CommandIDs::showProjectSettings,
  614. CommandIDs::showProjectTab,
  615. CommandIDs::showBuildTab,
  616. CommandIDs::showFileExplorerPanel,
  617. CommandIDs::showModulesPanel,
  618. CommandIDs::showExportersPanel,
  619. CommandIDs::showExporterSettings,
  620. CommandIDs::openInIDE,
  621. CommandIDs::saveAndOpenInIDE,
  622. CommandIDs::createNewExporter,
  623. CommandIDs::deleteSelectedItem,
  624. CommandIDs::showTranslationTool,
  625. CommandIDs::cleanAll,
  626. CommandIDs::toggleBuildEnabled,
  627. CommandIDs::buildNow,
  628. CommandIDs::toggleContinuousBuild,
  629. CommandIDs::launchApp,
  630. CommandIDs::killApp,
  631. CommandIDs::reinstantiateComp,
  632. CommandIDs::showWarnings,
  633. CommandIDs::nextError,
  634. CommandIDs::prevError };
  635. commands.addArray (ids, numElementsInArray (ids));
  636. }
  637. void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  638. {
  639. String documentName;
  640. if (currentDocument != nullptr)
  641. documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
  642. #if JUCE_MAC
  643. const ModifierKeys cmdCtrl (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier);
  644. #else
  645. const ModifierKeys cmdCtrl (ModifierKeys::ctrlModifier | ModifierKeys::altModifier);
  646. #endif
  647. switch (commandID)
  648. {
  649. case CommandIDs::saveProject:
  650. result.setInfo ("Save Project",
  651. "Saves the current project",
  652. CommandCategories::general, 0);
  653. result.setActive (project != nullptr && ! project->isCurrentlySaving());
  654. break;
  655. case CommandIDs::closeProject:
  656. result.setInfo ("Close Project",
  657. "Closes the current project",
  658. CommandCategories::general, 0);
  659. result.setActive (project != nullptr);
  660. break;
  661. case CommandIDs::saveDocument:
  662. result.setInfo ("Save" + documentName,
  663. "Saves the current document",
  664. CommandCategories::general, 0);
  665. result.setActive (currentDocument != nullptr || (project != nullptr && ! project->isCurrentlySaving()));
  666. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  667. break;
  668. case CommandIDs::saveDocumentAs:
  669. result.setInfo ("Save As...",
  670. "Saves the current document to a new location",
  671. CommandCategories::general, 0);
  672. result.setActive (currentDocument != nullptr);
  673. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  674. break;
  675. case CommandIDs::closeDocument:
  676. result.setInfo ("Close" + documentName,
  677. "Closes the current document",
  678. CommandCategories::general, 0);
  679. result.setActive (contentView != nullptr);
  680. result.defaultKeypresses.add (KeyPress ('w', cmdCtrl, 0));
  681. break;
  682. case CommandIDs::goToPreviousDoc:
  683. result.setInfo ("Previous Document",
  684. "Go to previous document",
  685. CommandCategories::general, 0);
  686. result.setActive (recentDocumentList.canGoToPrevious());
  687. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, cmdCtrl, 0));
  688. break;
  689. case CommandIDs::goToNextDoc:
  690. result.setInfo ("Next Document",
  691. "Go to next document",
  692. CommandCategories::general, 0);
  693. result.setActive (recentDocumentList.canGoToNext());
  694. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, cmdCtrl, 0));
  695. break;
  696. case CommandIDs::goToCounterpart:
  697. result.setInfo ("Open Counterpart File",
  698. "Open corresponding header or cpp file",
  699. CommandCategories::general, 0);
  700. result.setActive (canGoToCounterpart());
  701. result.defaultKeypresses.add (KeyPress (KeyPress::upKey, cmdCtrl, 0));
  702. break;
  703. case CommandIDs::showProjectSettings:
  704. result.setInfo ("Show Project Settings",
  705. "Shows the main project options page",
  706. CommandCategories::general, 0);
  707. result.setActive (project != nullptr);
  708. result.defaultKeypresses.add (KeyPress ('x', cmdCtrl, 0));
  709. break;
  710. case CommandIDs::showProjectTab:
  711. result.setInfo ("Show Project Tab",
  712. "Shows the tab containing the project information",
  713. CommandCategories::general, 0);
  714. result.setActive (project != nullptr);
  715. result.defaultKeypresses.add (KeyPress ('p', cmdCtrl, 0));
  716. break;
  717. case CommandIDs::showBuildTab:
  718. result.setInfo ("Show Build Tab",
  719. "Shows the tab containing the build panel",
  720. CommandCategories::general, 0);
  721. result.setActive (project != nullptr);
  722. result.defaultKeypresses.add (KeyPress ('b', cmdCtrl, 0));
  723. break;
  724. case CommandIDs::showFileExplorerPanel:
  725. result.setInfo ("Show File Explorer Panel",
  726. "Shows the panel containing the tree of files for this project",
  727. CommandCategories::general, 0);
  728. result.setActive (project != nullptr);
  729. result.defaultKeypresses.add (KeyPress ('f', cmdCtrl, 0));
  730. break;
  731. case CommandIDs::showModulesPanel:
  732. result.setInfo ("Show Modules Panel",
  733. "Shows the panel containing the project's list of modules",
  734. CommandCategories::general, 0);
  735. result.setActive (project != nullptr);
  736. result.defaultKeypresses.add (KeyPress ('m', cmdCtrl, 0));
  737. break;
  738. case CommandIDs::showExportersPanel:
  739. result.setInfo ("Show Exporters Panel",
  740. "Shows the panel containing the project's list of exporters",
  741. CommandCategories::general, 0);
  742. result.setActive (project != nullptr);
  743. result.defaultKeypresses.add (KeyPress ('e', cmdCtrl, 0));
  744. break;
  745. case CommandIDs::showExporterSettings:
  746. result.setInfo ("Show Exporter Settings",
  747. "Shows the settings page for the currently selected exporter",
  748. CommandCategories::general, 0);
  749. result.setActive (project != nullptr);
  750. result.defaultKeypresses.add (KeyPress ('e', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  751. break;
  752. case CommandIDs::openInIDE:
  753. result.setInfo ("Open in IDE...",
  754. "Launches the project in an external IDE",
  755. CommandCategories::general, 0);
  756. result.setActive (ProjectExporter::canProjectBeLaunched (project));
  757. break;
  758. case CommandIDs::saveAndOpenInIDE:
  759. result.setInfo ("Save Project and Open in IDE...",
  760. "Saves the project and launches it in an external IDE",
  761. CommandCategories::general, 0);
  762. result.setActive (ProjectExporter::canProjectBeLaunched (project) && ! project->isCurrentlySaving());
  763. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  764. break;
  765. case CommandIDs::createNewExporter:
  766. result.setInfo ("Create New Exporter...",
  767. "Creates a new exporter for a compiler type",
  768. CommandCategories::general, 0);
  769. result.setActive (project != nullptr);
  770. break;
  771. case CommandIDs::deleteSelectedItem:
  772. result.setInfo ("Delete Selected File",
  773. String(),
  774. CommandCategories::general, 0);
  775. result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0));
  776. result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0));
  777. result.setActive (sidebarTabs.getCurrentTabIndex() == 0);
  778. break;
  779. case CommandIDs::showTranslationTool:
  780. result.setInfo ("Translation File Builder",
  781. "Shows the translation file helper tool",
  782. CommandCategories::general, 0);
  783. break;
  784. case CommandIDs::cleanAll:
  785. result.setInfo ("Clean All",
  786. "Cleans all intermediate files",
  787. CommandCategories::general, 0);
  788. result.defaultKeypresses.add (KeyPress ('k', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  789. result.setActive (project != nullptr);
  790. break;
  791. case CommandIDs::toggleBuildEnabled:
  792. result.setInfo ("Enable Compilation",
  793. "Enables/disables the compiler",
  794. CommandCategories::general, 0);
  795. result.defaultKeypresses.add (KeyPress ('b', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  796. result.setActive (project != nullptr);
  797. result.setTicked (childProcess != nullptr);
  798. break;
  799. case CommandIDs::buildNow:
  800. result.setInfo ("Build Now",
  801. "Recompiles any out-of-date files and updates the JIT engine",
  802. CommandCategories::general, 0);
  803. result.defaultKeypresses.add (KeyPress ('b', ModifierKeys::commandModifier, 0));
  804. result.setActive (childProcess != nullptr);
  805. break;
  806. case CommandIDs::toggleContinuousBuild:
  807. result.setInfo ("Enable Continuous Recompiling",
  808. "Continuously recompiles any changes made in code editors",
  809. CommandCategories::general, 0);
  810. result.setActive (childProcess != nullptr);
  811. result.setTicked (isContinuousRebuildEnabled());
  812. break;
  813. case CommandIDs::launchApp:
  814. result.setInfo ("Launch Application",
  815. "Invokes the app's main() function",
  816. CommandCategories::general, 0);
  817. result.defaultKeypresses.add (KeyPress ('r', ModifierKeys::commandModifier, 0));
  818. result.setActive (childProcess != nullptr && childProcess->canLaunchApp());
  819. break;
  820. case CommandIDs::killApp:
  821. result.setInfo ("Stop Application",
  822. "Kills the app if it's running",
  823. CommandCategories::general, 0);
  824. result.defaultKeypresses.add (KeyPress ('.', ModifierKeys::commandModifier, 0));
  825. result.setActive (childProcess != nullptr && childProcess->canKillApp());
  826. break;
  827. case CommandIDs::reinstantiateComp:
  828. result.setInfo ("Re-instantiate Components",
  829. "Re-loads any component editors that are open",
  830. CommandCategories::general, 0);
  831. result.defaultKeypresses.add (KeyPress ('r', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  832. result.setActive (childProcess != nullptr);
  833. break;
  834. case CommandIDs::showWarnings:
  835. result.setInfo ("Show Warnings",
  836. "Shows or hides compilation warnings",
  837. CommandCategories::general, 0);
  838. result.setActive (project != nullptr);
  839. result.setTicked (areWarningsEnabled());
  840. break;
  841. case CommandIDs::nextError:
  842. result.setInfo ("Highlight next error",
  843. "Jumps to the next error or warning",
  844. CommandCategories::general, 0);
  845. result.defaultKeypresses.add (KeyPress ('\'', ModifierKeys::commandModifier, 0));
  846. result.setActive (childProcess != nullptr && ! childProcess->errorList.isEmpty());
  847. break;
  848. case CommandIDs::prevError:
  849. result.setInfo ("Highlight previous error",
  850. "Jumps to the last error or warning",
  851. CommandCategories::general, 0);
  852. result.defaultKeypresses.add (KeyPress ('\"', ModifierKeys::commandModifier, 0));
  853. result.setActive (childProcess != nullptr && ! childProcess->errorList.isEmpty());
  854. break;
  855. default:
  856. break;
  857. }
  858. }
  859. bool ProjectContentComponent::perform (const InvocationInfo& info)
  860. {
  861. // don't allow the project to be saved again if it's currently saving
  862. if (isSaveCommand (info.commandID) && (project != nullptr && project->isCurrentlySaving()))
  863. return false;
  864. switch (info.commandID)
  865. {
  866. case CommandIDs::saveProject:
  867. case CommandIDs::closeProject:
  868. case CommandIDs::saveDocument:
  869. case CommandIDs::saveDocumentAs:
  870. case CommandIDs::closeDocument:
  871. case CommandIDs::goToPreviousDoc:
  872. case CommandIDs::goToNextDoc:
  873. case CommandIDs::goToCounterpart:
  874. case CommandIDs::saveAndOpenInIDE:
  875. if (reinvokeCommandAfterCancellingModalComps (info))
  876. {
  877. grabKeyboardFocus(); // to force any open labels to close their text editors
  878. return true;
  879. }
  880. break;
  881. default:
  882. break;
  883. }
  884. if (isCurrentlyBlockedByAnotherModalComponent())
  885. return false;
  886. switch (info.commandID)
  887. {
  888. case CommandIDs::saveProject: saveProject(); break;
  889. case CommandIDs::closeProject: closeProject(); break;
  890. case CommandIDs::saveDocument: saveDocument(); break;
  891. case CommandIDs::saveDocumentAs: saveAs(); break;
  892. case CommandIDs::closeDocument: closeDocument(); break;
  893. case CommandIDs::goToPreviousDoc: goToPreviousFile(); break;
  894. case CommandIDs::goToNextDoc: goToNextFile(); break;
  895. case CommandIDs::goToCounterpart: goToCounterpart(); break;
  896. case CommandIDs::showProjectSettings: showProjectSettings(); break;
  897. case CommandIDs::showProjectTab: showProjectTab(); break;
  898. case CommandIDs::showBuildTab: showBuildTab(); break;
  899. case CommandIDs::showFileExplorerPanel: showFilesPanel(); break;
  900. case CommandIDs::showModulesPanel: showModulesPanel(); break;
  901. case CommandIDs::showExportersPanel: showExportersPanel(); break;
  902. case CommandIDs::showExporterSettings: showCurrentExporterSettings(); break;
  903. case CommandIDs::openInIDE: openInSelectedIDE (false); break;
  904. case CommandIDs::saveAndOpenInIDE: openInSelectedIDE (true); break;
  905. case CommandIDs::createNewExporter: showNewExporterMenu(); break;
  906. case CommandIDs::deleteSelectedItem: deleteSelectedTreeItems(); break;
  907. case CommandIDs::showTranslationTool: showTranslationTool(); break;
  908. case CommandIDs::cleanAll: cleanAll(); break;
  909. case CommandIDs::toggleBuildEnabled: setBuildEnabled (! isBuildEnabled()); break;
  910. case CommandIDs::buildNow: rebuildNow(); break;
  911. case CommandIDs::toggleContinuousBuild: setContinuousRebuildEnabled (! isContinuousRebuildEnabled()); break;
  912. case CommandIDs::launchApp: launchApp(); break;
  913. case CommandIDs::killApp: killApp(); break;
  914. case CommandIDs::reinstantiateComp: reinstantiateLivePreviewWindows(); break;
  915. case CommandIDs::showWarnings: toggleWarnings(); break;
  916. case CommandIDs::nextError: showNextError(); break;
  917. case CommandIDs::prevError: showPreviousError(); break;
  918. default:
  919. return false;
  920. }
  921. return true;
  922. }
  923. bool ProjectContentComponent::isSaveCommand (const CommandID id)
  924. {
  925. return (id == CommandIDs::saveProject || id == CommandIDs::saveDocument || id == CommandIDs::saveAndOpenInIDE);
  926. }
  927. void ProjectContentComponent::getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
  928. OwnedArray<Project::Item>& selectedNodes)
  929. {
  930. TreeItemTypes::FileTreeItemBase::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  931. }
  932. //==============================================================================
  933. void ProjectContentComponent::killChildProcess()
  934. {
  935. if (childProcess != nullptr)
  936. {
  937. deleteProjectTabs();
  938. childProcess = nullptr;
  939. ProjucerApplication::getApp().childProcessCache->removeOrphans();
  940. }
  941. }
  942. void ProjectContentComponent::setBuildEnabled (bool isEnabled, bool displayError)
  943. {
  944. if (project != nullptr && isEnabled != isBuildEnabled())
  945. {
  946. if (! displayError)
  947. lastCrashMessage = {};
  948. LiveBuildProjectSettings::setBuildDisabled (*project, ! isEnabled);
  949. killChildProcess();
  950. refreshTabsIfBuildStatusChanged();
  951. if (auto* h = dynamic_cast<HeaderComponent*> (header.get()))
  952. h->updateBuildButtons (isEnabled, isContinuousRebuildEnabled());
  953. }
  954. }
  955. void ProjectContentComponent::cleanAll()
  956. {
  957. lastCrashMessage = String();
  958. if (childProcess != nullptr)
  959. childProcess->cleanAll();
  960. else if (Project* p = getProject())
  961. CompileEngineChildProcess::cleanAllCachedFilesForProject (*p);
  962. }
  963. void ProjectContentComponent::handleCrash (const String& message)
  964. {
  965. lastCrashMessage = message.isEmpty() ? TRANS("JIT process stopped responding!")
  966. : (TRANS("JIT process crashed!") + ":\n\n" + message);
  967. if (project != nullptr)
  968. {
  969. setBuildEnabled (false, true);
  970. showBuildTab();
  971. }
  972. }
  973. bool ProjectContentComponent::isBuildEnabled() const
  974. {
  975. return project != nullptr && ! LiveBuildProjectSettings::isBuildDisabled (*project)
  976. && CompileEngineDLL::getInstance()->isLoaded();
  977. }
  978. void ProjectContentComponent::refreshTabsIfBuildStatusChanged()
  979. {
  980. if (project != nullptr
  981. && (sidebarTabs.getNumTabs() < 2
  982. || isBuildEnabled() != isBuildTabEnabled()))
  983. rebuildProjectTabs();
  984. }
  985. bool ProjectContentComponent::areWarningsEnabled() const
  986. {
  987. return project != nullptr && ! LiveBuildProjectSettings::areWarningsDisabled (*project);
  988. }
  989. void ProjectContentComponent::updateWarningState()
  990. {
  991. if (childProcess != nullptr)
  992. childProcess->errorList.setWarningsEnabled (areWarningsEnabled());
  993. }
  994. void ProjectContentComponent::toggleWarnings()
  995. {
  996. if (project != nullptr)
  997. {
  998. LiveBuildProjectSettings::setWarningsDisabled (*project, areWarningsEnabled());
  999. updateWarningState();
  1000. }
  1001. }
  1002. static ProjucerAppClasses::ErrorListComp* findErrorListComp (const TabbedComponent& tabs)
  1003. {
  1004. if (LiveBuildTab* bt = findBuildTab (tabs))
  1005. return bt->errorListComp;
  1006. return nullptr;
  1007. }
  1008. void ProjectContentComponent::showNextError()
  1009. {
  1010. if (ProjucerAppClasses::ErrorListComp* el = findErrorListComp (sidebarTabs))
  1011. {
  1012. showBuildTab();
  1013. el->showNext();
  1014. }
  1015. }
  1016. void ProjectContentComponent::showPreviousError()
  1017. {
  1018. if (ProjucerAppClasses::ErrorListComp* el = findErrorListComp (sidebarTabs))
  1019. {
  1020. showBuildTab();
  1021. el->showPrevious();
  1022. }
  1023. }
  1024. void ProjectContentComponent::reinstantiateLivePreviewWindows()
  1025. {
  1026. if (childProcess != nullptr)
  1027. childProcess->reinstantiatePreviews();
  1028. }
  1029. void ProjectContentComponent::launchApp()
  1030. {
  1031. if (childProcess != nullptr)
  1032. childProcess->launchApp();
  1033. }
  1034. void ProjectContentComponent::killApp()
  1035. {
  1036. if (childProcess != nullptr)
  1037. childProcess->killApp();
  1038. }
  1039. void ProjectContentComponent::rebuildNow()
  1040. {
  1041. if (childProcess != nullptr)
  1042. childProcess->flushEditorChanges();
  1043. }
  1044. void ProjectContentComponent::globalFocusChanged (Component* focusedComponent)
  1045. {
  1046. const bool nowForeground = (Process::isForegroundProcess()
  1047. && (focusedComponent == this || isParentOf (focusedComponent)));
  1048. if (nowForeground != isForeground)
  1049. {
  1050. isForeground = nowForeground;
  1051. if (childProcess != nullptr)
  1052. childProcess->processActivationChanged (isForeground);
  1053. }
  1054. }
  1055. void ProjectContentComponent::timerCallback()
  1056. {
  1057. if (! isBuildEnabled())
  1058. killChildProcess();
  1059. refreshTabsIfBuildStatusChanged();
  1060. }
  1061. bool ProjectContentComponent::isContinuousRebuildEnabled()
  1062. {
  1063. return getAppSettings().getGlobalProperties().getBoolValue ("continuousRebuild", true);
  1064. }
  1065. void ProjectContentComponent::setContinuousRebuildEnabled (bool b)
  1066. {
  1067. if (childProcess != nullptr)
  1068. {
  1069. childProcess->setContinuousRebuild (b);
  1070. if (auto* h = dynamic_cast<HeaderComponent*> (header.get()))
  1071. h->updateBuildButtons (isBuildEnabled(), b);
  1072. getAppSettings().getGlobalProperties().setValue ("continuousRebuild", b);
  1073. ProjucerApplication::getCommandManager().commandStatusChanged();
  1074. }
  1075. }
  1076. ReferenceCountedObjectPtr<CompileEngineChildProcess> ProjectContentComponent::getChildProcess()
  1077. {
  1078. if (childProcess == nullptr && isBuildEnabled())
  1079. {
  1080. childProcess = ProjucerApplication::getApp().childProcessCache->getOrCreate (*project);
  1081. if (childProcess != nullptr)
  1082. childProcess->setContinuousRebuild (isContinuousRebuildEnabled());
  1083. }
  1084. return childProcess;
  1085. }
  1086. void ProjectContentComponent::handleMissingSystemHeaders()
  1087. {
  1088. #if JUCE_MAC
  1089. const String tabMessage = "Compiler not available due to missing system headers\nPlease install a recent version of Xcode";
  1090. const String alertWindowMessage = "Missing system headers\nPlease install a recent version of Xcode";
  1091. #elif JUCE_WINDOWS
  1092. const String tabMessage = "Compiler not available due to missing system headers\nPlease install a recent version of Visual Studio and the Windows Desktop SDK";
  1093. const String alertWindowMessage = "Missing system headers\nPlease install a recent version of Visual Studio and the Windows Desktop SDK";
  1094. #elif JUCE_LINUX
  1095. const String tabMessage = "Compiler not available due to missing system headers\nPlease do a sudo apt-get install ...";
  1096. const String alertWindowMessage = "Missing system headers\nPlease do sudo apt-get install ...";
  1097. #endif
  1098. setBuildEnabled (false, true);
  1099. deleteProjectTabs();
  1100. createProjectTabs();
  1101. if (auto* bt = getLiveBuildTab())
  1102. {
  1103. bt->isEnabled = false;
  1104. bt->errorMessage = tabMessage;
  1105. }
  1106. showBuildTab();
  1107. AlertWindow::showMessageBox (AlertWindow::AlertIconType::WarningIcon,
  1108. "Missing system headers", alertWindowMessage);
  1109. }
  1110. //==============================================================================
  1111. void ProjectContentComponent::showProjectPanel (const int index)
  1112. {
  1113. showProjectTab();
  1114. if (auto* pTab = getProjectTab())
  1115. pTab->showPanel (index);
  1116. }
  1117. ProjectTab* ProjectContentComponent::getProjectTab()
  1118. {
  1119. return dynamic_cast<ProjectTab*> (sidebarTabs.getTabContentComponent (0));
  1120. }
  1121. LiveBuildTab* ProjectContentComponent::getLiveBuildTab()
  1122. {
  1123. return dynamic_cast<LiveBuildTab*> (sidebarTabs.getTabContentComponent (1));
  1124. }