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