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.

390 lines
11KB

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