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.

645 lines
21KB

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