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.

397 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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. namespace juce
  19. {
  20. struct PropertyPanel::SectionComponent final : public Component
  21. {
  22. SectionComponent (const String& sectionTitle,
  23. const Array<PropertyComponent*>& newProperties,
  24. bool sectionIsOpen,
  25. int extraPadding)
  26. : Component (sectionTitle),
  27. isOpen (sectionIsOpen),
  28. padding (extraPadding)
  29. {
  30. lookAndFeelChanged();
  31. propertyComps.addArray (newProperties);
  32. for (auto* propertyComponent : propertyComps)
  33. {
  34. addAndMakeVisible (propertyComponent);
  35. propertyComponent->refresh();
  36. }
  37. }
  38. ~SectionComponent() override
  39. {
  40. propertyComps.clear();
  41. }
  42. void paint (Graphics& g) override
  43. {
  44. if (titleHeight > 0)
  45. getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen, getWidth(), titleHeight);
  46. }
  47. void resized() override
  48. {
  49. auto y = titleHeight;
  50. for (auto* propertyComponent : propertyComps)
  51. {
  52. propertyComponent->setBounds (1, y, getWidth() - 2, propertyComponent->getPreferredHeight());
  53. y = propertyComponent->getBottom() + padding;
  54. }
  55. }
  56. void lookAndFeelChanged() override
  57. {
  58. titleHeight = getLookAndFeel().getPropertyPanelSectionHeaderHeight (getName());
  59. resized();
  60. repaint();
  61. }
  62. int getPreferredHeight() const
  63. {
  64. auto y = titleHeight;
  65. auto numComponents = propertyComps.size();
  66. if (numComponents > 0 && isOpen)
  67. {
  68. for (auto* propertyComponent : propertyComps)
  69. y += propertyComponent->getPreferredHeight();
  70. y += (numComponents - 1) * padding;
  71. }
  72. return y;
  73. }
  74. void setOpen (bool open)
  75. {
  76. if (isOpen != open)
  77. {
  78. isOpen = open;
  79. for (auto* propertyComponent : propertyComps)
  80. propertyComponent->setVisible (open);
  81. if (auto* propertyPanel = findParentComponentOfClass<PropertyPanel>())
  82. propertyPanel->resized();
  83. }
  84. }
  85. void refreshAll() const
  86. {
  87. for (auto* propertyComponent : propertyComps)
  88. propertyComponent->refresh();
  89. }
  90. void mouseUp (const MouseEvent& e) override
  91. {
  92. if (e.getMouseDownX() < titleHeight
  93. && e.x < titleHeight
  94. && e.getNumberOfClicks() != 2)
  95. mouseDoubleClick (e);
  96. }
  97. void mouseDoubleClick (const MouseEvent& e) override
  98. {
  99. if (e.y < titleHeight)
  100. setOpen (! isOpen);
  101. }
  102. OwnedArray<PropertyComponent> propertyComps;
  103. int titleHeight;
  104. bool isOpen;
  105. int padding;
  106. JUCE_DECLARE_NON_COPYABLE (SectionComponent)
  107. };
  108. //==============================================================================
  109. struct PropertyPanel::PropertyHolderComponent final : public Component
  110. {
  111. PropertyHolderComponent() {}
  112. void paint (Graphics&) override {}
  113. void updateLayout (int width)
  114. {
  115. auto y = 0;
  116. for (auto* section : sections)
  117. {
  118. section->setBounds (0, y, width, section->getPreferredHeight());
  119. y = section->getBottom();
  120. }
  121. setSize (width, y);
  122. repaint();
  123. }
  124. void refreshAll() const
  125. {
  126. for (auto* section : sections)
  127. section->refreshAll();
  128. }
  129. void insertSection (int indexToInsertAt, SectionComponent* newSection)
  130. {
  131. sections.insert (indexToInsertAt, newSection);
  132. addAndMakeVisible (newSection, 0);
  133. }
  134. SectionComponent* getSectionWithNonEmptyName (int targetIndex) const noexcept
  135. {
  136. auto index = 0;
  137. for (auto* section : sections)
  138. {
  139. if (section->getName().isNotEmpty())
  140. if (index++ == targetIndex)
  141. return section;
  142. }
  143. return nullptr;
  144. }
  145. OwnedArray<SectionComponent> sections;
  146. JUCE_DECLARE_NON_COPYABLE (PropertyHolderComponent)
  147. };
  148. //==============================================================================
  149. PropertyPanel::PropertyPanel()
  150. {
  151. init();
  152. }
  153. PropertyPanel::PropertyPanel (const String& name) : Component (name)
  154. {
  155. init();
  156. }
  157. void PropertyPanel::init()
  158. {
  159. messageWhenEmpty = TRANS ("(nothing selected)");
  160. addAndMakeVisible (viewport);
  161. viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent());
  162. viewport.setFocusContainerType (FocusContainerType::keyboardFocusContainer);
  163. }
  164. PropertyPanel::~PropertyPanel()
  165. {
  166. clear();
  167. }
  168. //==============================================================================
  169. void PropertyPanel::paint (Graphics& g)
  170. {
  171. if (isEmpty())
  172. {
  173. g.setColour (Colours::black.withAlpha (0.5f));
  174. g.setFont (14.0f);
  175. g.drawText (messageWhenEmpty, getLocalBounds().withHeight (30),
  176. Justification::centred, true);
  177. }
  178. }
  179. void PropertyPanel::resized()
  180. {
  181. viewport.setBounds (getLocalBounds());
  182. updatePropHolderLayout();
  183. }
  184. //==============================================================================
  185. void PropertyPanel::clear()
  186. {
  187. if (! isEmpty())
  188. {
  189. propertyHolderComponent->sections.clear();
  190. updatePropHolderLayout();
  191. }
  192. }
  193. bool PropertyPanel::isEmpty() const
  194. {
  195. return propertyHolderComponent->sections.size() == 0;
  196. }
  197. int PropertyPanel::getTotalContentHeight() const
  198. {
  199. return propertyHolderComponent->getHeight();
  200. }
  201. void PropertyPanel::addProperties (const Array<PropertyComponent*>& newProperties,
  202. int extraPaddingBetweenComponents)
  203. {
  204. if (isEmpty())
  205. repaint();
  206. propertyHolderComponent->insertSection (-1, new SectionComponent ({}, newProperties, true, extraPaddingBetweenComponents));
  207. updatePropHolderLayout();
  208. }
  209. void PropertyPanel::addSection (const String& sectionTitle,
  210. const Array<PropertyComponent*>& newProperties,
  211. bool shouldBeOpen,
  212. int indexToInsertAt,
  213. int extraPaddingBetweenComponents)
  214. {
  215. jassert (sectionTitle.isNotEmpty());
  216. if (isEmpty())
  217. repaint();
  218. propertyHolderComponent->insertSection (indexToInsertAt, new SectionComponent (sectionTitle,
  219. newProperties,
  220. shouldBeOpen,
  221. extraPaddingBetweenComponents));
  222. updatePropHolderLayout();
  223. }
  224. void PropertyPanel::updatePropHolderLayout() const
  225. {
  226. auto maxWidth = viewport.getMaximumVisibleWidth();
  227. propertyHolderComponent->updateLayout (maxWidth);
  228. auto newMaxWidth = viewport.getMaximumVisibleWidth();
  229. if (maxWidth != newMaxWidth)
  230. {
  231. // need to do this twice because of scrollbars changing the size, etc.
  232. propertyHolderComponent->updateLayout (newMaxWidth);
  233. }
  234. }
  235. void PropertyPanel::refreshAll() const
  236. {
  237. propertyHolderComponent->refreshAll();
  238. }
  239. //==============================================================================
  240. StringArray PropertyPanel::getSectionNames() const
  241. {
  242. StringArray s;
  243. for (auto* section : propertyHolderComponent->sections)
  244. {
  245. if (section->getName().isNotEmpty())
  246. s.add (section->getName());
  247. }
  248. return s;
  249. }
  250. bool PropertyPanel::isSectionOpen (int sectionIndex) const
  251. {
  252. if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
  253. return s->isOpen;
  254. return false;
  255. }
  256. void PropertyPanel::setSectionOpen (int sectionIndex, bool shouldBeOpen)
  257. {
  258. if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
  259. s->setOpen (shouldBeOpen);
  260. }
  261. void PropertyPanel::setSectionEnabled (int sectionIndex, bool shouldBeEnabled)
  262. {
  263. if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
  264. s->setEnabled (shouldBeEnabled);
  265. }
  266. void PropertyPanel::removeSection (int sectionIndex)
  267. {
  268. if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
  269. {
  270. propertyHolderComponent->sections.removeObject (s);
  271. updatePropHolderLayout();
  272. }
  273. }
  274. //==============================================================================
  275. std::unique_ptr<XmlElement> PropertyPanel::getOpennessState() const
  276. {
  277. auto xml = std::make_unique<XmlElement> ("PROPERTYPANELSTATE");
  278. xml->setAttribute ("scrollPos", viewport.getViewPositionY());
  279. auto sections = getSectionNames();
  280. for (auto s : sections)
  281. {
  282. if (s.isNotEmpty())
  283. {
  284. auto* e = xml->createNewChildElement ("SECTION");
  285. e->setAttribute ("name", s);
  286. e->setAttribute ("open", isSectionOpen (sections.indexOf (s)) ? 1 : 0);
  287. }
  288. }
  289. return xml;
  290. }
  291. void PropertyPanel::restoreOpennessState (const XmlElement& xml)
  292. {
  293. if (xml.hasTagName ("PROPERTYPANELSTATE"))
  294. {
  295. auto sections = getSectionNames();
  296. for (auto* e : xml.getChildWithTagNameIterator ("SECTION"))
  297. {
  298. setSectionOpen (sections.indexOf (e->getStringAttribute ("name")),
  299. e->getBoolAttribute ("open"));
  300. }
  301. viewport.setViewPosition (viewport.getViewPositionX(),
  302. xml.getIntAttribute ("scrollPos", viewport.getViewPositionY()));
  303. }
  304. }
  305. //==============================================================================
  306. void PropertyPanel::setMessageWhenEmpty (const String& newMessage)
  307. {
  308. if (messageWhenEmpty != newMessage)
  309. {
  310. messageWhenEmpty = newMessage;
  311. repaint();
  312. }
  313. }
  314. const String& PropertyPanel::getMessageWhenEmpty() const noexcept
  315. {
  316. return messageWhenEmpty;
  317. }
  318. } // namespace juce