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.

561 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  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. setTitle (getName());
  28. panelIcon = Icon (iconPath, Colours::white);
  29. nameLabel.setText (name, dontSendNotification);
  30. nameLabel.setJustificationType (Justification::centredLeft);
  31. nameLabel.setInterceptsMouseClicks (false, false);
  32. nameLabel.setAccessible (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 (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 (! e.mouseWasDraggedSinceMouseDown())
  56. sendChangeMessage();
  57. }
  58. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  59. {
  60. return std::make_unique<AccessibilityHandler> (*this,
  61. AccessibilityRole::button,
  62. AccessibilityActions().addAction (AccessibilityActionType::press,
  63. [this] { sendChangeMessage(); }));
  64. }
  65. int direction = 0;
  66. int yPosition = 0;
  67. private:
  68. String name;
  69. Label nameLabel;
  70. Path iconPath;
  71. Icon panelIcon;
  72. Rectangle<float> arrowBounds, iconBounds;
  73. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaHeader)
  74. };
  75. //==============================================================================
  76. class FindPanel : public Component,
  77. private Timer,
  78. private FocusChangeListener
  79. {
  80. public:
  81. FindPanel (std::function<void (const String&)> cb)
  82. : callback (cb)
  83. {
  84. addAndMakeVisible (editor);
  85. editor.onTextChange = [this] { startTimer (250); };
  86. editor.onFocusLost = [this]
  87. {
  88. isFocused = false;
  89. repaint();
  90. };
  91. Desktop::getInstance().addFocusChangeListener (this);
  92. lookAndFeelChanged();
  93. }
  94. ~FindPanel() override
  95. {
  96. Desktop::getInstance().removeFocusChangeListener (this);
  97. }
  98. void paintOverChildren (Graphics& g) override
  99. {
  100. if (! isFocused)
  101. return;
  102. g.setColour (findColour (defaultHighlightColourId));
  103. Path p;
  104. p.addRoundedRectangle (getLocalBounds().reduced (2), 3.0f);
  105. g.strokePath (p, PathStrokeType (2.0f));
  106. }
  107. void resized() override
  108. {
  109. editor.setBounds (getLocalBounds().reduced (2));
  110. }
  111. private:
  112. TextEditor editor;
  113. bool isFocused = false;
  114. std::function<void (const String&)> callback;
  115. //==============================================================================
  116. void lookAndFeelChanged() override
  117. {
  118. editor.setTextToShowWhenEmpty ("Filter...", findColour (widgetTextColourId).withAlpha (0.3f));
  119. }
  120. void globalFocusChanged (Component* focusedComponent) override
  121. {
  122. if (focusedComponent == &editor)
  123. {
  124. isFocused = true;
  125. repaint();
  126. }
  127. }
  128. void timerCallback() override
  129. {
  130. stopTimer();
  131. callback (editor.getText());
  132. }
  133. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FindPanel)
  134. };
  135. //==============================================================================
  136. class ConcertinaTreeComponent : public Component
  137. {
  138. public:
  139. class AdditionalComponents
  140. {
  141. public:
  142. enum Type
  143. {
  144. addButton = (1 << 0),
  145. settingsButton = (1 << 1),
  146. findPanel = (1 << 2)
  147. };
  148. AdditionalComponents with (Type t)
  149. {
  150. auto copy = *this;
  151. copy.componentTypes |= t;
  152. return copy;
  153. }
  154. bool has (Type t) const noexcept
  155. {
  156. return (componentTypes & t) != 0;
  157. }
  158. private:
  159. int componentTypes = 0;
  160. };
  161. ConcertinaTreeComponent (const String& name,
  162. TreePanelBase* tree,
  163. AdditionalComponents additionalComponents)
  164. : Component (name),
  165. treeToDisplay (tree)
  166. {
  167. setTitle (getName());
  168. setFocusContainerType (FocusContainerType::focusContainer);
  169. if (additionalComponents.has (AdditionalComponents::addButton))
  170. {
  171. addButton = std::make_unique<IconButton> ("Add", getIcons().plus);
  172. addAndMakeVisible (addButton.get());
  173. addButton->onClick = [this] { showAddMenu(); };
  174. }
  175. if (additionalComponents.has (AdditionalComponents::settingsButton))
  176. {
  177. settingsButton = std::make_unique<IconButton> ("Settings", getIcons().settings);
  178. addAndMakeVisible (settingsButton.get());
  179. settingsButton->onClick = [this] { showSettings(); };
  180. }
  181. if (additionalComponents.has (AdditionalComponents::findPanel))
  182. {
  183. findPanel = std::make_unique<FindPanel> ([this] (const String& filter) { treeToDisplay->rootItem->setSearchFilter (filter); });
  184. addAndMakeVisible (findPanel.get());
  185. }
  186. addAndMakeVisible (treeToDisplay.get());
  187. }
  188. void resized() override
  189. {
  190. auto bounds = getLocalBounds();
  191. if (addButton != nullptr || settingsButton != nullptr || findPanel != nullptr)
  192. {
  193. auto bottomSlice = bounds.removeFromBottom (25);
  194. bottomSlice.removeFromRight (3);
  195. if (addButton != nullptr)
  196. addButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
  197. if (settingsButton != nullptr)
  198. settingsButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
  199. if (findPanel != nullptr)
  200. findPanel->setBounds (bottomSlice.reduced (2));
  201. }
  202. treeToDisplay->setBounds (bounds);
  203. }
  204. TreePanelBase* getTree() const noexcept { return treeToDisplay.get(); }
  205. private:
  206. std::unique_ptr<TreePanelBase> treeToDisplay;
  207. std::unique_ptr<IconButton> addButton, settingsButton;
  208. std::unique_ptr<FindPanel> findPanel;
  209. void showAddMenu()
  210. {
  211. auto numSelected = treeToDisplay->tree.getNumSelectedItems();
  212. if (numSelected > 1)
  213. return;
  214. if (numSelected == 0)
  215. {
  216. if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
  217. root->showPopupMenu (addButton->getScreenBounds().getCentre());
  218. }
  219. else
  220. {
  221. if (auto* item = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getSelectedItem (0)))
  222. item->showAddMenu (addButton->getScreenBounds().getCentre());
  223. }
  224. }
  225. void showSettings()
  226. {
  227. if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
  228. {
  229. treeToDisplay->tree.clearSelectedItems();
  230. root->showDocument();
  231. }
  232. }
  233. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaTreeComponent)
  234. };
  235. //==============================================================================
  236. struct ProjectSettingsComponent : public Component,
  237. private ChangeListener
  238. {
  239. ProjectSettingsComponent (Project& p)
  240. : project (p),
  241. group (project.getProjectFilenameRootString(),
  242. Icon (getIcons().settings, Colours::transparentBlack))
  243. {
  244. setTitle ("Project Settings");
  245. setFocusContainerType (FocusContainerType::focusContainer);
  246. addAndMakeVisible (group);
  247. updatePropertyList();
  248. project.addChangeListener (this);
  249. }
  250. ~ProjectSettingsComponent() override
  251. {
  252. project.removeChangeListener (this);
  253. }
  254. void resized() override
  255. {
  256. group.updateSize (12, 0, getWidth() - 24);
  257. group.setBounds (getLocalBounds().reduced (12, 0));
  258. }
  259. void updatePropertyList()
  260. {
  261. PropertyListBuilder props;
  262. project.createPropertyEditors (props);
  263. group.setProperties (props);
  264. group.setName ("Project Settings");
  265. lastProjectType = project.getProjectTypeString();
  266. parentSizeChanged();
  267. }
  268. void changeListenerCallback (ChangeBroadcaster*) override
  269. {
  270. if (lastProjectType != project.getProjectTypeString())
  271. updatePropertyList();
  272. }
  273. void parentSizeChanged() override
  274. {
  275. auto width = jmax (550, getParentWidth());
  276. auto y = group.updateSize (12, 0, width - 12);
  277. y = jmax (getParentHeight(), y);
  278. setSize (width, y);
  279. }
  280. Project& project;
  281. var lastProjectType;
  282. PropertyGroupComponent group;
  283. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSettingsComponent)
  284. };
  285. //==============================================================================
  286. struct FileTreePanel : public TreePanelBase
  287. {
  288. FileTreePanel (Project& p)
  289. : TreePanelBase (&p, "fileTreeState")
  290. {
  291. tree.setMultiSelectEnabled (true);
  292. setRoot (std::make_unique<TreeItemTypes::GroupItem> (p.getMainGroup()));
  293. tree.setRootItemVisible (false);
  294. }
  295. void updateMissingFileStatuses()
  296. {
  297. if (auto* p = dynamic_cast<TreeItemTypes::FileTreeItemBase*> (rootItem.get()))
  298. p->checkFileStatus();
  299. }
  300. };
  301. struct ModuleTreePanel : public TreePanelBase
  302. {
  303. ModuleTreePanel (Project& p)
  304. : TreePanelBase (&p, "moduleTreeState")
  305. {
  306. tree.setMultiSelectEnabled (false);
  307. setRoot (std::make_unique<TreeItemTypes::EnabledModulesItem> (p));
  308. tree.setRootItemVisible (false);
  309. }
  310. };
  311. struct ExportersTreePanel : public TreePanelBase
  312. {
  313. ExportersTreePanel (Project& p)
  314. : TreePanelBase (&p, "exportersTreeState")
  315. {
  316. tree.setMultiSelectEnabled (false);
  317. setRoot (std::make_unique<TreeItemTypes::ExportersTreeRoot> (p));
  318. tree.setRootItemVisible (false);
  319. }
  320. };
  321. //==============================================================================
  322. class Sidebar : public Component,
  323. private ChangeListener
  324. {
  325. public:
  326. Sidebar (Project* p)
  327. : project (p)
  328. {
  329. setFocusContainerType (FocusContainerType::focusContainer);
  330. if (project != nullptr)
  331. buildConcertina();
  332. }
  333. ~Sidebar() override
  334. {
  335. TreePanelBase* panels[] = { getFileTreePanel(), getModuleTreePanel(), getExportersTreePanel() };
  336. for (auto* panel : panels)
  337. if (panel != nullptr)
  338. panel->saveOpenness();
  339. }
  340. void paint (Graphics& g) override
  341. {
  342. g.fillAll (findColour (secondaryBackgroundColourId));
  343. }
  344. void resized() override
  345. {
  346. concertinaPanel.setBounds (getLocalBounds().withTrimmedBottom (3));
  347. }
  348. TreePanelBase* getTreeWithSelectedItems()
  349. {
  350. for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
  351. {
  352. if (auto* treeComponent = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (i)))
  353. {
  354. if (auto* base = treeComponent->getTree())
  355. if (base->tree.getNumSelectedItems() != 0)
  356. return base;
  357. }
  358. }
  359. return nullptr;
  360. }
  361. FileTreePanel* getFileTreePanel() { return getPanel<FileTreePanel> (0); }
  362. ModuleTreePanel* getModuleTreePanel() { return getPanel<ModuleTreePanel> (1); }
  363. ExportersTreePanel* getExportersTreePanel() { return getPanel<ExportersTreePanel> (2); }
  364. void showPanel (int panelIndex)
  365. {
  366. jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
  367. concertinaPanel.expandPanelFully (concertinaPanel.getPanel (panelIndex), true);
  368. }
  369. private:
  370. //==============================================================================
  371. template <typename PanelType>
  372. PanelType* getPanel (int panelIndex)
  373. {
  374. if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (panelIndex)))
  375. return dynamic_cast<PanelType*> (panel->getTree());
  376. return nullptr;
  377. }
  378. void changeListenerCallback (ChangeBroadcaster* source) override
  379. {
  380. const auto pointerMatches = [source] (const std::unique_ptr<ConcertinaHeader>& header) { return header.get() == source; };
  381. const auto it = std::find_if (headers.begin(), headers.end(), pointerMatches);
  382. const auto index = (int) std::distance (headers.begin(), it);
  383. if (index != (int) headers.size())
  384. concertinaPanel.expandPanelFully (concertinaPanel.getPanel (index), true);
  385. }
  386. void buildConcertina()
  387. {
  388. for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0 ; --i)
  389. concertinaPanel.removePanel (concertinaPanel.getPanel (i));
  390. headers.clear();
  391. auto addPanel = [this] (const String& name,
  392. TreePanelBase* tree,
  393. ConcertinaTreeComponent::AdditionalComponents components,
  394. const Path& icon)
  395. {
  396. if (project != nullptr)
  397. concertinaPanel.addPanel (-1, new ConcertinaTreeComponent (name, tree, components), true);
  398. headers.push_back (std::make_unique<ConcertinaHeader> (name, icon));
  399. };
  400. using AdditionalComponents = ConcertinaTreeComponent::AdditionalComponents;
  401. addPanel ("File Explorer", new FileTreePanel (*project),
  402. AdditionalComponents{}
  403. .with (AdditionalComponents::addButton)
  404. .with (AdditionalComponents::findPanel),
  405. getIcons().fileExplorer);
  406. addPanel ("Modules", new ModuleTreePanel (*project),
  407. AdditionalComponents{}
  408. .with (AdditionalComponents::addButton)
  409. .with (AdditionalComponents::settingsButton),
  410. getIcons().modules);
  411. addPanel ("Exporters", new ExportersTreePanel (*project),
  412. AdditionalComponents{}.with (AdditionalComponents::addButton),
  413. getIcons().exporter);
  414. for (int i = 0; i < concertinaPanel.getNumPanels(); ++i)
  415. {
  416. auto* p = concertinaPanel.getPanel (i);
  417. auto* h = headers[(size_t) i].get();
  418. p->addMouseListener (this, true);
  419. h->addChangeListener (this);
  420. h->yPosition = i * 30;
  421. concertinaPanel.setCustomPanelHeader (p, h, false);
  422. concertinaPanel.setPanelHeaderSize (p, 30);
  423. }
  424. addAndMakeVisible (concertinaPanel);
  425. }
  426. void mouseDown (const MouseEvent& e) override
  427. {
  428. for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
  429. {
  430. if (auto* p = concertinaPanel.getPanel (i))
  431. {
  432. if (! (p->isParentOf (e.eventComponent)))
  433. {
  434. auto* base = dynamic_cast<TreePanelBase*> (p);
  435. if (base == nullptr)
  436. if (auto* concertina = dynamic_cast<ConcertinaTreeComponent*> (p))
  437. base = concertina->getTree();
  438. if (base != nullptr)
  439. base->tree.clearSelectedItems();
  440. }
  441. }
  442. }
  443. }
  444. //==============================================================================
  445. ConcertinaPanel concertinaPanel;
  446. std::vector<std::unique_ptr<ConcertinaHeader>> headers;
  447. Project* project = nullptr;
  448. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Sidebar)
  449. };