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
11KB

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