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.

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