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.

568 lines
18KB

  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. void setSearchFilter (const String& filter)
  150. {
  151. if (auto* p = dynamic_cast<FileTreeItemTypes::GroupItem*> (rootItem.get()))
  152. p->setSearchFilter (filter);
  153. }
  154. };
  155. struct ModuleTreePanel : public TreePanelBase
  156. {
  157. ModuleTreePanel (Project& p)
  158. : TreePanelBase (&p, "moduleTreeState")
  159. {
  160. tree.setMultiSelectEnabled (false);
  161. setRoot (new ConfigTreeItemTypes::EnabledModulesItem (p));
  162. tree.setRootItemVisible (false);
  163. }
  164. };
  165. struct ExportersTreePanel : public TreePanelBase
  166. {
  167. ExportersTreePanel (Project& p)
  168. : TreePanelBase (&p, "exportersTreeState")
  169. {
  170. tree.setMultiSelectEnabled (false);
  171. setRoot (new ExportersTreeRoot (p));
  172. tree.setRootItemVisible (false);
  173. }
  174. void deleteSelectedItems() override
  175. {
  176. for (int i = rootItem->getNumSubItems() - 1; i >= 0; --i)
  177. if (rootItem->getSubItem (i)->isSelected())
  178. if (auto* root = dynamic_cast<ExportersTreeRoot*> (rootItem.get()))
  179. root->removeExporter (i);
  180. }
  181. };
  182. //==============================================================================
  183. class ConcertinaTreeComponent : public Component,
  184. private Button::Listener,
  185. private ChangeListener
  186. {
  187. public:
  188. ConcertinaTreeComponent (TreePanelBase* tree, bool showSettingsButton = false, bool showFindPanel = false)
  189. : treeToDisplay (tree)
  190. {
  191. addAndMakeVisible (popupMenuButton = new IconButton ("Add", &getIcons().plus));
  192. popupMenuButton->addListener (this);
  193. if (showSettingsButton)
  194. {
  195. addAndMakeVisible (settingsButton = new IconButton ("Settings", &getIcons().settings));
  196. settingsButton->addListener (this);
  197. }
  198. if (showFindPanel)
  199. {
  200. addAndMakeVisible (findPanel = new FindPanel());
  201. findPanel->addChangeListener (this);
  202. }
  203. addAndMakeVisible (treeToDisplay);
  204. }
  205. ~ConcertinaTreeComponent()
  206. {
  207. treeToDisplay = nullptr;
  208. popupMenuButton = nullptr;
  209. findPanel = nullptr;
  210. settingsButton = nullptr;
  211. }
  212. void resized() override
  213. {
  214. auto bounds = getLocalBounds();
  215. auto bottomSlice = bounds.removeFromBottom (25);
  216. bottomSlice.removeFromRight (5);
  217. popupMenuButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
  218. if (settingsButton != nullptr)
  219. settingsButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
  220. if (findPanel != nullptr)
  221. findPanel->setBounds (bottomSlice.reduced (2));
  222. treeToDisplay->setBounds (bounds);
  223. }
  224. TreePanelBase* getTree() const noexcept { return treeToDisplay.get(); }
  225. void grabFindFocus()
  226. {
  227. if (findPanel != nullptr)
  228. findPanel->grabKeyboardFocus();
  229. }
  230. private:
  231. ScopedPointer<TreePanelBase> treeToDisplay;
  232. ScopedPointer<IconButton> popupMenuButton, settingsButton;
  233. void buttonClicked (Button* b) override
  234. {
  235. if (b == popupMenuButton)
  236. {
  237. auto numSelected = treeToDisplay->tree.getNumSelectedItems();
  238. if (numSelected > 1)
  239. return;
  240. if (numSelected == 0)
  241. {
  242. if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
  243. root->showPopupMenu();
  244. }
  245. else
  246. {
  247. auto* selectedItem = treeToDisplay->tree.getSelectedItem (0);
  248. if (auto* fileItem = dynamic_cast<FileTreeItemTypes::ProjectTreeItemBase*> (selectedItem))
  249. fileItem->showPlusMenu();
  250. else if (auto* exporterItem = dynamic_cast<ConfigTreeItemTypes::ExporterItem*> (selectedItem))
  251. exporterItem->showPlusMenu();
  252. }
  253. }
  254. else if (b == settingsButton)
  255. {
  256. if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
  257. {
  258. treeToDisplay->tree.clearSelectedItems();
  259. root->showDocument();
  260. }
  261. }
  262. }
  263. void changeListenerCallback (ChangeBroadcaster* source) override
  264. {
  265. if (source == findPanel)
  266. if (auto* fileTree = dynamic_cast<FileTreePanel*> (treeToDisplay.get()))
  267. fileTree->setSearchFilter (findPanel->editor.getText());
  268. }
  269. class FindPanel : public Component,
  270. public ChangeBroadcaster,
  271. private TextEditor::Listener,
  272. private Timer,
  273. private FocusChangeListener
  274. {
  275. public:
  276. FindPanel()
  277. {
  278. addAndMakeVisible (editor);
  279. editor.addListener (this);
  280. Desktop::getInstance().addFocusChangeListener (this);
  281. lookAndFeelChanged();
  282. }
  283. ~FindPanel()
  284. {
  285. Desktop::getInstance().removeFocusChangeListener (this);
  286. }
  287. void paintOverChildren (Graphics& g) override
  288. {
  289. if (! isFocused)
  290. return;
  291. g.setColour (findColour (defaultHighlightColourId));
  292. Path p;
  293. p.addRoundedRectangle (getLocalBounds().reduced (2), 3.0f);
  294. g.strokePath (p, PathStrokeType (2.0f));
  295. }
  296. void resized() override
  297. {
  298. editor.setBounds (getLocalBounds().reduced (2));
  299. }
  300. TextEditor editor;
  301. private:
  302. void lookAndFeelChanged() override
  303. {
  304. editor.setTextToShowWhenEmpty ("Filter...", findColour (widgetTextColourId).withAlpha (0.3f));
  305. }
  306. void textEditorTextChanged (TextEditor&) override
  307. {
  308. startTimer (250);
  309. }
  310. void textEditorFocusLost (TextEditor&) override
  311. {
  312. isFocused = false;
  313. repaint();
  314. }
  315. void globalFocusChanged (Component* focusedComponent) override
  316. {
  317. if (focusedComponent == &editor)
  318. {
  319. isFocused = true;
  320. repaint();
  321. }
  322. }
  323. void timerCallback() override
  324. {
  325. stopTimer();
  326. sendChangeMessage();
  327. }
  328. bool isFocused = false;
  329. };
  330. ScopedPointer<FindPanel> findPanel;
  331. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaTreeComponent)
  332. };
  333. //==============================================================================
  334. class ProjectTab : public Component,
  335. private ChangeListener
  336. {
  337. public:
  338. ProjectTab (Project* p)
  339. : project (p)
  340. {
  341. addAndMakeVisible (concertinaPanel);
  342. buildConcertina();
  343. }
  344. ~ProjectTab()
  345. {
  346. getFileTreePanel()->saveOpenness();
  347. getModuleTreePanel()->saveOpenness();
  348. getExportersTreePanel()->saveOpenness();
  349. }
  350. void paint (Graphics& g) override
  351. {
  352. g.fillAll (findColour (secondaryBackgroundColourId));
  353. }
  354. void resized() override
  355. {
  356. concertinaPanel.setBounds (getLocalBounds());
  357. }
  358. TreePanelBase* getTreeWithSelectedItems()
  359. {
  360. for (int i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
  361. {
  362. if (auto* treeComponent = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (i)))
  363. {
  364. if (auto* base = treeComponent->getTree())
  365. if (base->tree.getNumSelectedItems() != 0)
  366. return base;
  367. }
  368. }
  369. return nullptr;
  370. }
  371. FileTreePanel* getFileTreePanel()
  372. {
  373. if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (0)))
  374. return dynamic_cast<FileTreePanel*> (panel->getTree());
  375. return nullptr;
  376. }
  377. ModuleTreePanel* getModuleTreePanel()
  378. {
  379. if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (1)))
  380. return dynamic_cast<ModuleTreePanel*> (panel->getTree());
  381. return nullptr;
  382. }
  383. ExportersTreePanel* getExportersTreePanel()
  384. {
  385. if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (2)))
  386. return dynamic_cast<ExportersTreePanel*> (panel->getTree());
  387. return nullptr;
  388. }
  389. void showPanel (int panelIndex)
  390. {
  391. jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
  392. concertinaPanel.expandPanelFully (concertinaPanel.getPanel (panelIndex), true);
  393. }
  394. void setPanelHeightProportion (int panelIndex, float prop)
  395. {
  396. jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
  397. concertinaPanel.setPanelSize (concertinaPanel.getPanel (panelIndex),
  398. roundToInt (prop * (concertinaPanel.getHeight() - 90)), false);
  399. }
  400. float getPanelHeightProportion (int panelIndex)
  401. {
  402. jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
  403. return ((float) (concertinaPanel.getPanel (panelIndex)->getHeight()) / (concertinaPanel.getHeight() - 90));
  404. }
  405. private:
  406. ConcertinaPanel concertinaPanel;
  407. OwnedArray<ConcertinaHeader> headers;
  408. Project* project = nullptr;
  409. //==============================================================================
  410. void changeListenerCallback (ChangeBroadcaster* source) override
  411. {
  412. if (auto* header = dynamic_cast<ConcertinaHeader*> (source))
  413. {
  414. auto index = headers.indexOf (header);
  415. concertinaPanel.expandPanelFully (concertinaPanel.getPanel (index), true);
  416. }
  417. }
  418. void buildConcertina()
  419. {
  420. for (int i = concertinaPanel.getNumPanels() - 1; i >= 0 ; --i)
  421. concertinaPanel.removePanel (concertinaPanel.getPanel (i));
  422. headers.clear();
  423. if (project != nullptr)
  424. {
  425. concertinaPanel.addPanel (0, new ConcertinaTreeComponent (new FileTreePanel (*project), false, true), true);
  426. concertinaPanel.addPanel (1, new ConcertinaTreeComponent (new ModuleTreePanel (*project), true), true);
  427. concertinaPanel.addPanel (2, new ConcertinaTreeComponent (new ExportersTreePanel (*project)), true);
  428. }
  429. headers.add (new ConcertinaHeader ("File explorer", getIcons().fileExplorer));
  430. headers.add (new ConcertinaHeader ("Modules", getIcons().modules));
  431. headers.add (new ConcertinaHeader ("Exporters", getIcons().exporter));
  432. for (int i = 0; i < concertinaPanel.getNumPanels(); ++i)
  433. {
  434. auto* p = concertinaPanel.getPanel (i);
  435. auto* h = headers.getUnchecked (i);
  436. p->addMouseListener (this, true);
  437. h->addChangeListener (this);
  438. h->yPosition = i * 30;
  439. concertinaPanel.setCustomPanelHeader (p, h, false);
  440. concertinaPanel.setPanelHeaderSize (p, 30);
  441. }
  442. }
  443. void mouseDown (const MouseEvent& e) override
  444. {
  445. for (int i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
  446. {
  447. auto* p = concertinaPanel.getPanel (i);
  448. if (! (p->isParentOf (e.eventComponent)))
  449. {
  450. auto* base = dynamic_cast<TreePanelBase*> (p);
  451. if (base == nullptr)
  452. base = dynamic_cast<ConcertinaTreeComponent*> (p)->getTree();
  453. if (base != nullptr)
  454. base->tree.clearSelectedItems();
  455. }
  456. }
  457. }
  458. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectTab)
  459. };