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.

683 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "jucer_ProjectContentComponent.h"
  19. #include "../Application/jucer_MainWindow.h"
  20. #include "../Application/jucer_Application.h"
  21. #include "../Code Editor/jucer_SourceCodeEditor.h"
  22. #include "jucer_ConfigPage.h"
  23. #include "jucer_TreeViewTypes.h"
  24. #include "../Project Saving/jucer_ProjectExporter.h"
  25. //==============================================================================
  26. class FileTreeTab : public TreePanelBase
  27. {
  28. public:
  29. FileTreeTab (Project& project)
  30. : TreePanelBase ("treeViewState_" + project.getProjectUID())
  31. {
  32. tree.setMultiSelectEnabled (true);
  33. setRoot (new GroupTreeViewItem (project.getMainGroup()));
  34. }
  35. };
  36. //==============================================================================
  37. class ConfigTreeTab : public TreePanelBase
  38. {
  39. public:
  40. ConfigTreeTab (Project& project)
  41. : TreePanelBase ("settingsTreeViewState_" + project.getProjectUID())
  42. {
  43. tree.setMultiSelectEnabled (false);
  44. setRoot (createProjectConfigTreeViewRoot (project));
  45. if (tree.getNumSelectedItems() == 0)
  46. tree.getRootItem()->setSelected (true, true);
  47. #if JUCE_MAC || JUCE_WINDOWS
  48. addAndMakeVisible (&openProjectButton);
  49. openProjectButton.setCommandToTrigger (commandManager, CommandIDs::openInIDE, true);
  50. openProjectButton.setButtonText (commandManager->getNameOfCommand (CommandIDs::openInIDE));
  51. openProjectButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f));
  52. addAndMakeVisible (&saveAndOpenButton);
  53. saveAndOpenButton.setCommandToTrigger (commandManager, CommandIDs::saveAndOpenInIDE, true);
  54. saveAndOpenButton.setButtonText (commandManager->getNameOfCommand (CommandIDs::saveAndOpenInIDE));
  55. saveAndOpenButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f));
  56. #endif
  57. }
  58. void resized()
  59. {
  60. Rectangle<int> r (getAvailableBounds());
  61. r.removeFromBottom (6);
  62. if (saveAndOpenButton.isVisible())
  63. saveAndOpenButton.setBounds (r.removeFromBottom (28).reduced (20, 3));
  64. if (openProjectButton.isVisible())
  65. openProjectButton.setBounds (r.removeFromBottom (28).reduced (20, 3));
  66. tree.setBounds (r);
  67. }
  68. TextButton openProjectButton, saveAndOpenButton;
  69. };
  70. //==============================================================================
  71. class LogoComponent : public Component
  72. {
  73. public:
  74. LogoComponent() {}
  75. void paint (Graphics& g)
  76. {
  77. const Path& logo = getIcons().mainJuceLogo;
  78. const AffineTransform trans (RectanglePlacement (RectanglePlacement::centred)
  79. .getTransformToFit (logo.getBounds(),
  80. getLocalBounds().toFloat()));
  81. g.setColour (findColour (mainBackgroundColourId).contrasting (0.3f));
  82. g.fillPath (logo, trans);
  83. }
  84. };
  85. //==============================================================================
  86. ProjectContentComponent::ProjectContentComponent()
  87. : project (nullptr),
  88. currentDocument (nullptr),
  89. treeViewTabs (TabbedButtonBar::TabsAtTop)
  90. {
  91. setOpaque (true);
  92. setWantsKeyboardFocus (true);
  93. addAndMakeVisible (logo = new LogoComponent());
  94. treeSizeConstrainer.setMinimumWidth (200);
  95. treeSizeConstrainer.setMaximumWidth (500);
  96. treeViewTabs.setOutline (0);
  97. treeViewTabs.getTabbedButtonBar().setMinimumTabScaleFactor (0.3);
  98. JucerApplication::getApp().openDocumentManager.addListener (this);
  99. }
  100. ProjectContentComponent::~ProjectContentComponent()
  101. {
  102. JucerApplication::getApp().openDocumentManager.removeListener (this);
  103. logo = nullptr;
  104. setProject (nullptr);
  105. contentView = nullptr;
  106. removeChildComponent (&bubbleMessage);
  107. jassert (getNumChildComponents() <= 1);
  108. }
  109. void ProjectContentComponent::paint (Graphics& g)
  110. {
  111. dynamic_cast<IntrojucerLookAndFeel&> (getLookAndFeel()).fillWithBackgroundTexture (g);
  112. }
  113. void ProjectContentComponent::paintOverChildren (Graphics& g)
  114. {
  115. if (resizerBar != nullptr)
  116. {
  117. const int shadowSize = 15;
  118. const int x = resizerBar->getRight();
  119. ColourGradient cg (Colours::black.withAlpha (0.25f), (float) x, 0,
  120. Colours::transparentBlack, (float) (x - shadowSize), 0, false);
  121. cg.addColour (0.4, Colours::black.withAlpha (0.07f));
  122. cg.addColour (0.6, Colours::black.withAlpha (0.02f));
  123. g.setGradientFill (cg);
  124. g.fillRect (x - shadowSize, 0, shadowSize, getHeight());
  125. }
  126. }
  127. void ProjectContentComponent::resized()
  128. {
  129. Rectangle<int> r (getLocalBounds());
  130. if (treeViewTabs.isVisible())
  131. treeViewTabs.setBounds (r.removeFromLeft (treeViewTabs.getWidth()));
  132. if (resizerBar != nullptr)
  133. resizerBar->setBounds (r.removeFromLeft (4));
  134. if (contentView != nullptr)
  135. contentView->setBounds (r);
  136. logo->setBounds (r.reduced (r.getWidth() / 4, r.getHeight() / 4));
  137. }
  138. void ProjectContentComponent::lookAndFeelChanged()
  139. {
  140. repaint();
  141. }
  142. void ProjectContentComponent::childBoundsChanged (Component* child)
  143. {
  144. if (child == &treeViewTabs)
  145. resized();
  146. }
  147. void ProjectContentComponent::setProject (Project* newProject)
  148. {
  149. if (project != newProject)
  150. {
  151. PropertiesFile& settings = getAppProperties();
  152. if (project != nullptr)
  153. project->removeChangeListener (this);
  154. contentView = nullptr;
  155. resizerBar = nullptr;
  156. if (project != nullptr && treeViewTabs.isShowing())
  157. {
  158. if (treeViewTabs.getWidth() > 0)
  159. settings.setValue ("projectTreeviewWidth_" + project->getProjectUID(), treeViewTabs.getWidth());
  160. settings.setValue ("lastTab_" + project->getProjectUID(), treeViewTabs.getCurrentTabName());
  161. }
  162. treeViewTabs.clearTabs();
  163. project = newProject;
  164. if (project != nullptr)
  165. {
  166. addAndMakeVisible (&treeViewTabs);
  167. createProjectTabs();
  168. const String lastTabName (settings.getValue ("lastTab_" + project->getProjectUID()));
  169. int lastTabIndex = treeViewTabs.getTabNames().indexOf (lastTabName);
  170. if (lastTabIndex < 0 || lastTabIndex > treeViewTabs.getNumTabs())
  171. lastTabIndex = 1;
  172. treeViewTabs.setCurrentTabIndex (lastTabIndex);
  173. int lastTreeWidth = settings.getValue ("projectTreeviewWidth_" + project->getProjectUID()).getIntValue();
  174. if (lastTreeWidth < 150)
  175. lastTreeWidth = 240;
  176. treeViewTabs.setBounds (0, 0, lastTreeWidth, getHeight());
  177. addAndMakeVisible (resizerBar = new ResizableEdgeComponent (&treeViewTabs, &treeSizeConstrainer,
  178. ResizableEdgeComponent::rightEdge));
  179. project->addChangeListener (this);
  180. updateMissingFileStatuses();
  181. }
  182. else
  183. {
  184. treeViewTabs.setVisible (false);
  185. }
  186. resized();
  187. }
  188. }
  189. void ProjectContentComponent::createProjectTabs()
  190. {
  191. jassert (project != nullptr);
  192. const Colour tabColour (Colours::transparentBlack);
  193. treeViewTabs.addTab ("Files", tabColour, new FileTreeTab (*project), true);
  194. treeViewTabs.addTab ("Config", tabColour, new ConfigTreeTab (*project), true);
  195. }
  196. TreeView* ProjectContentComponent::getFilesTreeView() const
  197. {
  198. FileTreeTab* ft = dynamic_cast<FileTreeTab*> (treeViewTabs.getTabContentComponent (0));
  199. return ft != nullptr ? &(ft->tree) : nullptr;
  200. }
  201. ProjectTreeViewBase* ProjectContentComponent::getFilesTreeRoot() const
  202. {
  203. TreeView* tv = getFilesTreeView();
  204. return tv != nullptr ? dynamic_cast <ProjectTreeViewBase*> (tv->getRootItem()) : nullptr;
  205. }
  206. void ProjectContentComponent::saveTreeViewState()
  207. {
  208. for (int i = treeViewTabs.getNumTabs(); --i >= 0;)
  209. {
  210. TreePanelBase* t = dynamic_cast<TreePanelBase*> (treeViewTabs.getTabContentComponent (i));
  211. if (t != nullptr)
  212. t->saveOpenness();
  213. }
  214. }
  215. void ProjectContentComponent::saveOpenDocumentList()
  216. {
  217. if (project != nullptr)
  218. {
  219. ScopedPointer<XmlElement> xml (recentDocumentList.createXML());
  220. if (xml != nullptr)
  221. getAppProperties().setValue ("lastDocs_" + project->getProjectUID(), xml);
  222. }
  223. }
  224. void ProjectContentComponent::reloadLastOpenDocuments()
  225. {
  226. if (project != nullptr)
  227. {
  228. ScopedPointer<XmlElement> xml (getAppProperties().getXmlValue ("lastDocs_" + project->getProjectUID()));
  229. if (xml != nullptr)
  230. {
  231. recentDocumentList.restoreFromXML (*project, *xml);
  232. showDocument (recentDocumentList.getCurrentDocument(), true);
  233. }
  234. }
  235. }
  236. void ProjectContentComponent::documentAboutToClose (OpenDocumentManager::Document* document)
  237. {
  238. hideDocument (document);
  239. }
  240. void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*)
  241. {
  242. updateMissingFileStatuses();
  243. }
  244. void ProjectContentComponent::updateMissingFileStatuses()
  245. {
  246. ProjectTreeViewBase* p = getFilesTreeRoot();
  247. if (p != nullptr)
  248. p->checkFileStatus();
  249. }
  250. bool ProjectContentComponent::showEditorForFile (const File& f, bool grabFocus)
  251. {
  252. return getCurrentFile() == f
  253. || showDocument (JucerApplication::getApp().openDocumentManager.openFile (project, f), grabFocus);
  254. }
  255. File ProjectContentComponent::getCurrentFile() const
  256. {
  257. return currentDocument != nullptr ? currentDocument->getFile()
  258. : File::nonexistent;
  259. }
  260. bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  261. {
  262. if (doc == nullptr)
  263. return false;
  264. if (doc->hasFileBeenModifiedExternally())
  265. doc->reloadFromFile();
  266. if (doc == getCurrentDocument() && contentView != nullptr)
  267. {
  268. if (grabFocus)
  269. contentView->grabKeyboardFocus();
  270. return true;
  271. }
  272. recentDocumentList.newDocumentOpened (doc);
  273. bool opened = setEditorComponent (doc->createEditor(), doc);
  274. if (opened && grabFocus)
  275. contentView->grabKeyboardFocus();
  276. return opened;
  277. }
  278. void ProjectContentComponent::hideEditor()
  279. {
  280. currentDocument = nullptr;
  281. contentView = nullptr;
  282. updateMainWindowTitle();
  283. commandManager->commandStatusChanged();
  284. resized();
  285. }
  286. void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
  287. {
  288. if (doc == currentDocument)
  289. {
  290. OpenDocumentManager::Document* replacement = recentDocumentList.getClosestPreviousDocOtherThan (doc);
  291. if (replacement != nullptr)
  292. showDocument (replacement, true);
  293. else
  294. hideEditor();
  295. }
  296. }
  297. bool ProjectContentComponent::setEditorComponent (Component* editor,
  298. OpenDocumentManager::Document* doc)
  299. {
  300. if (editor != nullptr)
  301. {
  302. contentView = nullptr;
  303. contentView = editor;
  304. currentDocument = doc;
  305. addAndMakeVisible (editor);
  306. resized();
  307. updateMainWindowTitle();
  308. commandManager->commandStatusChanged();
  309. return true;
  310. }
  311. updateMainWindowTitle();
  312. return false;
  313. }
  314. void ProjectContentComponent::closeDocument()
  315. {
  316. if (currentDocument != nullptr)
  317. JucerApplication::getApp().openDocumentManager.closeDocument (currentDocument, true);
  318. else if (contentView != nullptr)
  319. if (! goToPreviousFile())
  320. hideEditor();
  321. }
  322. void ProjectContentComponent::saveDocument()
  323. {
  324. if (currentDocument != nullptr)
  325. currentDocument->save();
  326. else
  327. saveProject();
  328. }
  329. bool ProjectContentComponent::goToPreviousFile()
  330. {
  331. OpenDocumentManager::Document* doc = recentDocumentList.getCurrentDocument();
  332. if (doc == nullptr || doc == getCurrentDocument())
  333. doc = recentDocumentList.getPrevious();
  334. return showDocument (doc, true);
  335. }
  336. bool ProjectContentComponent::goToNextFile()
  337. {
  338. return showDocument (recentDocumentList.getNext(), true);
  339. }
  340. bool ProjectContentComponent::saveProject()
  341. {
  342. return project != nullptr
  343. && project->save (true, true) == FileBasedDocument::savedOk;
  344. }
  345. void ProjectContentComponent::closeProject()
  346. {
  347. MainWindow* const mw = findParentComponentOfClass<MainWindow>();
  348. if (mw != nullptr)
  349. mw->closeCurrentProject();
  350. }
  351. void ProjectContentComponent::openInIDE()
  352. {
  353. if (project != nullptr)
  354. {
  355. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  356. if (exporter != nullptr)
  357. exporter->launchProject();
  358. }
  359. }
  360. void ProjectContentComponent::deleteSelectedTreeItems()
  361. {
  362. TreePanelBase* const tree = dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent());
  363. if (tree != nullptr)
  364. tree->deleteSelectedItems();
  365. }
  366. void ProjectContentComponent::updateMainWindowTitle()
  367. {
  368. MainWindow* mw = findParentComponentOfClass<MainWindow>();
  369. if (mw != nullptr)
  370. mw->updateTitle (currentDocument != nullptr ? currentDocument->getName() : String::empty);
  371. }
  372. void ProjectContentComponent::showBubbleMessage (const Rectangle<int>& pos, const String& text)
  373. {
  374. addChildComponent (&bubbleMessage);
  375. bubbleMessage.setColour (BubbleComponent::backgroundColourId, Colours::white.withAlpha (0.7f));
  376. bubbleMessage.setColour (BubbleComponent::outlineColourId, Colours::black.withAlpha (0.8f));
  377. bubbleMessage.setAlwaysOnTop (true);
  378. bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false);
  379. }
  380. //==============================================================================
  381. ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
  382. {
  383. return findFirstTargetParentComponent();
  384. }
  385. void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
  386. {
  387. const CommandID ids[] = { CommandIDs::saveDocument,
  388. CommandIDs::closeDocument,
  389. CommandIDs::saveProject,
  390. CommandIDs::closeProject,
  391. CommandIDs::openInIDE,
  392. CommandIDs::saveAndOpenInIDE,
  393. CommandIDs::showFilePanel,
  394. CommandIDs::showConfigPanel,
  395. CommandIDs::goToPreviousDoc,
  396. CommandIDs::goToNextDoc,
  397. StandardApplicationCommandIDs::del };
  398. commands.addArray (ids, numElementsInArray (ids));
  399. }
  400. void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  401. {
  402. String documentName;
  403. if (currentDocument != nullptr)
  404. documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
  405. switch (commandID)
  406. {
  407. case CommandIDs::saveProject:
  408. result.setInfo ("Save Project",
  409. "Saves the current project",
  410. CommandCategories::general, 0);
  411. result.setActive (project != nullptr);
  412. break;
  413. case CommandIDs::closeProject:
  414. result.setInfo ("Close Project",
  415. "Closes the current project",
  416. CommandCategories::general, 0);
  417. result.setActive (project != nullptr);
  418. break;
  419. case CommandIDs::saveDocument:
  420. result.setInfo ("Save" + documentName,
  421. "Saves the current document",
  422. CommandCategories::general, 0);
  423. result.setActive (currentDocument != nullptr || project != nullptr);
  424. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  425. break;
  426. case CommandIDs::closeDocument:
  427. result.setInfo ("Close" + documentName,
  428. "Closes the current document",
  429. CommandCategories::general, 0);
  430. result.setActive (contentView != nullptr);
  431. #if JUCE_MAC
  432. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  433. #else
  434. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  435. #endif
  436. break;
  437. case CommandIDs::goToPreviousDoc:
  438. result.setInfo ("Previous Document", "Go to previous document", CommandCategories::general, 0);
  439. result.setActive (recentDocumentList.canGoToPrevious());
  440. #if JUCE_MAC
  441. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  442. #else
  443. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  444. #endif
  445. break;
  446. case CommandIDs::goToNextDoc:
  447. result.setInfo ("Next Document", "Go to next document", CommandCategories::general, 0);
  448. result.setActive (recentDocumentList.canGoToNext());
  449. #if JUCE_MAC
  450. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  451. #else
  452. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  453. #endif
  454. break;
  455. case CommandIDs::openInIDE:
  456. #if JUCE_MAC
  457. result.setInfo ("Open in XCode...",
  458. #elif JUCE_WINDOWS
  459. result.setInfo ("Open in Visual Studio...",
  460. #else
  461. result.setInfo ("Open as a Makefile...",
  462. #endif
  463. "Launches the project in an external IDE",
  464. CommandCategories::general, 0);
  465. result.setActive (ProjectExporter::canProjectBeLaunched (project));
  466. break;
  467. case CommandIDs::saveAndOpenInIDE:
  468. #if JUCE_MAC
  469. result.setInfo ("Save Project and Open in XCode...",
  470. #elif JUCE_WINDOWS
  471. result.setInfo ("Save Project and Open in Visual Studio...",
  472. #else
  473. result.setInfo ("Save Project and Open as a Makefile...",
  474. #endif
  475. "Saves the project and launches it in an external IDE",
  476. CommandCategories::general, 0);
  477. result.setActive (ProjectExporter::canProjectBeLaunched (project));
  478. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
  479. break;
  480. case CommandIDs::showFilePanel:
  481. result.setInfo ("Show File Panel",
  482. "Shows the tree of files for this project",
  483. CommandCategories::general, 0);
  484. result.setActive (project != nullptr);
  485. result.defaultKeypresses.add (KeyPress ('p', ModifierKeys::commandModifier, 0));
  486. break;
  487. case CommandIDs::showConfigPanel:
  488. result.setInfo ("Show Config Panel",
  489. "Shows the build options for the project",
  490. CommandCategories::general, 0);
  491. result.setActive (project != nullptr);
  492. result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier, 0));
  493. break;
  494. case StandardApplicationCommandIDs::del:
  495. result.setInfo ("Delete Selected File", String::empty, CommandCategories::general, 0);
  496. result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0));
  497. result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0));
  498. result.setActive (dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent()) != nullptr);
  499. break;
  500. default:
  501. break;
  502. }
  503. }
  504. bool ProjectContentComponent::isCommandActive (const CommandID commandID)
  505. {
  506. return project != nullptr;
  507. }
  508. bool ProjectContentComponent::perform (const InvocationInfo& info)
  509. {
  510. switch (info.commandID)
  511. {
  512. case CommandIDs::saveProject:
  513. case CommandIDs::closeProject:
  514. case CommandIDs::saveDocument:
  515. case CommandIDs::closeDocument:
  516. case CommandIDs::goToPreviousDoc:
  517. case CommandIDs::goToNextDoc:
  518. case CommandIDs::saveAndOpenInIDE:
  519. if (reinvokeCommandAfterCancellingModalComps (info))
  520. {
  521. grabKeyboardFocus(); // to force any open labels to close their text editors
  522. return true;
  523. }
  524. break;
  525. default:
  526. break;
  527. }
  528. switch (info.commandID)
  529. {
  530. case CommandIDs::saveProject: saveProject(); break;
  531. case CommandIDs::closeProject: closeProject(); break;
  532. case CommandIDs::saveDocument: saveDocument(); break;
  533. case CommandIDs::closeDocument: closeDocument(); break;
  534. case CommandIDs::goToPreviousDoc: goToPreviousFile(); break;
  535. case CommandIDs::goToNextDoc: goToNextFile(); break;
  536. case CommandIDs::showFilePanel: treeViewTabs.setCurrentTabIndex (0); break;
  537. case CommandIDs::showConfigPanel: treeViewTabs.setCurrentTabIndex (1); break;
  538. case CommandIDs::openInIDE: openInIDE(); break;
  539. case StandardApplicationCommandIDs::del: deleteSelectedTreeItems(); break;
  540. case CommandIDs::saveAndOpenInIDE:
  541. if (saveProject())
  542. openInIDE();
  543. break;
  544. default:
  545. return false;
  546. }
  547. return true;
  548. }