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.

577 lines
19KB

  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. removeChildComponent (&bubbleMessage);
  84. jassert (getNumChildComponents() <= 1);
  85. }
  86. void ProjectContentComponent::paint (Graphics& g)
  87. {
  88. g.fillAll (Colour::greyLevel (0.8f));
  89. }
  90. void ProjectContentComponent::resized()
  91. {
  92. Rectangle<int> r (getLocalBounds());
  93. treeViewTabs.setBounds (r.removeFromLeft (treeViewTabs.getWidth()));
  94. if (resizerBar != nullptr)
  95. resizerBar->setBounds (r.removeFromLeft (4));
  96. if (contentView != nullptr)
  97. contentView->setBounds (r);
  98. }
  99. void ProjectContentComponent::childBoundsChanged (Component* child)
  100. {
  101. if (child == &treeViewTabs)
  102. resized();
  103. }
  104. void ProjectContentComponent::setProject (Project* newProject)
  105. {
  106. if (project != newProject)
  107. {
  108. PropertiesFile& settings = getAppProperties();
  109. if (project != nullptr)
  110. project->removeChangeListener (this);
  111. contentView = nullptr;
  112. resizerBar = nullptr;
  113. if (project != nullptr && treeViewTabs.isShowing())
  114. {
  115. if (treeViewTabs.getWidth() > 0)
  116. settings.setValue ("projectTreeviewWidth_" + project->getProjectUID(), treeViewTabs.getWidth());
  117. settings.setValue ("lastTab_" + project->getProjectUID(), treeViewTabs.getCurrentTabName());
  118. }
  119. treeViewTabs.clearTabs();
  120. project = newProject;
  121. if (project != nullptr)
  122. {
  123. addAndMakeVisible (&treeViewTabs);
  124. createProjectTabs();
  125. const String lastTabName (settings.getValue ("lastTab_" + project->getProjectUID()));
  126. int lastTabIndex = treeViewTabs.getTabNames().indexOf (lastTabName);
  127. if (lastTabIndex < 0 || lastTabIndex > treeViewTabs.getNumTabs())
  128. lastTabIndex = 1;
  129. treeViewTabs.setCurrentTabIndex (lastTabIndex);
  130. int lastTreeWidth = settings.getValue ("projectTreeviewWidth_" + project->getProjectUID()).getIntValue();
  131. if (lastTreeWidth < 150)
  132. lastTreeWidth = 250;
  133. treeViewTabs.setBounds (0, 0, lastTreeWidth, getHeight());
  134. addAndMakeVisible (resizerBar = new ResizableEdgeComponent (&treeViewTabs, &treeSizeConstrainer,
  135. ResizableEdgeComponent::rightEdge));
  136. project->addChangeListener (this);
  137. updateMissingFileStatuses();
  138. resized();
  139. }
  140. else
  141. {
  142. treeViewTabs.setVisible (false);
  143. }
  144. }
  145. }
  146. void ProjectContentComponent::createProjectTabs()
  147. {
  148. jassert (project != nullptr);
  149. treeViewTabs.addTab ("Files", Colour::greyLevel (0.93f), new FileTreeTab (*project), true);
  150. treeViewTabs.addTab ("Config", Colour::greyLevel (0.93f), new ConfigTreeTab (*project), true);
  151. }
  152. TreeView* ProjectContentComponent::getFilesTreeView() const
  153. {
  154. FileTreeTab* ft = dynamic_cast<FileTreeTab*> (treeViewTabs.getTabContentComponent (0));
  155. return ft != nullptr ? &(ft->tree) : nullptr;
  156. }
  157. ProjectTreeViewBase* ProjectContentComponent::getFilesTreeRoot() const
  158. {
  159. TreeView* tv = getFilesTreeView();
  160. return tv != nullptr ? dynamic_cast <ProjectTreeViewBase*> (tv->getRootItem()) : nullptr;
  161. }
  162. void ProjectContentComponent::saveTreeViewState()
  163. {
  164. for (int i = treeViewTabs.getNumTabs(); --i >= 0;)
  165. {
  166. TreePanelBase* t = dynamic_cast<TreePanelBase*> (treeViewTabs.getTabContentComponent (i));
  167. if (t != nullptr)
  168. t->saveOpenness();
  169. }
  170. }
  171. void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*)
  172. {
  173. updateMissingFileStatuses();
  174. }
  175. void ProjectContentComponent::updateMissingFileStatuses()
  176. {
  177. ProjectTreeViewBase* p = getFilesTreeRoot();
  178. if (p != nullptr)
  179. p->checkFileStatus();
  180. }
  181. bool ProjectContentComponent::showEditorForFile (const File& f)
  182. {
  183. return getCurrentFile() == f
  184. || showDocument (JucerApplication::getApp()->openDocumentManager.openFile (project, f));
  185. }
  186. File ProjectContentComponent::getCurrentFile() const
  187. {
  188. return currentDocument != nullptr ? currentDocument->getFile()
  189. : File::nonexistent;
  190. }
  191. bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc)
  192. {
  193. if (doc == nullptr)
  194. return false;
  195. if (doc->hasFileBeenModifiedExternally())
  196. doc->reloadFromFile();
  197. if (doc == getCurrentDocument())
  198. return true;
  199. recentDocumentList.newDocumentOpened (doc);
  200. return setEditorComponent (doc->createEditor(), doc);
  201. }
  202. void ProjectContentComponent::hideEditor()
  203. {
  204. currentDocument = nullptr;
  205. contentView = nullptr;
  206. updateMainWindowTitle();
  207. commandManager->commandStatusChanged();
  208. recentDocumentList.clear();
  209. }
  210. void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
  211. {
  212. if (doc == currentDocument)
  213. {
  214. OpenDocumentManager::Document* replacement = recentDocumentList.getClosestPreviousDocOtherThan (doc);
  215. if (replacement != nullptr)
  216. showDocument (replacement);
  217. else
  218. hideEditor();
  219. }
  220. }
  221. bool ProjectContentComponent::setEditorComponent (Component* editor, OpenDocumentManager::Document* doc)
  222. {
  223. if (editor != nullptr)
  224. {
  225. contentView = nullptr;
  226. contentView = editor;
  227. currentDocument = doc;
  228. addAndMakeVisible (editor);
  229. resized();
  230. updateMainWindowTitle();
  231. commandManager->commandStatusChanged();
  232. editor->grabKeyboardFocus();
  233. return true;
  234. }
  235. updateMainWindowTitle();
  236. return false;
  237. }
  238. bool ProjectContentComponent::goToPreviousFile()
  239. {
  240. return showDocument (recentDocumentList.getPrevious());
  241. }
  242. bool ProjectContentComponent::goToNextFile()
  243. {
  244. return showDocument (recentDocumentList.getNext());
  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. CommandIDs::goToPreviousDoc,
  275. CommandIDs::goToNextDoc,
  276. StandardApplicationCommandIDs::del };
  277. commands.addArray (ids, numElementsInArray (ids));
  278. }
  279. void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  280. {
  281. String documentName;
  282. if (currentDocument != nullptr)
  283. documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
  284. switch (commandID)
  285. {
  286. case CommandIDs::saveProject:
  287. result.setInfo ("Save Project",
  288. "Saves the current project",
  289. CommandCategories::general, 0);
  290. result.setActive (project != nullptr);
  291. break;
  292. case CommandIDs::closeProject:
  293. result.setInfo ("Close Project",
  294. "Closes the current project",
  295. CommandCategories::general, 0);
  296. result.setActive (project != nullptr);
  297. break;
  298. case CommandIDs::saveDocument:
  299. result.setInfo ("Save" + documentName,
  300. "Saves the current document",
  301. CommandCategories::general, 0);
  302. result.setActive (currentDocument != nullptr || project != nullptr);
  303. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  304. break;
  305. case CommandIDs::closeDocument:
  306. result.setInfo ("Close" + documentName,
  307. "Closes the current document",
  308. CommandCategories::general, 0);
  309. result.setActive (currentDocument != nullptr);
  310. #if JUCE_MAC
  311. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  312. #else
  313. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  314. #endif
  315. break;
  316. case CommandIDs::goToPreviousDoc:
  317. result.setInfo ("Previous Document", "Go to previous document", CommandCategories::general, 0);
  318. result.setActive (recentDocumentList.canGoToPrevious());
  319. #if JUCE_MAC
  320. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  321. #else
  322. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  323. #endif
  324. break;
  325. case CommandIDs::goToNextDoc:
  326. result.setInfo ("Next Document", "Go to next document", CommandCategories::general, 0);
  327. result.setActive (recentDocumentList.canGoToNext());
  328. #if JUCE_MAC
  329. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  330. #else
  331. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  332. #endif
  333. break;
  334. case CommandIDs::openInIDE:
  335. #if JUCE_MAC
  336. result.setInfo ("Open in XCode...",
  337. #elif JUCE_WINDOWS
  338. result.setInfo ("Open in Visual Studio...",
  339. #else
  340. result.setInfo ("Open as a Makefile...",
  341. #endif
  342. "Launches the project in an external IDE",
  343. CommandCategories::general, 0);
  344. result.setActive (canProjectBeLaunched());
  345. break;
  346. case CommandIDs::saveAndOpenInIDE:
  347. #if JUCE_MAC
  348. result.setInfo ("Save Project and Open in XCode...",
  349. #elif JUCE_WINDOWS
  350. result.setInfo ("Save Project and Open in Visual Studio...",
  351. #else
  352. result.setInfo ("Save Project and Open as a Makefile...",
  353. #endif
  354. "Saves the project and launches it in an external IDE",
  355. CommandCategories::general, 0);
  356. result.setActive (canProjectBeLaunched());
  357. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
  358. break;
  359. case CommandIDs::showProjectSettings:
  360. result.setInfo ("Show Project Build Settings",
  361. "Shows the build options for the project",
  362. CommandCategories::general, 0);
  363. result.setActive (project != nullptr);
  364. result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  365. break;
  366. case StandardApplicationCommandIDs::del:
  367. result.setInfo ("Delete", String::empty, CommandCategories::general, 0);
  368. result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0));
  369. result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0));
  370. result.setActive (dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent()) != nullptr);
  371. break;
  372. default:
  373. break;
  374. }
  375. }
  376. bool ProjectContentComponent::isCommandActive (const CommandID commandID)
  377. {
  378. return project != nullptr;
  379. }
  380. bool ProjectContentComponent::perform (const InvocationInfo& info)
  381. {
  382. switch (info.commandID)
  383. {
  384. case CommandIDs::saveProject:
  385. if (project != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  386. project->save (true, true);
  387. break;
  388. case CommandIDs::closeProject:
  389. {
  390. MainWindow* const mw = findParentComponentOfClass<MainWindow>();
  391. if (mw != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  392. mw->closeCurrentProject();
  393. }
  394. break;
  395. case CommandIDs::saveDocument:
  396. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  397. {
  398. if (currentDocument != nullptr)
  399. currentDocument->save();
  400. else if (project != nullptr)
  401. project->save (true, true);
  402. }
  403. break;
  404. case CommandIDs::closeDocument:
  405. if (currentDocument != nullptr)
  406. JucerApplication::getApp()->openDocumentManager.closeDocument (currentDocument, true);
  407. break;
  408. case CommandIDs::goToPreviousDoc:
  409. goToPreviousFile();
  410. break;
  411. case CommandIDs::goToNextDoc:
  412. goToNextFile();
  413. break;
  414. case CommandIDs::openInIDE:
  415. if (project != nullptr)
  416. {
  417. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  418. if (exporter != nullptr)
  419. exporter->launchProject();
  420. }
  421. break;
  422. case CommandIDs::saveAndOpenInIDE:
  423. if (project != nullptr)
  424. {
  425. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  426. {
  427. if (project->save (true, true) == FileBasedDocument::savedOk)
  428. {
  429. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  430. if (exporter != nullptr)
  431. exporter->launchProject();
  432. }
  433. }
  434. }
  435. break;
  436. case CommandIDs::showProjectSettings:
  437. treeViewTabs.setCurrentTabIndex (1);
  438. break;
  439. case StandardApplicationCommandIDs::del:
  440. {
  441. TreePanelBase* const tree = dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent());
  442. if (tree != nullptr)
  443. tree->deleteSelectedItems();
  444. }
  445. break;
  446. default:
  447. return false;
  448. }
  449. return true;
  450. }
  451. bool ProjectContentComponent::reinvokeCommandAfterClosingPropertyEditors (const InvocationInfo& info)
  452. {
  453. if (reinvokeCommandAfterCancellingModalComps (info))
  454. {
  455. grabKeyboardFocus(); // to force any open labels to close their text editors
  456. return true;
  457. }
  458. return false;
  459. }
  460. void ProjectContentComponent::showBubbleMessage (const Rectangle<int>& pos, const String& text)
  461. {
  462. addChildComponent (&bubbleMessage);
  463. bubbleMessage.setAlwaysOnTop (true);
  464. bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false);
  465. }