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.

1355 lines
46KB

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