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.

482 lines
16KB

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