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