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.

539 lines
17KB

  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 "../Code Editor/jucer_SourceCodeEditor.h"
  21. #include "jucer_ProjectInformationComponent.h"
  22. #include "jucer_TreeViewTypes.h"
  23. #include "../Project Saving/jucer_ProjectExporter.h"
  24. //==============================================================================
  25. class TreePanelBase : public Component
  26. {
  27. public:
  28. TreePanelBase (const String& opennessStateKey_)
  29. : opennessStateKey (opennessStateKey_)
  30. {
  31. addAndMakeVisible (&tree);
  32. tree.setRootItemVisible (true);
  33. tree.setDefaultOpenness (true);
  34. tree.setColour (TreeView::backgroundColourId, Colours::transparentBlack);
  35. tree.setIndentSize (14);
  36. }
  37. ~TreePanelBase()
  38. {
  39. tree.setRootItem (nullptr);
  40. }
  41. void setRoot (JucerTreeViewBase* root)
  42. {
  43. rootItem = root;
  44. tree.setRootItem (root);
  45. tree.getRootItem()->setOpen (true);
  46. const ScopedPointer<XmlElement> treeOpenness (StoredSettings::getInstance()->getProps()
  47. .getXmlValue (opennessStateKey));
  48. if (treeOpenness != nullptr)
  49. tree.restoreOpennessState (*treeOpenness, true);
  50. }
  51. void saveOpenness()
  52. {
  53. const ScopedPointer<XmlElement> opennessState (tree.getOpennessState (true));
  54. if (opennessState != nullptr)
  55. StoredSettings::getInstance()->getProps().setValue (opennessStateKey, opennessState);
  56. }
  57. void deleteSelectedItems()
  58. {
  59. if (rootItem != nullptr)
  60. rootItem->deleteAllSelectedItems();
  61. }
  62. void resized()
  63. {
  64. tree.setBounds (getLocalBounds());
  65. }
  66. TreeView tree;
  67. ScopedPointer<JucerTreeViewBase> rootItem;
  68. private:
  69. String opennessStateKey;
  70. };
  71. //==============================================================================
  72. class FileTreeTab : public TreePanelBase
  73. {
  74. public:
  75. FileTreeTab (Project& project)
  76. : TreePanelBase ("treeViewState_" + project.getProjectUID())
  77. {
  78. tree.setMultiSelectEnabled (true);
  79. setRoot (new GroupTreeViewItem (project.getMainGroup()));
  80. }
  81. };
  82. //==============================================================================
  83. class ConfigTreeTab : public TreePanelBase
  84. {
  85. public:
  86. ConfigTreeTab (Project& project)
  87. : TreePanelBase ("settingsTreeViewState_" + project.getProjectUID())
  88. {
  89. tree.setMultiSelectEnabled (false);
  90. setRoot (createProjectConfigTreeViewRoot (project));
  91. if (tree.getNumSelectedItems() == 0)
  92. tree.getRootItem()->setSelected (true, true);
  93. #if JUCE_MAC || JUCE_WINDOWS
  94. addAndMakeVisible (&openProjectButton);
  95. openProjectButton.setCommandToTrigger (commandManager, CommandIDs::openInIDE, true);
  96. openProjectButton.setButtonText (commandManager->getNameOfCommand (CommandIDs::openInIDE));
  97. addAndMakeVisible (&saveAndOpenButton);
  98. saveAndOpenButton.setCommandToTrigger (commandManager, CommandIDs::saveAndOpenInIDE, true);
  99. saveAndOpenButton.setButtonText (commandManager->getNameOfCommand (CommandIDs::saveAndOpenInIDE));
  100. #endif
  101. }
  102. void resized()
  103. {
  104. Rectangle<int> r (getLocalBounds());
  105. r.removeFromBottom (6);
  106. if (saveAndOpenButton.isVisible())
  107. saveAndOpenButton.setBounds (r.removeFromBottom (28).reduced (20, 3));
  108. if (openProjectButton.isVisible())
  109. openProjectButton.setBounds (r.removeFromBottom (28).reduced (20, 3));
  110. tree.setBounds (r);
  111. }
  112. TextButton openProjectButton, saveAndOpenButton;
  113. };
  114. //==============================================================================
  115. ProjectContentComponent::ProjectContentComponent()
  116. : project (nullptr),
  117. currentDocument (nullptr),
  118. treeViewTabs (TabbedButtonBar::TabsAtTop)
  119. {
  120. setOpaque (true);
  121. setWantsKeyboardFocus (true);
  122. treeSizeConstrainer.setMinimumWidth (100);
  123. treeSizeConstrainer.setMaximumWidth (500);
  124. }
  125. ProjectContentComponent::~ProjectContentComponent()
  126. {
  127. setProject (nullptr);
  128. contentView = nullptr;
  129. jassert (getNumChildComponents() <= 1);
  130. }
  131. void ProjectContentComponent::paint (Graphics& g)
  132. {
  133. g.fillAll (Colour::greyLevel (0.8f));
  134. }
  135. void ProjectContentComponent::setProject (Project* newProject)
  136. {
  137. if (project != newProject)
  138. {
  139. PropertiesFile& settings = StoredSettings::getInstance()->getProps();
  140. if (project != nullptr)
  141. project->removeChangeListener (this);
  142. contentView = nullptr;
  143. resizerBar = nullptr;
  144. treeViewTabs.clearTabs();
  145. if (treeViewTabs.isShowing() && treeViewTabs.getWidth() > 0)
  146. settings.setValue ("projectTreeviewWidth", treeViewTabs.getWidth());
  147. project = newProject;
  148. if (project != nullptr)
  149. {
  150. treeViewTabs.setVisible (true);
  151. addChildAndSetID (&treeViewTabs, "tree");
  152. createProjectTabs();
  153. String lastTreeWidth (settings.getValue ("projectTreeviewWidth"));
  154. if (lastTreeWidth.getIntValue() < 150)
  155. lastTreeWidth = "250";
  156. treeViewTabs.setBounds ("0, 0, left + " + lastTreeWidth + ", parent.height");
  157. addChildAndSetID (resizerBar = new ResizableEdgeComponent (&treeViewTabs, &treeSizeConstrainer,
  158. ResizableEdgeComponent::rightEdge),
  159. "resizer");
  160. resizerBar->setBounds ("tree.right, 0, tree.right + 4, parent.height");
  161. project->addChangeListener (this);
  162. if (currentDocument == nullptr)
  163. invokeDirectly (CommandIDs::showProjectSettings, true);
  164. updateMissingFileStatuses();
  165. }
  166. else
  167. {
  168. treeViewTabs.setVisible (false);
  169. }
  170. }
  171. }
  172. void ProjectContentComponent::createProjectTabs()
  173. {
  174. treeViewTabs.addTab ("Files", Colour::greyLevel (0.93f), new FileTreeTab (*project), true);
  175. treeViewTabs.addTab ("Config", Colour::greyLevel (0.93f), new ConfigTreeTab (*project), true);
  176. }
  177. TreeView* ProjectContentComponent::getFilesTreeView() const
  178. {
  179. FileTreeTab* ft = dynamic_cast<FileTreeTab*> (treeViewTabs.getTabContentComponent (0));
  180. return ft != nullptr ? &(ft->tree) : nullptr;
  181. }
  182. ProjectTreeViewBase* ProjectContentComponent::getFilesTreeRoot() const
  183. {
  184. TreeView* tv = getFilesTreeView();
  185. return tv != nullptr ? dynamic_cast <ProjectTreeViewBase*> (tv->getRootItem()) : nullptr;
  186. }
  187. void ProjectContentComponent::saveTreeViewState()
  188. {
  189. for (int i = treeViewTabs.getNumTabs(); --i >= 0;)
  190. {
  191. TreePanelBase* t = dynamic_cast<TreePanelBase*> (treeViewTabs.getTabContentComponent (i));
  192. if (t != nullptr)
  193. t->saveOpenness();
  194. }
  195. }
  196. void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*)
  197. {
  198. updateMissingFileStatuses();
  199. }
  200. void ProjectContentComponent::updateMissingFileStatuses()
  201. {
  202. ProjectTreeViewBase* p = getFilesTreeRoot();
  203. if (p != nullptr)
  204. p->checkFileStatus();
  205. }
  206. bool ProjectContentComponent::showEditorForFile (const File& f)
  207. {
  208. return showDocument (OpenDocumentManager::getInstance()->openFile (project, f));
  209. }
  210. bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc)
  211. {
  212. if (doc == nullptr)
  213. return false;
  214. OpenDocumentManager::getInstance()->moveDocumentToTopOfStack (doc);
  215. if (doc->hasFileBeenModifiedExternally())
  216. doc->reloadFromFile();
  217. return setEditorComponent (doc->createEditor(), doc);
  218. }
  219. void ProjectContentComponent::hideEditor()
  220. {
  221. currentDocument = nullptr;
  222. contentView = nullptr;
  223. updateMainWindowTitle();
  224. commandManager->commandStatusChanged();
  225. }
  226. void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
  227. {
  228. if (doc == currentDocument)
  229. hideEditor();
  230. }
  231. bool ProjectContentComponent::setEditorComponent (Component* editor, OpenDocumentManager::Document* doc)
  232. {
  233. if (editor != nullptr)
  234. {
  235. contentView = editor;
  236. currentDocument = doc;
  237. addAndMakeVisible (editor);
  238. editor->setBounds ("resizer.right, 0, parent.right, parent.height");
  239. updateMainWindowTitle();
  240. commandManager->commandStatusChanged();
  241. return true;
  242. }
  243. updateMainWindowTitle();
  244. return false;
  245. }
  246. void ProjectContentComponent::updateMainWindowTitle()
  247. {
  248. MainWindow* mw = findParentComponentOfClass<MainWindow>();
  249. if (mw != nullptr)
  250. mw->updateTitle (currentDocument != nullptr ? currentDocument->getName() : String::empty);
  251. }
  252. bool ProjectContentComponent::canProjectBeLaunched() const
  253. {
  254. if (project != nullptr)
  255. {
  256. ScopedPointer <ProjectExporter> launcher (ProjectExporter::createPlatformDefaultExporter (*project));
  257. return launcher != nullptr;
  258. }
  259. return false;
  260. }
  261. ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
  262. {
  263. return findFirstTargetParentComponent();
  264. }
  265. void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
  266. {
  267. const CommandID ids[] = { CommandIDs::saveDocument,
  268. CommandIDs::closeDocument,
  269. CommandIDs::saveProject,
  270. CommandIDs::closeProject,
  271. CommandIDs::openInIDE,
  272. CommandIDs::saveAndOpenInIDE,
  273. CommandIDs::showProjectSettings,
  274. StandardApplicationCommandIDs::del };
  275. commands.addArray (ids, numElementsInArray (ids));
  276. }
  277. void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  278. {
  279. String documentName;
  280. if (currentDocument != nullptr)
  281. documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
  282. switch (commandID)
  283. {
  284. case CommandIDs::saveProject:
  285. result.setInfo ("Save Project",
  286. "Saves the current project",
  287. CommandCategories::general, 0);
  288. result.setActive (project != nullptr);
  289. break;
  290. case CommandIDs::closeProject:
  291. result.setInfo ("Close Project",
  292. "Closes the current project",
  293. CommandCategories::general, 0);
  294. result.setActive (project != nullptr);
  295. break;
  296. case CommandIDs::saveDocument:
  297. result.setInfo ("Save" + documentName,
  298. "Saves the current document",
  299. CommandCategories::general, 0);
  300. result.setActive (currentDocument != nullptr || project != nullptr);
  301. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  302. break;
  303. case CommandIDs::closeDocument:
  304. result.setInfo ("Close" + documentName,
  305. "Closes the current document",
  306. CommandCategories::general, 0);
  307. result.setActive (currentDocument != nullptr);
  308. #if JUCE_MAC
  309. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  310. #else
  311. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  312. #endif
  313. break;
  314. case CommandIDs::openInIDE:
  315. #if JUCE_MAC
  316. result.setInfo ("Open in XCode...",
  317. #elif JUCE_WINDOWS
  318. result.setInfo ("Open in Visual Studio...",
  319. #else
  320. result.setInfo ("Open as a Makefile...",
  321. #endif
  322. "Launches the project in an external IDE",
  323. CommandCategories::general, 0);
  324. result.setActive (canProjectBeLaunched());
  325. break;
  326. case CommandIDs::saveAndOpenInIDE:
  327. #if JUCE_MAC
  328. result.setInfo ("Save Project and Open in XCode...",
  329. #elif JUCE_WINDOWS
  330. result.setInfo ("Save Project and Open in Visual Studio...",
  331. #else
  332. result.setInfo ("Save Project and Open as a Makefile...",
  333. #endif
  334. "Saves the project and launches it in an external IDE",
  335. CommandCategories::general, 0);
  336. result.setActive (canProjectBeLaunched());
  337. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
  338. break;
  339. case CommandIDs::showProjectSettings:
  340. result.setInfo ("Show Project Build Settings",
  341. "Shows the build options for the project",
  342. CommandCategories::general, 0);
  343. result.setActive (project != nullptr);
  344. result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  345. break;
  346. case StandardApplicationCommandIDs::del:
  347. result.setInfo ("Delete", String::empty, CommandCategories::general, 0);
  348. result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0));
  349. result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0));
  350. result.setActive (dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent()) != nullptr);
  351. break;
  352. default:
  353. break;
  354. }
  355. }
  356. bool ProjectContentComponent::isCommandActive (const CommandID commandID)
  357. {
  358. return project != nullptr;
  359. }
  360. bool ProjectContentComponent::perform (const InvocationInfo& info)
  361. {
  362. switch (info.commandID)
  363. {
  364. case CommandIDs::saveProject:
  365. if (project != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  366. project->save (true, true);
  367. break;
  368. case CommandIDs::closeProject:
  369. {
  370. MainWindow* const mw = findParentComponentOfClass<MainWindow>();
  371. if (mw != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  372. mw->closeCurrentProject();
  373. }
  374. break;
  375. case CommandIDs::saveDocument:
  376. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  377. {
  378. if (currentDocument != nullptr)
  379. currentDocument->save();
  380. else if (project != nullptr)
  381. project->save (true, true);
  382. }
  383. break;
  384. case CommandIDs::closeDocument:
  385. if (currentDocument != nullptr)
  386. OpenDocumentManager::getInstance()->closeDocument (currentDocument, true);
  387. break;
  388. case CommandIDs::openInIDE:
  389. if (project != nullptr)
  390. {
  391. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  392. if (exporter != nullptr)
  393. exporter->launchProject();
  394. }
  395. break;
  396. case CommandIDs::saveAndOpenInIDE:
  397. if (project != nullptr)
  398. {
  399. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  400. {
  401. if (project->save (true, true) == FileBasedDocument::savedOk)
  402. {
  403. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  404. if (exporter != nullptr)
  405. exporter->launchProject();
  406. }
  407. }
  408. }
  409. break;
  410. case CommandIDs::showProjectSettings:
  411. treeViewTabs.setCurrentTabIndex (1);
  412. break;
  413. case StandardApplicationCommandIDs::del:
  414. {
  415. TreePanelBase* const tree = dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent());
  416. if (tree != nullptr)
  417. tree->deleteSelectedItems();
  418. }
  419. break;
  420. default:
  421. return false;
  422. }
  423. return true;
  424. }
  425. bool ProjectContentComponent::reinvokeCommandAfterClosingPropertyEditors (const InvocationInfo& info)
  426. {
  427. if (reinvokeCommandAfterCancellingModalComps (info))
  428. {
  429. grabKeyboardFocus(); // to force any open labels to close their text editors
  430. return true;
  431. }
  432. return false;
  433. }