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.

459 lines
16KB

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