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.

461 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../jucer_Headers.h"
  20. #include "jucer_TreeItemTypes.h"
  21. //==============================================================================
  22. class ConcertinaHeader : public Component,
  23. public ChangeBroadcaster
  24. {
  25. public:
  26. ConcertinaHeader (String n, Path p)
  27. : Component (n), name (n), iconPath (p)
  28. {
  29. panelIcon = Icon (iconPath, Colours::white);
  30. nameLabel.setText (name, dontSendNotification);
  31. nameLabel.setJustificationType (Justification::centredLeft);
  32. nameLabel.setInterceptsMouseClicks (false, false);
  33. nameLabel.setColour (Label::textColourId, Colours::white);
  34. addAndMakeVisible (nameLabel);
  35. }
  36. void resized() override
  37. {
  38. auto b = getLocalBounds().toFloat();
  39. iconBounds = b.removeFromLeft (b.getHeight()).reduced (7, 7);
  40. arrowBounds = b.removeFromRight (b.getHeight());
  41. nameLabel.setBounds (b.toNearestInt());
  42. }
  43. void paint (Graphics& g) override
  44. {
  45. g.setColour (findColour (defaultButtonBackgroundColourId));
  46. g.fillRoundedRectangle (getLocalBounds().reduced (2, 3).toFloat(), 2.0f);
  47. g.setColour (Colours::white);
  48. g.fillPath (arrowPath = ProjucerLookAndFeel::getArrowPath (arrowBounds,
  49. getParentComponent()->getBoundsInParent().getY() == yPosition ? 2 : 0,
  50. true, Justification::centred));
  51. panelIcon.draw (g, iconBounds.toFloat(), false);
  52. }
  53. void mouseUp (const MouseEvent& e) override
  54. {
  55. if (arrowPath.getBounds().expanded (3).contains (e.getPosition().toFloat()))
  56. sendChangeMessage();
  57. }
  58. int direction = 0;
  59. int yPosition = 0;
  60. private:
  61. String name;
  62. Label nameLabel;
  63. Path iconPath;
  64. Icon panelIcon;
  65. Rectangle<float> arrowBounds, iconBounds;
  66. Path arrowPath;
  67. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaHeader)
  68. };
  69. //==============================================================================
  70. class ExportersTreeRoot : public JucerTreeViewBase,
  71. private ValueTree::Listener
  72. {
  73. public:
  74. ExportersTreeRoot (Project& p)
  75. : project (p),
  76. exportersTree (project.getExporters())
  77. {
  78. exportersTree.addListener (this);
  79. }
  80. bool isRoot() const override { return true; }
  81. bool canBeSelected() const override { return true; }
  82. bool isMissing() const override { return false; }
  83. bool mightContainSubItems() override { return project.getNumExporters() > 0; }
  84. String getUniqueName() const override { return "exporters"; }
  85. String getRenamingName() const override { return getDisplayName(); }
  86. String getDisplayName() const override { return "Exporters"; }
  87. void setName (const String&) override {}
  88. Icon getIcon() const override { return project.getMainGroup().getIcon (isOpen()).withColour (getContentColour (true)); }
  89. void showPopupMenu() override
  90. {
  91. if (auto* pcc = getProjectContentComponent())
  92. pcc->showNewExporterMenu();
  93. }
  94. void addSubItems() override
  95. {
  96. int i = 0;
  97. for (Project::ExporterIterator exporter (project); exporter.next(); ++i)
  98. addSubItem (new ConfigTreeItemTypes::ExporterItem (project, exporter.exporter.release(), i));
  99. }
  100. bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
  101. {
  102. return dragSourceDetails.description.toString().startsWith (getUniqueName());
  103. }
  104. void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
  105. {
  106. int oldIndex = dragSourceDetails.description.toString().getTrailingIntValue();
  107. exportersTree.moveChild (oldIndex, jmax (0, insertIndex), project.getUndoManagerFor (exportersTree));
  108. }
  109. void itemOpennessChanged (bool isNowOpen) override
  110. {
  111. if (isNowOpen)
  112. refreshSubItems();
  113. }
  114. void removeExporter (int index)
  115. {
  116. if (auto* exporter = dynamic_cast<ConfigTreeItemTypes::ExporterItem*> (getSubItem (index)))
  117. exporter->deleteItem();
  118. }
  119. private:
  120. Project& project;
  121. ValueTree exportersTree;
  122. //==========================================================================
  123. void valueTreePropertyChanged (ValueTree&, const Identifier&) override {}
  124. void valueTreeParentChanged (ValueTree&) override {}
  125. void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { refreshIfNeeded (parentTree); }
  126. void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { refreshIfNeeded (parentTree); }
  127. void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { refreshIfNeeded (parentTree); }
  128. void refreshIfNeeded (ValueTree& changedTree)
  129. {
  130. if (changedTree == exportersTree)
  131. refreshSubItems();
  132. }
  133. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExportersTreeRoot)
  134. };
  135. struct FileTreePanel : public TreePanelBase
  136. {
  137. FileTreePanel (Project& p)
  138. : TreePanelBase (&p, "fileTreeState")
  139. {
  140. tree.setMultiSelectEnabled (true);
  141. setRoot (new FileTreeItemTypes::GroupItem (p.getMainGroup()));
  142. tree.setRootItemVisible (false);
  143. }
  144. void updateMissingFileStatuses()
  145. {
  146. if (auto* p = dynamic_cast<FileTreeItemTypes::ProjectTreeItemBase*> (rootItem.get()))
  147. p->checkFileStatus();
  148. }
  149. };
  150. struct ModuleTreePanel : public TreePanelBase
  151. {
  152. ModuleTreePanel (Project& p)
  153. : TreePanelBase (&p, "moduleTreeState")
  154. {
  155. tree.setMultiSelectEnabled (false);
  156. setRoot (new ConfigTreeItemTypes::EnabledModulesItem (p));
  157. tree.setRootItemVisible (false);
  158. }
  159. };
  160. struct ExportersTreePanel : public TreePanelBase
  161. {
  162. ExportersTreePanel (Project& p)
  163. : TreePanelBase (&p, "exportersTreeState")
  164. {
  165. tree.setMultiSelectEnabled (false);
  166. setRoot (new ExportersTreeRoot (p));
  167. tree.setRootItemVisible (false);
  168. }
  169. void deleteSelectedItems() override
  170. {
  171. for (int i = rootItem->getNumSubItems() - 1; i >= 0; --i)
  172. if (rootItem->getSubItem (i)->isSelected())
  173. if (auto* root = dynamic_cast<ExportersTreeRoot*> (rootItem.get()))
  174. root->removeExporter (i);
  175. }
  176. };
  177. //==============================================================================
  178. class ConcertinaTreeComponent : public Component,
  179. private Button::Listener
  180. {
  181. public:
  182. ConcertinaTreeComponent (TreePanelBase* tree, bool showSettings = false)
  183. : treeToDisplay (tree),
  184. showSettingsButton (showSettings)
  185. {
  186. addAndMakeVisible (popupMenuButton = new IconButton ("Add", &getIcons().plus));
  187. popupMenuButton->addListener (this);
  188. if (showSettingsButton)
  189. {
  190. addAndMakeVisible (settingsButton = new IconButton ("Settings", &getIcons().settings));
  191. settingsButton->addListener (this);
  192. }
  193. addAndMakeVisible (treeToDisplay);
  194. }
  195. ~ConcertinaTreeComponent()
  196. {
  197. treeToDisplay = nullptr;
  198. popupMenuButton = nullptr;
  199. }
  200. void resized() override
  201. {
  202. auto bounds = getLocalBounds();
  203. auto bottomSlice = bounds.removeFromBottom (25);
  204. bottomSlice.removeFromRight (5);
  205. popupMenuButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
  206. if (showSettingsButton)
  207. settingsButton->setBounds (bottomSlice.removeFromRight(25).reduced (2));
  208. treeToDisplay->setBounds (bounds);
  209. }
  210. TreePanelBase* getTree() const noexcept { return treeToDisplay.get(); }
  211. private:
  212. ScopedPointer<TreePanelBase> treeToDisplay;
  213. ScopedPointer<IconButton> popupMenuButton, settingsButton;
  214. bool showSettingsButton;
  215. void buttonClicked (Button* b) override
  216. {
  217. if (b == popupMenuButton)
  218. {
  219. auto numSelected = treeToDisplay->tree.getNumSelectedItems();
  220. if (numSelected > 1)
  221. return;
  222. if (numSelected == 0)
  223. {
  224. if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
  225. root->showPopupMenu();
  226. }
  227. else
  228. {
  229. auto* selectedItem = treeToDisplay->tree.getSelectedItem (0);
  230. if (auto* fileItem = dynamic_cast<FileTreeItemTypes::ProjectTreeItemBase*> (selectedItem))
  231. fileItem->showPlusMenu();
  232. else if (auto* exporterItem = dynamic_cast<ConfigTreeItemTypes::ExporterItem*> (selectedItem))
  233. exporterItem->showPlusMenu();
  234. }
  235. }
  236. else if (b == settingsButton)
  237. {
  238. if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
  239. {
  240. treeToDisplay->tree.clearSelectedItems();
  241. root->showDocument();
  242. }
  243. }
  244. }
  245. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaTreeComponent)
  246. };
  247. //==============================================================================
  248. class ProjectTab : public Component,
  249. private ChangeListener
  250. {
  251. public:
  252. ProjectTab (Project* p)
  253. : project (p)
  254. {
  255. addAndMakeVisible (concertinaPanel);
  256. buildConcertina();
  257. }
  258. ~ProjectTab()
  259. {
  260. getFileTreePanel()->saveOpenness();
  261. getModuleTreePanel()->saveOpenness();
  262. getExportersTreePanel()->saveOpenness();
  263. }
  264. void paint (Graphics& g) override
  265. {
  266. g.fillAll (findColour (secondaryBackgroundColourId));
  267. }
  268. void resized() override
  269. {
  270. concertinaPanel.setBounds (getLocalBounds());
  271. }
  272. TreePanelBase* getTreeWithSelectedItems()
  273. {
  274. for (int i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
  275. {
  276. if (auto* treeComponent = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (i)))
  277. {
  278. if (auto* base = treeComponent->getTree())
  279. if (base->tree.getNumSelectedItems() != 0)
  280. return base;
  281. }
  282. }
  283. return nullptr;
  284. }
  285. FileTreePanel* getFileTreePanel()
  286. {
  287. if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (0)))
  288. return dynamic_cast<FileTreePanel*> (panel->getTree());
  289. return nullptr;
  290. }
  291. ModuleTreePanel* getModuleTreePanel()
  292. {
  293. if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (1)))
  294. return dynamic_cast<ModuleTreePanel*> (panel->getTree());
  295. return nullptr;
  296. }
  297. ExportersTreePanel* getExportersTreePanel()
  298. {
  299. if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (2)))
  300. return dynamic_cast<ExportersTreePanel*> (panel->getTree());
  301. return nullptr;
  302. }
  303. void showPanel (int panelIndex)
  304. {
  305. jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
  306. concertinaPanel.expandPanelFully (concertinaPanel.getPanel (panelIndex), true);
  307. }
  308. void setPanelHeightProportion (int panelIndex, float prop)
  309. {
  310. jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
  311. concertinaPanel.setPanelSize (concertinaPanel.getPanel (panelIndex),
  312. roundToInt (prop * (concertinaPanel.getHeight() - 90)), false);
  313. }
  314. float getPanelHeightProportion (int panelIndex)
  315. {
  316. jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
  317. return ((float) (concertinaPanel.getPanel (panelIndex)->getHeight()) / (concertinaPanel.getHeight() - 90));
  318. }
  319. private:
  320. ConcertinaPanel concertinaPanel;
  321. OwnedArray<ConcertinaHeader> headers;
  322. Project* project = nullptr;
  323. //==============================================================================
  324. void changeListenerCallback (ChangeBroadcaster* source) override
  325. {
  326. if (auto* header = dynamic_cast<ConcertinaHeader*> (source))
  327. {
  328. auto index = headers.indexOf (header);
  329. concertinaPanel.expandPanelFully (concertinaPanel.getPanel (index), true);
  330. }
  331. }
  332. void buildConcertina()
  333. {
  334. for (int i = concertinaPanel.getNumPanels() - 1; i >= 0 ; --i)
  335. concertinaPanel.removePanel (concertinaPanel.getPanel (i));
  336. headers.clear();
  337. if (project != nullptr)
  338. {
  339. concertinaPanel.addPanel (0, new ConcertinaTreeComponent (new FileTreePanel (*project)), true);
  340. concertinaPanel.addPanel (1, new ConcertinaTreeComponent (new ModuleTreePanel (*project), true), true);
  341. concertinaPanel.addPanel (2, new ConcertinaTreeComponent (new ExportersTreePanel (*project)), true);
  342. }
  343. headers.add (new ConcertinaHeader ("File explorer", getIcons().fileExplorer));
  344. headers.add (new ConcertinaHeader ("Modules", getIcons().modules));
  345. headers.add (new ConcertinaHeader ("Exporters", getIcons().exporter));
  346. for (int i = 0; i < concertinaPanel.getNumPanels(); ++i)
  347. {
  348. auto* p = concertinaPanel.getPanel (i);
  349. auto* h = headers.getUnchecked (i);
  350. p->addMouseListener (this, true);
  351. h->addChangeListener (this);
  352. h->yPosition = i * 30;
  353. concertinaPanel.setCustomPanelHeader (p, h, false);
  354. concertinaPanel.setPanelHeaderSize (p, 30);
  355. }
  356. }
  357. void mouseDown (const MouseEvent& e) override
  358. {
  359. for (int i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
  360. {
  361. auto* p = concertinaPanel.getPanel (i);
  362. if (! (p->isParentOf (e.eventComponent)))
  363. {
  364. auto* base = dynamic_cast<TreePanelBase*> (p);
  365. if (base == nullptr)
  366. base = dynamic_cast<ConcertinaTreeComponent*> (p)->getTree();
  367. if (base != nullptr)
  368. base->tree.clearSelectedItems();
  369. }
  370. }
  371. }
  372. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectTab)
  373. };