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.

404 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software 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. class PropertyPanel::SectionComponent : public Component
  18. {
  19. public:
  20. SectionComponent (const String& sectionTitle,
  21. const Array <PropertyComponent*>& newProperties,
  22. const bool sectionIsOpen_)
  23. : Component (sectionTitle),
  24. titleHeight (sectionTitle.isNotEmpty() ? 22 : 0),
  25. sectionIsOpen (sectionIsOpen_)
  26. {
  27. propertyComps.addArray (newProperties);
  28. for (int i = propertyComps.size(); --i >= 0;)
  29. {
  30. addAndMakeVisible (propertyComps.getUnchecked(i));
  31. propertyComps.getUnchecked(i)->refresh();
  32. }
  33. }
  34. ~SectionComponent()
  35. {
  36. propertyComps.clear();
  37. }
  38. void paint (Graphics& g) override
  39. {
  40. if (titleHeight > 0)
  41. getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen(), getWidth(), titleHeight);
  42. }
  43. void resized() override
  44. {
  45. int y = titleHeight;
  46. for (int i = 0; i < propertyComps.size(); ++i)
  47. {
  48. PropertyComponent* const pec = propertyComps.getUnchecked (i);
  49. pec->setBounds (1, y, getWidth() - 2, pec->getPreferredHeight());
  50. y = pec->getBottom();
  51. }
  52. }
  53. int getPreferredHeight() const
  54. {
  55. int y = titleHeight;
  56. if (isOpen())
  57. {
  58. for (int i = propertyComps.size(); --i >= 0;)
  59. y += propertyComps.getUnchecked(i)->getPreferredHeight();
  60. }
  61. return y;
  62. }
  63. void setOpen (const bool open)
  64. {
  65. if (sectionIsOpen != open)
  66. {
  67. sectionIsOpen = open;
  68. for (int i = propertyComps.size(); --i >= 0;)
  69. propertyComps.getUnchecked(i)->setVisible (open);
  70. if (PropertyPanel* const pp = findParentComponentOfClass<PropertyPanel>())
  71. pp->resized();
  72. }
  73. }
  74. bool isOpen() const
  75. {
  76. return sectionIsOpen;
  77. }
  78. void refreshAll() const
  79. {
  80. for (int i = propertyComps.size(); --i >= 0;)
  81. propertyComps.getUnchecked (i)->refresh();
  82. }
  83. void mouseUp (const MouseEvent& e) override
  84. {
  85. if (e.getMouseDownX() < titleHeight
  86. && e.x < titleHeight
  87. && e.y < titleHeight
  88. && e.getNumberOfClicks() != 2)
  89. {
  90. setOpen (! isOpen());
  91. }
  92. }
  93. void mouseDoubleClick (const MouseEvent& e) override
  94. {
  95. if (e.y < titleHeight)
  96. setOpen (! isOpen());
  97. }
  98. private:
  99. OwnedArray <PropertyComponent> propertyComps;
  100. int titleHeight;
  101. bool sectionIsOpen;
  102. JUCE_DECLARE_NON_COPYABLE (SectionComponent)
  103. };
  104. //==============================================================================
  105. class PropertyPanel::PropertyHolderComponent : public Component
  106. {
  107. public:
  108. PropertyHolderComponent() {}
  109. void paint (Graphics&) override {}
  110. void updateLayout (int width)
  111. {
  112. int y = 0;
  113. for (int i = 0; i < sections.size(); ++i)
  114. {
  115. SectionComponent* const section = sections.getUnchecked(i);
  116. section->setBounds (0, y, width, section->getPreferredHeight());
  117. y = section->getBottom();
  118. }
  119. setSize (width, y);
  120. repaint();
  121. }
  122. void refreshAll() const
  123. {
  124. for (int i = 0; i < sections.size(); ++i)
  125. sections.getUnchecked(i)->refreshAll();
  126. }
  127. void clear()
  128. {
  129. sections.clear();
  130. }
  131. void addSection (SectionComponent* newSection)
  132. {
  133. sections.add (newSection);
  134. addAndMakeVisible (newSection, 0);
  135. }
  136. int getNumSections() const noexcept { return sections.size(); }
  137. SectionComponent* getSection (const int index) const { return sections [index]; }
  138. private:
  139. OwnedArray<SectionComponent> sections;
  140. JUCE_DECLARE_NON_COPYABLE (PropertyHolderComponent)
  141. };
  142. //==============================================================================
  143. PropertyPanel::PropertyPanel()
  144. : messageWhenEmpty (TRANS("(nothing selected)"))
  145. {
  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->clear();
  176. updatePropHolderLayout();
  177. }
  178. }
  179. bool PropertyPanel::isEmpty() const
  180. {
  181. return propertyHolderComponent->getNumSections() == 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->addSection (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. {
  198. jassert (sectionTitle.isNotEmpty());
  199. if (isEmpty())
  200. repaint();
  201. propertyHolderComponent->addSection (new SectionComponent (sectionTitle, newProperties, shouldBeOpen));
  202. updatePropHolderLayout();
  203. }
  204. void PropertyPanel::updatePropHolderLayout() const
  205. {
  206. const int maxWidth = viewport.getMaximumVisibleWidth();
  207. propertyHolderComponent->updateLayout (maxWidth);
  208. const int newMaxWidth = viewport.getMaximumVisibleWidth();
  209. if (maxWidth != newMaxWidth)
  210. {
  211. // need to do this twice because of scrollbars changing the size, etc.
  212. propertyHolderComponent->updateLayout (newMaxWidth);
  213. }
  214. }
  215. void PropertyPanel::refreshAll() const
  216. {
  217. propertyHolderComponent->refreshAll();
  218. }
  219. //==============================================================================
  220. StringArray PropertyPanel::getSectionNames() const
  221. {
  222. StringArray s;
  223. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  224. {
  225. SectionComponent* const section = propertyHolderComponent->getSection (i);
  226. if (section->getName().isNotEmpty())
  227. s.add (section->getName());
  228. }
  229. return s;
  230. }
  231. bool PropertyPanel::isSectionOpen (const int sectionIndex) const
  232. {
  233. int index = 0;
  234. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  235. {
  236. SectionComponent* const section = propertyHolderComponent->getSection (i);
  237. if (section->getName().isNotEmpty())
  238. {
  239. if (index == sectionIndex)
  240. return section->isOpen();
  241. ++index;
  242. }
  243. }
  244. return false;
  245. }
  246. void PropertyPanel::setSectionOpen (const int sectionIndex, const bool shouldBeOpen)
  247. {
  248. int index = 0;
  249. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  250. {
  251. SectionComponent* const section = propertyHolderComponent->getSection (i);
  252. if (section->getName().isNotEmpty())
  253. {
  254. if (index == sectionIndex)
  255. {
  256. section->setOpen (shouldBeOpen);
  257. break;
  258. }
  259. ++index;
  260. }
  261. }
  262. }
  263. void PropertyPanel::setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled)
  264. {
  265. int index = 0;
  266. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  267. {
  268. SectionComponent* const section = propertyHolderComponent->getSection (i);
  269. if (section->getName().isNotEmpty())
  270. {
  271. if (index == sectionIndex)
  272. {
  273. section->setEnabled (shouldBeEnabled);
  274. break;
  275. }
  276. ++index;
  277. }
  278. }
  279. }
  280. //==============================================================================
  281. XmlElement* PropertyPanel::getOpennessState() const
  282. {
  283. XmlElement* const xml = new XmlElement ("PROPERTYPANELSTATE");
  284. xml->setAttribute ("scrollPos", viewport.getViewPositionY());
  285. const StringArray sections (getSectionNames());
  286. for (int i = 0; i < sections.size(); ++i)
  287. {
  288. if (sections[i].isNotEmpty())
  289. {
  290. XmlElement* const e = xml->createNewChildElement ("SECTION");
  291. e->setAttribute ("name", sections[i]);
  292. e->setAttribute ("open", isSectionOpen (i) ? 1 : 0);
  293. }
  294. }
  295. return xml;
  296. }
  297. void PropertyPanel::restoreOpennessState (const XmlElement& xml)
  298. {
  299. if (xml.hasTagName ("PROPERTYPANELSTATE"))
  300. {
  301. const StringArray sections (getSectionNames());
  302. forEachXmlChildElementWithTagName (xml, e, "SECTION")
  303. {
  304. setSectionOpen (sections.indexOf (e->getStringAttribute ("name")),
  305. e->getBoolAttribute ("open"));
  306. }
  307. viewport.setViewPosition (viewport.getViewPositionX(),
  308. xml.getIntAttribute ("scrollPos", viewport.getViewPositionY()));
  309. }
  310. }
  311. //==============================================================================
  312. void PropertyPanel::setMessageWhenEmpty (const String& newMessage)
  313. {
  314. if (messageWhenEmpty != newMessage)
  315. {
  316. messageWhenEmpty = newMessage;
  317. repaint();
  318. }
  319. }
  320. const String& PropertyPanel::getMessageWhenEmpty() const
  321. {
  322. return messageWhenEmpty;
  323. }