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.

382 lines
10KB

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