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.

367 lines
9.8KB

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