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.

662 lines
22KB

  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. ProjectContentComponent::ProjectContentComponent()
  72. : project (nullptr),
  73. currentDocument (nullptr),
  74. treeViewTabs (TabbedButtonBar::TabsAtTop)
  75. {
  76. setOpaque (true);
  77. setWantsKeyboardFocus (true);
  78. treeSizeConstrainer.setMinimumWidth (200);
  79. treeSizeConstrainer.setMaximumWidth (500);
  80. treeViewTabs.setOutline (0);
  81. treeViewTabs.getTabbedButtonBar().setMinimumTabScaleFactor (0.3);
  82. JucerApplication::getApp().openDocumentManager.addListener (this);
  83. }
  84. ProjectContentComponent::~ProjectContentComponent()
  85. {
  86. JucerApplication::getApp().openDocumentManager.removeListener (this);
  87. setProject (nullptr);
  88. contentView = nullptr;
  89. removeChildComponent (&bubbleMessage);
  90. jassert (getNumChildComponents() <= 1);
  91. }
  92. void ProjectContentComponent::paint (Graphics& g)
  93. {
  94. g.fillAll (findColour (mainBackgroundColourId));
  95. }
  96. void ProjectContentComponent::paintOverChildren (Graphics& g)
  97. {
  98. if (resizerBar != nullptr)
  99. {
  100. const int shadowSize = 15;
  101. const int x = resizerBar->getRight();
  102. ColourGradient cg (Colours::black.withAlpha (0.25f), (float) x, 0,
  103. Colours::transparentBlack, (float) (x - shadowSize), 0, false);
  104. cg.addColour (0.4, Colours::black.withAlpha (0.07f));
  105. cg.addColour (0.6, Colours::black.withAlpha (0.02f));
  106. g.setGradientFill (cg);
  107. g.fillRect (x - shadowSize, 0, shadowSize, getHeight());
  108. }
  109. }
  110. void ProjectContentComponent::resized()
  111. {
  112. Rectangle<int> r (getLocalBounds());
  113. treeViewTabs.setBounds (r.removeFromLeft (treeViewTabs.getWidth()));
  114. if (resizerBar != nullptr)
  115. resizerBar->setBounds (r.removeFromLeft (4));
  116. if (contentView != nullptr)
  117. contentView->setBounds (r);
  118. }
  119. void ProjectContentComponent::lookAndFeelChanged()
  120. {
  121. const Colour tabColour (findColour (mainBackgroundColourId));
  122. for (int i = treeViewTabs.getNumTabs(); --i >= 0;)
  123. treeViewTabs.setTabBackgroundColour (i, tabColour);
  124. repaint();
  125. }
  126. void ProjectContentComponent::childBoundsChanged (Component* child)
  127. {
  128. if (child == &treeViewTabs)
  129. resized();
  130. }
  131. void ProjectContentComponent::setProject (Project* newProject)
  132. {
  133. if (project != newProject)
  134. {
  135. PropertiesFile& settings = getAppProperties();
  136. if (project != nullptr)
  137. project->removeChangeListener (this);
  138. contentView = nullptr;
  139. resizerBar = nullptr;
  140. if (project != nullptr && treeViewTabs.isShowing())
  141. {
  142. if (treeViewTabs.getWidth() > 0)
  143. settings.setValue ("projectTreeviewWidth_" + project->getProjectUID(), treeViewTabs.getWidth());
  144. settings.setValue ("lastTab_" + project->getProjectUID(), treeViewTabs.getCurrentTabName());
  145. }
  146. treeViewTabs.clearTabs();
  147. project = newProject;
  148. if (project != nullptr)
  149. {
  150. addAndMakeVisible (&treeViewTabs);
  151. createProjectTabs();
  152. const String lastTabName (settings.getValue ("lastTab_" + project->getProjectUID()));
  153. int lastTabIndex = treeViewTabs.getTabNames().indexOf (lastTabName);
  154. if (lastTabIndex < 0 || lastTabIndex > treeViewTabs.getNumTabs())
  155. lastTabIndex = 1;
  156. treeViewTabs.setCurrentTabIndex (lastTabIndex);
  157. int lastTreeWidth = settings.getValue ("projectTreeviewWidth_" + project->getProjectUID()).getIntValue();
  158. if (lastTreeWidth < 150)
  159. lastTreeWidth = 240;
  160. treeViewTabs.setBounds (0, 0, lastTreeWidth, getHeight());
  161. addAndMakeVisible (resizerBar = new ResizableEdgeComponent (&treeViewTabs, &treeSizeConstrainer,
  162. ResizableEdgeComponent::rightEdge));
  163. project->addChangeListener (this);
  164. updateMissingFileStatuses();
  165. resized();
  166. }
  167. else
  168. {
  169. treeViewTabs.setVisible (false);
  170. }
  171. }
  172. }
  173. void ProjectContentComponent::createProjectTabs()
  174. {
  175. jassert (project != nullptr);
  176. const Colour tabColour (findColour (mainBackgroundColourId));
  177. treeViewTabs.addTab ("Files", tabColour, new FileTreeTab (*project), true);
  178. treeViewTabs.addTab ("Config", tabColour, new ConfigTreeTab (*project), true);
  179. }
  180. TreeView* ProjectContentComponent::getFilesTreeView() const
  181. {
  182. FileTreeTab* ft = dynamic_cast<FileTreeTab*> (treeViewTabs.getTabContentComponent (0));
  183. return ft != nullptr ? &(ft->tree) : nullptr;
  184. }
  185. ProjectTreeViewBase* ProjectContentComponent::getFilesTreeRoot() const
  186. {
  187. TreeView* tv = getFilesTreeView();
  188. return tv != nullptr ? dynamic_cast <ProjectTreeViewBase*> (tv->getRootItem()) : nullptr;
  189. }
  190. void ProjectContentComponent::saveTreeViewState()
  191. {
  192. for (int i = treeViewTabs.getNumTabs(); --i >= 0;)
  193. {
  194. TreePanelBase* t = dynamic_cast<TreePanelBase*> (treeViewTabs.getTabContentComponent (i));
  195. if (t != nullptr)
  196. t->saveOpenness();
  197. }
  198. }
  199. void ProjectContentComponent::saveOpenDocumentList()
  200. {
  201. if (project != nullptr)
  202. {
  203. ScopedPointer<XmlElement> xml (recentDocumentList.createXML());
  204. if (xml != nullptr)
  205. getAppProperties().setValue ("lastDocs_" + project->getProjectUID(), xml);
  206. }
  207. }
  208. void ProjectContentComponent::reloadLastOpenDocuments()
  209. {
  210. if (project != nullptr)
  211. {
  212. ScopedPointer<XmlElement> xml (getAppProperties().getXmlValue ("lastDocs_" + project->getProjectUID()));
  213. if (xml != nullptr)
  214. {
  215. recentDocumentList.restoreFromXML (*project, *xml);
  216. showDocument (recentDocumentList.getCurrentDocument(), true);
  217. }
  218. }
  219. }
  220. void ProjectContentComponent::documentAboutToClose (OpenDocumentManager::Document* document)
  221. {
  222. hideDocument (document);
  223. }
  224. void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*)
  225. {
  226. updateMissingFileStatuses();
  227. }
  228. void ProjectContentComponent::updateMissingFileStatuses()
  229. {
  230. ProjectTreeViewBase* p = getFilesTreeRoot();
  231. if (p != nullptr)
  232. p->checkFileStatus();
  233. }
  234. bool ProjectContentComponent::showEditorForFile (const File& f, bool grabFocus)
  235. {
  236. return getCurrentFile() == f
  237. || showDocument (JucerApplication::getApp().openDocumentManager.openFile (project, f), grabFocus);
  238. }
  239. File ProjectContentComponent::getCurrentFile() const
  240. {
  241. return currentDocument != nullptr ? currentDocument->getFile()
  242. : File::nonexistent;
  243. }
  244. bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  245. {
  246. if (doc == nullptr)
  247. return false;
  248. if (doc->hasFileBeenModifiedExternally())
  249. doc->reloadFromFile();
  250. if (doc == getCurrentDocument() && contentView != nullptr)
  251. {
  252. if (grabFocus)
  253. contentView->grabKeyboardFocus();
  254. return true;
  255. }
  256. recentDocumentList.newDocumentOpened (doc);
  257. bool opened = setEditorComponent (doc->createEditor(), doc);
  258. if (opened && grabFocus)
  259. contentView->grabKeyboardFocus();
  260. return opened;
  261. }
  262. void ProjectContentComponent::hideEditor()
  263. {
  264. currentDocument = nullptr;
  265. contentView = nullptr;
  266. updateMainWindowTitle();
  267. commandManager->commandStatusChanged();
  268. }
  269. void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
  270. {
  271. if (doc == currentDocument)
  272. {
  273. OpenDocumentManager::Document* replacement = recentDocumentList.getClosestPreviousDocOtherThan (doc);
  274. if (replacement != nullptr)
  275. showDocument (replacement, true);
  276. else
  277. hideEditor();
  278. }
  279. }
  280. bool ProjectContentComponent::setEditorComponent (Component* editor,
  281. OpenDocumentManager::Document* doc)
  282. {
  283. if (editor != nullptr)
  284. {
  285. contentView = nullptr;
  286. contentView = editor;
  287. currentDocument = doc;
  288. addAndMakeVisible (editor);
  289. resized();
  290. updateMainWindowTitle();
  291. commandManager->commandStatusChanged();
  292. return true;
  293. }
  294. updateMainWindowTitle();
  295. return false;
  296. }
  297. bool ProjectContentComponent::goToPreviousFile()
  298. {
  299. OpenDocumentManager::Document* currentSourceDoc = recentDocumentList.getCurrentDocument();
  300. if (currentSourceDoc != nullptr && currentSourceDoc != getCurrentDocument())
  301. return showDocument (currentSourceDoc, true);
  302. else
  303. return showDocument (recentDocumentList.getPrevious(), true);
  304. }
  305. bool ProjectContentComponent::goToNextFile()
  306. {
  307. return showDocument (recentDocumentList.getNext(), true);
  308. }
  309. void ProjectContentComponent::updateMainWindowTitle()
  310. {
  311. MainWindow* mw = findParentComponentOfClass<MainWindow>();
  312. if (mw != nullptr)
  313. mw->updateTitle (currentDocument != nullptr ? currentDocument->getName() : String::empty);
  314. }
  315. ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
  316. {
  317. return findFirstTargetParentComponent();
  318. }
  319. void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
  320. {
  321. const CommandID ids[] = { CommandIDs::saveDocument,
  322. CommandIDs::closeDocument,
  323. CommandIDs::saveProject,
  324. CommandIDs::closeProject,
  325. CommandIDs::openInIDE,
  326. CommandIDs::saveAndOpenInIDE,
  327. CommandIDs::showFilePanel,
  328. CommandIDs::showConfigPanel,
  329. CommandIDs::goToPreviousDoc,
  330. CommandIDs::goToNextDoc,
  331. StandardApplicationCommandIDs::del };
  332. commands.addArray (ids, numElementsInArray (ids));
  333. }
  334. void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  335. {
  336. String documentName;
  337. if (currentDocument != nullptr)
  338. documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
  339. switch (commandID)
  340. {
  341. case CommandIDs::saveProject:
  342. result.setInfo ("Save Project",
  343. "Saves the current project",
  344. CommandCategories::general, 0);
  345. result.setActive (project != nullptr);
  346. break;
  347. case CommandIDs::closeProject:
  348. result.setInfo ("Close Project",
  349. "Closes the current project",
  350. CommandCategories::general, 0);
  351. result.setActive (project != nullptr);
  352. break;
  353. case CommandIDs::saveDocument:
  354. result.setInfo ("Save" + documentName,
  355. "Saves the current document",
  356. CommandCategories::general, 0);
  357. result.setActive (currentDocument != nullptr || project != nullptr);
  358. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  359. break;
  360. case CommandIDs::closeDocument:
  361. result.setInfo ("Close" + documentName,
  362. "Closes the current document",
  363. CommandCategories::general, 0);
  364. result.setActive (currentDocument != nullptr);
  365. #if JUCE_MAC
  366. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  367. #else
  368. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  369. #endif
  370. break;
  371. case CommandIDs::goToPreviousDoc:
  372. result.setInfo ("Previous Document", "Go to previous document", CommandCategories::general, 0);
  373. result.setActive (recentDocumentList.canGoToPrevious());
  374. #if JUCE_MAC
  375. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  376. #else
  377. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  378. #endif
  379. break;
  380. case CommandIDs::goToNextDoc:
  381. result.setInfo ("Next Document", "Go to next document", CommandCategories::general, 0);
  382. result.setActive (recentDocumentList.canGoToNext());
  383. #if JUCE_MAC
  384. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  385. #else
  386. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  387. #endif
  388. break;
  389. case CommandIDs::openInIDE:
  390. #if JUCE_MAC
  391. result.setInfo ("Open in XCode...",
  392. #elif JUCE_WINDOWS
  393. result.setInfo ("Open in Visual Studio...",
  394. #else
  395. result.setInfo ("Open as a Makefile...",
  396. #endif
  397. "Launches the project in an external IDE",
  398. CommandCategories::general, 0);
  399. result.setActive (ProjectExporter::canProjectBeLaunched (project));
  400. break;
  401. case CommandIDs::saveAndOpenInIDE:
  402. #if JUCE_MAC
  403. result.setInfo ("Save Project and Open in XCode...",
  404. #elif JUCE_WINDOWS
  405. result.setInfo ("Save Project and Open in Visual Studio...",
  406. #else
  407. result.setInfo ("Save Project and Open as a Makefile...",
  408. #endif
  409. "Saves the project and launches it in an external IDE",
  410. CommandCategories::general, 0);
  411. result.setActive (ProjectExporter::canProjectBeLaunched (project));
  412. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
  413. break;
  414. case CommandIDs::showFilePanel:
  415. result.setInfo ("Show File Panel",
  416. "Shows the tree of files for this project",
  417. CommandCategories::general, 0);
  418. result.setActive (project != nullptr);
  419. result.defaultKeypresses.add (KeyPress ('p', ModifierKeys::commandModifier, 0));
  420. break;
  421. case CommandIDs::showConfigPanel:
  422. result.setInfo ("Show Config Panel",
  423. "Shows the build options for the project",
  424. CommandCategories::general, 0);
  425. result.setActive (project != nullptr);
  426. result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier, 0));
  427. break;
  428. case StandardApplicationCommandIDs::del:
  429. result.setInfo ("Delete Selected File", String::empty, CommandCategories::general, 0);
  430. result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0));
  431. result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0));
  432. result.setActive (dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent()) != nullptr);
  433. break;
  434. default:
  435. break;
  436. }
  437. }
  438. bool ProjectContentComponent::isCommandActive (const CommandID commandID)
  439. {
  440. return project != nullptr;
  441. }
  442. bool ProjectContentComponent::perform (const InvocationInfo& info)
  443. {
  444. switch (info.commandID)
  445. {
  446. case CommandIDs::saveProject:
  447. if (project != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  448. project->save (true, true);
  449. break;
  450. case CommandIDs::closeProject:
  451. {
  452. MainWindow* const mw = findParentComponentOfClass<MainWindow>();
  453. if (mw != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  454. mw->closeCurrentProject();
  455. }
  456. break;
  457. case CommandIDs::saveDocument:
  458. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  459. {
  460. if (currentDocument != nullptr)
  461. currentDocument->save();
  462. else if (project != nullptr)
  463. project->save (true, true);
  464. }
  465. break;
  466. case CommandIDs::closeDocument:
  467. if (currentDocument != nullptr)
  468. JucerApplication::getApp().openDocumentManager.closeDocument (currentDocument, true);
  469. break;
  470. case CommandIDs::goToPreviousDoc:
  471. goToPreviousFile();
  472. break;
  473. case CommandIDs::goToNextDoc:
  474. goToNextFile();
  475. break;
  476. case CommandIDs::openInIDE:
  477. if (project != nullptr)
  478. {
  479. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  480. if (exporter != nullptr)
  481. exporter->launchProject();
  482. }
  483. break;
  484. case CommandIDs::saveAndOpenInIDE:
  485. if (project != nullptr)
  486. {
  487. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  488. {
  489. if (project->save (true, true) == FileBasedDocument::savedOk)
  490. {
  491. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  492. if (exporter != nullptr)
  493. exporter->launchProject();
  494. }
  495. }
  496. }
  497. break;
  498. case CommandIDs::showFilePanel:
  499. treeViewTabs.setCurrentTabIndex (0);
  500. break;
  501. case CommandIDs::showConfigPanel:
  502. treeViewTabs.setCurrentTabIndex (1);
  503. break;
  504. case StandardApplicationCommandIDs::del:
  505. {
  506. TreePanelBase* const tree = dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent());
  507. if (tree != nullptr)
  508. tree->deleteSelectedItems();
  509. }
  510. break;
  511. default:
  512. return false;
  513. }
  514. return true;
  515. }
  516. bool ProjectContentComponent::reinvokeCommandAfterClosingPropertyEditors (const InvocationInfo& info)
  517. {
  518. if (reinvokeCommandAfterCancellingModalComps (info))
  519. {
  520. grabKeyboardFocus(); // to force any open labels to close their text editors
  521. return true;
  522. }
  523. return false;
  524. }
  525. void ProjectContentComponent::showBubbleMessage (const Rectangle<int>& pos, const String& text)
  526. {
  527. addChildComponent (&bubbleMessage);
  528. bubbleMessage.setAlwaysOnTop (true);
  529. bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false);
  530. }