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.

405 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. class PropertyPanel::SectionComponent : public Component
  19. {
  20. public:
  21. SectionComponent (const String& sectionTitle,
  22. const Array <PropertyComponent*>& newProperties,
  23. const bool sectionIsOpen_)
  24. : Component (sectionTitle),
  25. titleHeight (sectionTitle.isNotEmpty() ? 22 : 0),
  26. sectionIsOpen (sectionIsOpen_)
  27. {
  28. propertyComps.addArray (newProperties);
  29. for (int i = propertyComps.size(); --i >= 0;)
  30. {
  31. addAndMakeVisible (propertyComps.getUnchecked(i));
  32. propertyComps.getUnchecked(i)->refresh();
  33. }
  34. }
  35. ~SectionComponent()
  36. {
  37. propertyComps.clear();
  38. }
  39. void paint (Graphics& g)
  40. {
  41. if (titleHeight > 0)
  42. getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen(), getWidth(), titleHeight);
  43. }
  44. void resized()
  45. {
  46. int y = titleHeight;
  47. for (int i = 0; i < propertyComps.size(); ++i)
  48. {
  49. PropertyComponent* const pec = propertyComps.getUnchecked (i);
  50. pec->setBounds (1, y, getWidth() - 2, pec->getPreferredHeight());
  51. y = pec->getBottom();
  52. }
  53. }
  54. int getPreferredHeight() const
  55. {
  56. int y = titleHeight;
  57. if (isOpen())
  58. {
  59. for (int i = propertyComps.size(); --i >= 0;)
  60. y += propertyComps.getUnchecked(i)->getPreferredHeight();
  61. }
  62. return y;
  63. }
  64. void setOpen (const bool open)
  65. {
  66. if (sectionIsOpen != open)
  67. {
  68. sectionIsOpen = 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. bool isOpen() const
  76. {
  77. return sectionIsOpen;
  78. }
  79. void refreshAll() const
  80. {
  81. for (int i = propertyComps.size(); --i >= 0;)
  82. propertyComps.getUnchecked (i)->refresh();
  83. }
  84. void mouseUp (const MouseEvent& e)
  85. {
  86. if (e.getMouseDownX() < titleHeight
  87. && e.x < titleHeight
  88. && e.y < titleHeight
  89. && e.getNumberOfClicks() != 2)
  90. {
  91. setOpen (! isOpen());
  92. }
  93. }
  94. void mouseDoubleClick (const MouseEvent& e)
  95. {
  96. if (e.y < titleHeight)
  97. setOpen (! isOpen());
  98. }
  99. private:
  100. OwnedArray <PropertyComponent> propertyComps;
  101. int titleHeight;
  102. bool sectionIsOpen;
  103. JUCE_DECLARE_NON_COPYABLE (SectionComponent)
  104. };
  105. //==============================================================================
  106. class PropertyPanel::PropertyHolderComponent : public Component
  107. {
  108. public:
  109. PropertyHolderComponent() {}
  110. void paint (Graphics&) {}
  111. void updateLayout (int width)
  112. {
  113. int y = 0;
  114. for (int i = 0; i < sections.size(); ++i)
  115. {
  116. SectionComponent* const section = sections.getUnchecked(i);
  117. section->setBounds (0, y, width, section->getPreferredHeight());
  118. y = section->getBottom();
  119. }
  120. setSize (width, y);
  121. repaint();
  122. }
  123. void refreshAll() const
  124. {
  125. for (int i = 0; i < sections.size(); ++i)
  126. sections.getUnchecked(i)->refreshAll();
  127. }
  128. void clear()
  129. {
  130. sections.clear();
  131. }
  132. void addSection (SectionComponent* newSection)
  133. {
  134. sections.add (newSection);
  135. addAndMakeVisible (newSection, 0);
  136. }
  137. int getNumSections() const noexcept { return sections.size(); }
  138. SectionComponent* getSection (const int index) const { return sections [index]; }
  139. private:
  140. OwnedArray<SectionComponent> sections;
  141. JUCE_DECLARE_NON_COPYABLE (PropertyHolderComponent)
  142. };
  143. //==============================================================================
  144. PropertyPanel::PropertyPanel()
  145. : messageWhenEmpty (TRANS("(nothing selected)"))
  146. {
  147. addAndMakeVisible (&viewport);
  148. viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent());
  149. viewport.setFocusContainer (true);
  150. }
  151. PropertyPanel::~PropertyPanel()
  152. {
  153. clear();
  154. }
  155. //==============================================================================
  156. void PropertyPanel::paint (Graphics& g)
  157. {
  158. if (isEmpty())
  159. {
  160. g.setColour (Colours::black.withAlpha (0.5f));
  161. g.setFont (14.0f);
  162. g.drawText (messageWhenEmpty, getLocalBounds().withHeight (30),
  163. Justification::centred, true);
  164. }
  165. }
  166. void PropertyPanel::resized()
  167. {
  168. viewport.setBounds (getLocalBounds());
  169. updatePropHolderLayout();
  170. }
  171. //==============================================================================
  172. void PropertyPanel::clear()
  173. {
  174. if (! isEmpty())
  175. {
  176. propertyHolderComponent->clear();
  177. updatePropHolderLayout();
  178. }
  179. }
  180. bool PropertyPanel::isEmpty() const
  181. {
  182. return propertyHolderComponent->getNumSections() == 0;
  183. }
  184. int PropertyPanel::getTotalContentHeight() const
  185. {
  186. return propertyHolderComponent->getHeight();
  187. }
  188. void PropertyPanel::addProperties (const Array <PropertyComponent*>& newProperties)
  189. {
  190. if (isEmpty())
  191. repaint();
  192. propertyHolderComponent->addSection (new SectionComponent (String::empty, newProperties, true));
  193. updatePropHolderLayout();
  194. }
  195. void PropertyPanel::addSection (const String& sectionTitle,
  196. const Array <PropertyComponent*>& newProperties,
  197. const bool shouldBeOpen)
  198. {
  199. jassert (sectionTitle.isNotEmpty());
  200. if (isEmpty())
  201. repaint();
  202. propertyHolderComponent->addSection (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->getNumSections(); ++i)
  225. {
  226. SectionComponent* const section = propertyHolderComponent->getSection (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. int index = 0;
  235. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  236. {
  237. SectionComponent* const section = propertyHolderComponent->getSection (i);
  238. if (section->getName().isNotEmpty())
  239. {
  240. if (index == sectionIndex)
  241. return section->isOpen();
  242. ++index;
  243. }
  244. }
  245. return false;
  246. }
  247. void PropertyPanel::setSectionOpen (const int sectionIndex, const bool shouldBeOpen)
  248. {
  249. int index = 0;
  250. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  251. {
  252. SectionComponent* const section = propertyHolderComponent->getSection (i);
  253. if (section->getName().isNotEmpty())
  254. {
  255. if (index == sectionIndex)
  256. {
  257. section->setOpen (shouldBeOpen);
  258. break;
  259. }
  260. ++index;
  261. }
  262. }
  263. }
  264. void PropertyPanel::setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled)
  265. {
  266. int index = 0;
  267. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  268. {
  269. SectionComponent* const section = propertyHolderComponent->getSection (i);
  270. if (section->getName().isNotEmpty())
  271. {
  272. if (index == sectionIndex)
  273. {
  274. section->setEnabled (shouldBeEnabled);
  275. break;
  276. }
  277. ++index;
  278. }
  279. }
  280. }
  281. //==============================================================================
  282. XmlElement* PropertyPanel::getOpennessState() const
  283. {
  284. XmlElement* const xml = new XmlElement ("PROPERTYPANELSTATE");
  285. xml->setAttribute ("scrollPos", viewport.getViewPositionY());
  286. const StringArray sections (getSectionNames());
  287. for (int i = 0; i < sections.size(); ++i)
  288. {
  289. if (sections[i].isNotEmpty())
  290. {
  291. XmlElement* const e = xml->createNewChildElement ("SECTION");
  292. e->setAttribute ("name", sections[i]);
  293. e->setAttribute ("open", isSectionOpen (i) ? 1 : 0);
  294. }
  295. }
  296. return xml;
  297. }
  298. void PropertyPanel::restoreOpennessState (const XmlElement& xml)
  299. {
  300. if (xml.hasTagName ("PROPERTYPANELSTATE"))
  301. {
  302. const StringArray sections (getSectionNames());
  303. forEachXmlChildElementWithTagName (xml, e, "SECTION")
  304. {
  305. setSectionOpen (sections.indexOf (e->getStringAttribute ("name")),
  306. e->getBoolAttribute ("open"));
  307. }
  308. viewport.setViewPosition (viewport.getViewPositionX(),
  309. xml.getIntAttribute ("scrollPos", viewport.getViewPositionY()));
  310. }
  311. }
  312. //==============================================================================
  313. void PropertyPanel::setMessageWhenEmpty (const String& newMessage)
  314. {
  315. if (messageWhenEmpty != newMessage)
  316. {
  317. messageWhenEmpty = newMessage;
  318. repaint();
  319. }
  320. }
  321. const String& PropertyPanel::getMessageWhenEmpty() const
  322. {
  323. return messageWhenEmpty;
  324. }