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.

622 lines
20KB

  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::saveOpenDocumentList()
  172. {
  173. if (project != nullptr)
  174. {
  175. ScopedPointer<XmlElement> xml (recentDocumentList.createXML());
  176. if (xml != nullptr)
  177. getAppProperties().setValue ("lastDocs_" + project->getProjectUID(), xml);
  178. }
  179. }
  180. void ProjectContentComponent::reloadLastOpenDocuments()
  181. {
  182. if (project != nullptr)
  183. {
  184. ScopedPointer<XmlElement> xml (getAppProperties().getXmlValue ("lastDocs_" + project->getProjectUID()));
  185. if (xml != nullptr)
  186. {
  187. recentDocumentList.restoreFromXML (*project, *xml);
  188. showDocument (recentDocumentList.getCurrentDocument());
  189. }
  190. }
  191. }
  192. void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*)
  193. {
  194. updateMissingFileStatuses();
  195. }
  196. void ProjectContentComponent::updateMissingFileStatuses()
  197. {
  198. ProjectTreeViewBase* p = getFilesTreeRoot();
  199. if (p != nullptr)
  200. p->checkFileStatus();
  201. }
  202. bool ProjectContentComponent::showEditorForFile (const File& f)
  203. {
  204. return getCurrentFile() == f
  205. || showDocument (JucerApplication::getApp().openDocumentManager.openFile (project, f));
  206. }
  207. File ProjectContentComponent::getCurrentFile() const
  208. {
  209. return currentDocument != nullptr ? currentDocument->getFile()
  210. : File::nonexistent;
  211. }
  212. bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc)
  213. {
  214. if (doc == nullptr)
  215. return false;
  216. if (doc->hasFileBeenModifiedExternally())
  217. doc->reloadFromFile();
  218. if (doc == getCurrentDocument() && contentView != nullptr)
  219. {
  220. contentView->grabKeyboardFocus();
  221. return true;
  222. }
  223. recentDocumentList.newDocumentOpened (doc);
  224. return setEditorComponent (doc->createEditor(), doc);
  225. }
  226. void ProjectContentComponent::hideEditor()
  227. {
  228. currentDocument = nullptr;
  229. contentView = nullptr;
  230. updateMainWindowTitle();
  231. commandManager->commandStatusChanged();
  232. }
  233. void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
  234. {
  235. if (doc == currentDocument)
  236. {
  237. OpenDocumentManager::Document* replacement = recentDocumentList.getClosestPreviousDocOtherThan (doc);
  238. if (replacement != nullptr)
  239. showDocument (replacement);
  240. else
  241. hideEditor();
  242. }
  243. }
  244. bool ProjectContentComponent::setEditorComponent (Component* editor, OpenDocumentManager::Document* doc)
  245. {
  246. if (editor != nullptr)
  247. {
  248. contentView = nullptr;
  249. contentView = editor;
  250. currentDocument = doc;
  251. addAndMakeVisible (editor);
  252. resized();
  253. updateMainWindowTitle();
  254. commandManager->commandStatusChanged();
  255. editor->grabKeyboardFocus();
  256. return true;
  257. }
  258. updateMainWindowTitle();
  259. return false;
  260. }
  261. bool ProjectContentComponent::goToPreviousFile()
  262. {
  263. OpenDocumentManager::Document* currentSourceDoc = recentDocumentList.getCurrentDocument();
  264. if (currentSourceDoc != nullptr && currentSourceDoc != getCurrentDocument())
  265. return showDocument (currentSourceDoc);
  266. else
  267. return showDocument (recentDocumentList.getPrevious());
  268. }
  269. bool ProjectContentComponent::goToNextFile()
  270. {
  271. return showDocument (recentDocumentList.getNext());
  272. }
  273. void ProjectContentComponent::updateMainWindowTitle()
  274. {
  275. MainWindow* mw = findParentComponentOfClass<MainWindow>();
  276. if (mw != nullptr)
  277. mw->updateTitle (currentDocument != nullptr ? currentDocument->getName() : String::empty);
  278. }
  279. bool ProjectContentComponent::canProjectBeLaunched() const
  280. {
  281. if (project != nullptr)
  282. {
  283. ScopedPointer <ProjectExporter> launcher (ProjectExporter::createPlatformDefaultExporter (*project));
  284. return launcher != nullptr;
  285. }
  286. return false;
  287. }
  288. ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
  289. {
  290. return findFirstTargetParentComponent();
  291. }
  292. void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
  293. {
  294. const CommandID ids[] = { CommandIDs::saveDocument,
  295. CommandIDs::closeDocument,
  296. CommandIDs::saveProject,
  297. CommandIDs::closeProject,
  298. CommandIDs::openInIDE,
  299. CommandIDs::saveAndOpenInIDE,
  300. CommandIDs::showFilePanel,
  301. CommandIDs::showConfigPanel,
  302. CommandIDs::goToPreviousDoc,
  303. CommandIDs::goToNextDoc,
  304. StandardApplicationCommandIDs::del };
  305. commands.addArray (ids, numElementsInArray (ids));
  306. }
  307. void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  308. {
  309. String documentName;
  310. if (currentDocument != nullptr)
  311. documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
  312. switch (commandID)
  313. {
  314. case CommandIDs::saveProject:
  315. result.setInfo ("Save Project",
  316. "Saves the current project",
  317. CommandCategories::general, 0);
  318. result.setActive (project != nullptr);
  319. break;
  320. case CommandIDs::closeProject:
  321. result.setInfo ("Close Project",
  322. "Closes the current project",
  323. CommandCategories::general, 0);
  324. result.setActive (project != nullptr);
  325. break;
  326. case CommandIDs::saveDocument:
  327. result.setInfo ("Save" + documentName,
  328. "Saves the current document",
  329. CommandCategories::general, 0);
  330. result.setActive (currentDocument != nullptr || project != nullptr);
  331. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  332. break;
  333. case CommandIDs::closeDocument:
  334. result.setInfo ("Close" + documentName,
  335. "Closes the current document",
  336. CommandCategories::general, 0);
  337. result.setActive (currentDocument != nullptr);
  338. #if JUCE_MAC
  339. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  340. #else
  341. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  342. #endif
  343. break;
  344. case CommandIDs::goToPreviousDoc:
  345. result.setInfo ("Previous Document", "Go to previous document", CommandCategories::general, 0);
  346. result.setActive (recentDocumentList.canGoToPrevious());
  347. #if JUCE_MAC
  348. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  349. #else
  350. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  351. #endif
  352. break;
  353. case CommandIDs::goToNextDoc:
  354. result.setInfo ("Next Document", "Go to next document", CommandCategories::general, 0);
  355. result.setActive (recentDocumentList.canGoToNext());
  356. #if JUCE_MAC
  357. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  358. #else
  359. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  360. #endif
  361. break;
  362. case CommandIDs::openInIDE:
  363. #if JUCE_MAC
  364. result.setInfo ("Open in XCode...",
  365. #elif JUCE_WINDOWS
  366. result.setInfo ("Open in Visual Studio...",
  367. #else
  368. result.setInfo ("Open as a Makefile...",
  369. #endif
  370. "Launches the project in an external IDE",
  371. CommandCategories::general, 0);
  372. result.setActive (canProjectBeLaunched());
  373. break;
  374. case CommandIDs::saveAndOpenInIDE:
  375. #if JUCE_MAC
  376. result.setInfo ("Save Project and Open in XCode...",
  377. #elif JUCE_WINDOWS
  378. result.setInfo ("Save Project and Open in Visual Studio...",
  379. #else
  380. result.setInfo ("Save Project and Open as a Makefile...",
  381. #endif
  382. "Saves the project and launches it in an external IDE",
  383. CommandCategories::general, 0);
  384. result.setActive (canProjectBeLaunched());
  385. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
  386. break;
  387. case CommandIDs::showFilePanel:
  388. result.setInfo ("Show File Panel",
  389. "Shows the tree of files for this project",
  390. CommandCategories::general, 0);
  391. result.setActive (project != nullptr);
  392. result.defaultKeypresses.add (KeyPress ('p', ModifierKeys::commandModifier, 0));
  393. break;
  394. case CommandIDs::showConfigPanel:
  395. result.setInfo ("Show Config Panel",
  396. "Shows the build options for the project",
  397. CommandCategories::general, 0);
  398. result.setActive (project != nullptr);
  399. result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier, 0));
  400. break;
  401. case StandardApplicationCommandIDs::del:
  402. result.setInfo ("Delete Selected File", String::empty, CommandCategories::general, 0);
  403. result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0));
  404. result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0));
  405. result.setActive (dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent()) != nullptr);
  406. break;
  407. default:
  408. break;
  409. }
  410. }
  411. bool ProjectContentComponent::isCommandActive (const CommandID commandID)
  412. {
  413. return project != nullptr;
  414. }
  415. bool ProjectContentComponent::perform (const InvocationInfo& info)
  416. {
  417. switch (info.commandID)
  418. {
  419. case CommandIDs::saveProject:
  420. if (project != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  421. project->save (true, true);
  422. break;
  423. case CommandIDs::closeProject:
  424. {
  425. MainWindow* const mw = findParentComponentOfClass<MainWindow>();
  426. if (mw != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  427. mw->closeCurrentProject();
  428. }
  429. break;
  430. case CommandIDs::saveDocument:
  431. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  432. {
  433. if (currentDocument != nullptr)
  434. currentDocument->save();
  435. else if (project != nullptr)
  436. project->save (true, true);
  437. }
  438. break;
  439. case CommandIDs::closeDocument:
  440. if (currentDocument != nullptr)
  441. JucerApplication::getApp().openDocumentManager.closeDocument (currentDocument, true);
  442. break;
  443. case CommandIDs::goToPreviousDoc:
  444. goToPreviousFile();
  445. break;
  446. case CommandIDs::goToNextDoc:
  447. goToNextFile();
  448. break;
  449. case CommandIDs::openInIDE:
  450. if (project != nullptr)
  451. {
  452. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  453. if (exporter != nullptr)
  454. exporter->launchProject();
  455. }
  456. break;
  457. case CommandIDs::saveAndOpenInIDE:
  458. if (project != nullptr)
  459. {
  460. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  461. {
  462. if (project->save (true, true) == FileBasedDocument::savedOk)
  463. {
  464. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  465. if (exporter != nullptr)
  466. exporter->launchProject();
  467. }
  468. }
  469. }
  470. break;
  471. case CommandIDs::showFilePanel:
  472. treeViewTabs.setCurrentTabIndex (0);
  473. break;
  474. case CommandIDs::showConfigPanel:
  475. treeViewTabs.setCurrentTabIndex (1);
  476. break;
  477. case StandardApplicationCommandIDs::del:
  478. {
  479. TreePanelBase* const tree = dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent());
  480. if (tree != nullptr)
  481. tree->deleteSelectedItems();
  482. }
  483. break;
  484. default:
  485. return false;
  486. }
  487. return true;
  488. }
  489. bool ProjectContentComponent::reinvokeCommandAfterClosingPropertyEditors (const InvocationInfo& info)
  490. {
  491. if (reinvokeCommandAfterCancellingModalComps (info))
  492. {
  493. grabKeyboardFocus(); // to force any open labels to close their text editors
  494. return true;
  495. }
  496. return false;
  497. }
  498. void ProjectContentComponent::showBubbleMessage (const Rectangle<int>& pos, const String& text)
  499. {
  500. addChildComponent (&bubbleMessage);
  501. bubbleMessage.setAlwaysOnTop (true);
  502. bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false);
  503. }