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.

375 lines
10KB

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