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.

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