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

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. struct PropertyPanel::SectionComponent : public Component
  16. {
  17. SectionComponent (const String& sectionTitle,
  18. const Array<PropertyComponent*>& newProperties,
  19. bool sectionIsOpen)
  20. : Component (sectionTitle),
  21. isOpen (sectionIsOpen)
  22. {
  23. lookAndFeelChanged();
  24. propertyComps.addArray (newProperties);
  25. for (auto* propertyComponent : propertyComps)
  26. {
  27. addAndMakeVisible (propertyComponent);
  28. propertyComponent->refresh();
  29. }
  30. }
  31. ~SectionComponent() override
  32. {
  33. propertyComps.clear();
  34. }
  35. void paint (Graphics& g) override
  36. {
  37. if (titleHeight > 0)
  38. getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen, getWidth(), titleHeight);
  39. }
  40. void resized() override
  41. {
  42. auto y = titleHeight;
  43. for (auto* propertyComponent : propertyComps)
  44. {
  45. propertyComponent->setBounds (1, y, getWidth() - 2, propertyComponent->getPreferredHeight());
  46. y = propertyComponent->getBottom();
  47. }
  48. }
  49. void lookAndFeelChanged() override
  50. {
  51. titleHeight = getLookAndFeel().getPropertyPanelSectionHeaderHeight (getName());
  52. resized();
  53. repaint();
  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. std::unique_ptr<XmlElement> PropertyPanel::getOpennessState() const
  259. {
  260. auto xml = std::make_unique<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