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.

673 lines
22KB

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