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.

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