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.

401 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. {
  146. messageWhenEmpty = TRANS("(nothing selected)");
  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. void PropertyPanel::addProperties (const Array <PropertyComponent*>& newProperties)
  185. {
  186. if (isEmpty())
  187. repaint();
  188. propertyHolderComponent->addSection (new SectionComponent (String::empty, newProperties, true));
  189. updatePropHolderLayout();
  190. }
  191. void PropertyPanel::addSection (const String& sectionTitle,
  192. const Array <PropertyComponent*>& newProperties,
  193. const bool shouldBeOpen)
  194. {
  195. jassert (sectionTitle.isNotEmpty());
  196. if (isEmpty())
  197. repaint();
  198. propertyHolderComponent->addSection (new SectionComponent (sectionTitle, newProperties, shouldBeOpen));
  199. updatePropHolderLayout();
  200. }
  201. void PropertyPanel::updatePropHolderLayout() const
  202. {
  203. const int maxWidth = viewport.getMaximumVisibleWidth();
  204. propertyHolderComponent->updateLayout (maxWidth);
  205. const int newMaxWidth = viewport.getMaximumVisibleWidth();
  206. if (maxWidth != newMaxWidth)
  207. {
  208. // need to do this twice because of scrollbars changing the size, etc.
  209. propertyHolderComponent->updateLayout (newMaxWidth);
  210. }
  211. }
  212. void PropertyPanel::refreshAll() const
  213. {
  214. propertyHolderComponent->refreshAll();
  215. }
  216. //==============================================================================
  217. StringArray PropertyPanel::getSectionNames() const
  218. {
  219. StringArray s;
  220. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  221. {
  222. SectionComponent* const section = propertyHolderComponent->getSection (i);
  223. if (section->getName().isNotEmpty())
  224. s.add (section->getName());
  225. }
  226. return s;
  227. }
  228. bool PropertyPanel::isSectionOpen (const int sectionIndex) const
  229. {
  230. int index = 0;
  231. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  232. {
  233. SectionComponent* const section = propertyHolderComponent->getSection (i);
  234. if (section->getName().isNotEmpty())
  235. {
  236. if (index == sectionIndex)
  237. return section->isOpen();
  238. ++index;
  239. }
  240. }
  241. return false;
  242. }
  243. void PropertyPanel::setSectionOpen (const int sectionIndex, const bool shouldBeOpen)
  244. {
  245. int index = 0;
  246. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  247. {
  248. SectionComponent* const section = propertyHolderComponent->getSection (i);
  249. if (section->getName().isNotEmpty())
  250. {
  251. if (index == sectionIndex)
  252. {
  253. section->setOpen (shouldBeOpen);
  254. break;
  255. }
  256. ++index;
  257. }
  258. }
  259. }
  260. void PropertyPanel::setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled)
  261. {
  262. int index = 0;
  263. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  264. {
  265. SectionComponent* const section = propertyHolderComponent->getSection (i);
  266. if (section->getName().isNotEmpty())
  267. {
  268. if (index == sectionIndex)
  269. {
  270. section->setEnabled (shouldBeEnabled);
  271. break;
  272. }
  273. ++index;
  274. }
  275. }
  276. }
  277. //==============================================================================
  278. XmlElement* PropertyPanel::getOpennessState() const
  279. {
  280. XmlElement* const xml = new XmlElement ("PROPERTYPANELSTATE");
  281. xml->setAttribute ("scrollPos", viewport.getViewPositionY());
  282. const StringArray sections (getSectionNames());
  283. for (int i = 0; i < sections.size(); ++i)
  284. {
  285. if (sections[i].isNotEmpty())
  286. {
  287. XmlElement* const e = xml->createNewChildElement ("SECTION");
  288. e->setAttribute ("name", sections[i]);
  289. e->setAttribute ("open", isSectionOpen (i) ? 1 : 0);
  290. }
  291. }
  292. return xml;
  293. }
  294. void PropertyPanel::restoreOpennessState (const XmlElement& xml)
  295. {
  296. if (xml.hasTagName ("PROPERTYPANELSTATE"))
  297. {
  298. const StringArray sections (getSectionNames());
  299. forEachXmlChildElementWithTagName (xml, e, "SECTION")
  300. {
  301. setSectionOpen (sections.indexOf (e->getStringAttribute ("name")),
  302. e->getBoolAttribute ("open"));
  303. }
  304. viewport.setViewPosition (viewport.getViewPositionX(),
  305. xml.getIntAttribute ("scrollPos", viewport.getViewPositionY()));
  306. }
  307. }
  308. //==============================================================================
  309. void PropertyPanel::setMessageWhenEmpty (const String& newMessage)
  310. {
  311. if (messageWhenEmpty != newMessage)
  312. {
  313. messageWhenEmpty = newMessage;
  314. repaint();
  315. }
  316. }
  317. const String& PropertyPanel::getMessageWhenEmpty() const
  318. {
  319. return messageWhenEmpty;
  320. }