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.

377 lines
11KB

  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. struct PropertyPanel::SectionComponent : public Component
  20. {
  21. SectionComponent (const String& sectionTitle,
  22. const Array<PropertyComponent*>& newProperties,
  23. const bool sectionIsOpen)
  24. : Component (sectionTitle),
  25. titleHeight (sectionTitle.isNotEmpty() ? 22 : 0),
  26. isOpen (sectionIsOpen)
  27. {
  28. propertyComps.addArray (newProperties);
  29. for (int i = propertyComps.size(); --i >= 0;)
  30. {
  31. addAndMakeVisible (propertyComps.getUnchecked(i));
  32. propertyComps.getUnchecked(i)->refresh();
  33. }
  34. }
  35. ~SectionComponent()
  36. {
  37. propertyComps.clear();
  38. }
  39. void paint (Graphics& g) override
  40. {
  41. if (titleHeight > 0)
  42. getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen, getWidth(), titleHeight);
  43. }
  44. void resized() override
  45. {
  46. int y = titleHeight;
  47. for (int i = 0; i < propertyComps.size(); ++i)
  48. {
  49. PropertyComponent* const pec = propertyComps.getUnchecked (i);
  50. pec->setBounds (1, y, getWidth() - 2, pec->getPreferredHeight());
  51. y = pec->getBottom();
  52. }
  53. }
  54. int getPreferredHeight() const
  55. {
  56. int y = titleHeight;
  57. if (isOpen)
  58. for (int i = propertyComps.size(); --i >= 0;)
  59. y += propertyComps.getUnchecked(i)->getPreferredHeight();
  60. return y;
  61. }
  62. void setOpen (const bool open)
  63. {
  64. if (isOpen != open)
  65. {
  66. isOpen = open;
  67. for (int i = propertyComps.size(); --i >= 0;)
  68. propertyComps.getUnchecked(i)->setVisible (open);
  69. if (PropertyPanel* const pp = findParentComponentOfClass<PropertyPanel>())
  70. pp->resized();
  71. }
  72. }
  73. void refreshAll() const
  74. {
  75. for (int i = propertyComps.size(); --i >= 0;)
  76. propertyComps.getUnchecked (i)->refresh();
  77. }
  78. void mouseUp (const MouseEvent& e) override
  79. {
  80. if (e.getMouseDownX() < titleHeight
  81. && e.x < titleHeight
  82. && e.getNumberOfClicks() != 2)
  83. mouseDoubleClick (e);
  84. }
  85. void mouseDoubleClick (const MouseEvent& e) override
  86. {
  87. if (e.y < titleHeight)
  88. setOpen (! isOpen);
  89. }
  90. OwnedArray<PropertyComponent> propertyComps;
  91. const int titleHeight;
  92. bool isOpen;
  93. JUCE_DECLARE_NON_COPYABLE (SectionComponent)
  94. };
  95. //==============================================================================
  96. struct PropertyPanel::PropertyHolderComponent : public Component
  97. {
  98. PropertyHolderComponent() {}
  99. void paint (Graphics&) override {}
  100. void updateLayout (int width)
  101. {
  102. int y = 0;
  103. for (int i = 0; i < sections.size(); ++i)
  104. {
  105. SectionComponent* const section = sections.getUnchecked(i);
  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 (int i = 0; i < sections.size(); ++i)
  115. sections.getUnchecked(i)->refreshAll();
  116. }
  117. void insertSection (int indexToInsertAt, SectionComponent* newSection)
  118. {
  119. sections.insert (indexToInsertAt, newSection);
  120. addAndMakeVisible (newSection, 0);
  121. }
  122. SectionComponent* getSectionWithNonEmptyName (const int targetIndex) const noexcept
  123. {
  124. for (int index = 0, i = 0; i < sections.size(); ++i)
  125. {
  126. SectionComponent* const section = sections.getUnchecked (i);
  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. const bool shouldBeOpen,
  199. const 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. const int maxWidth = viewport.getMaximumVisibleWidth();
  210. propertyHolderComponent->updateLayout (maxWidth);
  211. const int 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 (int i = 0; i < propertyHolderComponent->sections.size(); ++i)
  227. {
  228. SectionComponent* const section = propertyHolderComponent->sections.getUnchecked(i);
  229. if (section->getName().isNotEmpty())
  230. s.add (section->getName());
  231. }
  232. return s;
  233. }
  234. bool PropertyPanel::isSectionOpen (const int sectionIndex) const
  235. {
  236. if (SectionComponent* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
  237. return s->isOpen;
  238. return false;
  239. }
  240. void PropertyPanel::setSectionOpen (const int sectionIndex, const bool shouldBeOpen)
  241. {
  242. if (SectionComponent* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
  243. s->setOpen (shouldBeOpen);
  244. }
  245. void PropertyPanel::setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled)
  246. {
  247. if (SectionComponent* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
  248. s->setEnabled (shouldBeEnabled);
  249. }
  250. void PropertyPanel::removeSection (int sectionIndex)
  251. {
  252. if (SectionComponent* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
  253. {
  254. propertyHolderComponent->sections.removeObject (s);
  255. updatePropHolderLayout();
  256. }
  257. }
  258. //==============================================================================
  259. XmlElement* PropertyPanel::getOpennessState() const
  260. {
  261. XmlElement* const xml = new XmlElement ("PROPERTYPANELSTATE");
  262. xml->setAttribute ("scrollPos", viewport.getViewPositionY());
  263. const StringArray sections (getSectionNames());
  264. for (int i = 0; i < sections.size(); ++i)
  265. {
  266. if (sections[i].isNotEmpty())
  267. {
  268. XmlElement* const e = xml->createNewChildElement ("SECTION");
  269. e->setAttribute ("name", sections[i]);
  270. e->setAttribute ("open", isSectionOpen (i) ? 1 : 0);
  271. }
  272. }
  273. return xml;
  274. }
  275. void PropertyPanel::restoreOpennessState (const XmlElement& xml)
  276. {
  277. if (xml.hasTagName ("PROPERTYPANELSTATE"))
  278. {
  279. const StringArray sections (getSectionNames());
  280. forEachXmlChildElementWithTagName (xml, e, "SECTION")
  281. {
  282. setSectionOpen (sections.indexOf (e->getStringAttribute ("name")),
  283. e->getBoolAttribute ("open"));
  284. }
  285. viewport.setViewPosition (viewport.getViewPositionX(),
  286. xml.getIntAttribute ("scrollPos", viewport.getViewPositionY()));
  287. }
  288. }
  289. //==============================================================================
  290. void PropertyPanel::setMessageWhenEmpty (const String& newMessage)
  291. {
  292. if (messageWhenEmpty != newMessage)
  293. {
  294. messageWhenEmpty = newMessage;
  295. repaint();
  296. }
  297. }
  298. const String& PropertyPanel::getMessageWhenEmpty() const noexcept
  299. {
  300. return messageWhenEmpty;
  301. }