juce_PropertyPanel.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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. {
  145. init();
  146. }
  147. PropertyPanel::PropertyPanel (const String& name) : Component (name)
  148. {
  149. init();
  150. }
  151. void PropertyPanel::init()
  152. {
  153. messageWhenEmpty = TRANS("(nothing selected)");
  154. addAndMakeVisible (viewport);
  155. viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent());
  156. viewport.setFocusContainer (true);
  157. }
  158. PropertyPanel::~PropertyPanel()
  159. {
  160. clear();
  161. }
  162. //==============================================================================
  163. void PropertyPanel::paint (Graphics& g)
  164. {
  165. if (isEmpty())
  166. {
  167. g.setColour (Colours::black.withAlpha (0.5f));
  168. g.setFont (14.0f);
  169. g.drawText (messageWhenEmpty, getLocalBounds().withHeight (30),
  170. Justification::centred, true);
  171. }
  172. }
  173. void PropertyPanel::resized()
  174. {
  175. viewport.setBounds (getLocalBounds());
  176. updatePropHolderLayout();
  177. }
  178. //==============================================================================
  179. void PropertyPanel::clear()
  180. {
  181. if (! isEmpty())
  182. {
  183. propertyHolderComponent->clear();
  184. updatePropHolderLayout();
  185. }
  186. }
  187. bool PropertyPanel::isEmpty() const
  188. {
  189. return propertyHolderComponent->getNumSections() == 0;
  190. }
  191. int PropertyPanel::getTotalContentHeight() const
  192. {
  193. return propertyHolderComponent->getHeight();
  194. }
  195. void PropertyPanel::addProperties (const Array <PropertyComponent*>& newProperties)
  196. {
  197. if (isEmpty())
  198. repaint();
  199. propertyHolderComponent->addSection (new SectionComponent (String::empty, newProperties, true));
  200. updatePropHolderLayout();
  201. }
  202. void PropertyPanel::addSection (const String& sectionTitle,
  203. const Array <PropertyComponent*>& newProperties,
  204. const bool shouldBeOpen)
  205. {
  206. jassert (sectionTitle.isNotEmpty());
  207. if (isEmpty())
  208. repaint();
  209. propertyHolderComponent->addSection (new SectionComponent (sectionTitle, newProperties, shouldBeOpen));
  210. updatePropHolderLayout();
  211. }
  212. void PropertyPanel::updatePropHolderLayout() const
  213. {
  214. const int maxWidth = viewport.getMaximumVisibleWidth();
  215. propertyHolderComponent->updateLayout (maxWidth);
  216. const int newMaxWidth = viewport.getMaximumVisibleWidth();
  217. if (maxWidth != newMaxWidth)
  218. {
  219. // need to do this twice because of scrollbars changing the size, etc.
  220. propertyHolderComponent->updateLayout (newMaxWidth);
  221. }
  222. }
  223. void PropertyPanel::refreshAll() const
  224. {
  225. propertyHolderComponent->refreshAll();
  226. }
  227. //==============================================================================
  228. StringArray PropertyPanel::getSectionNames() const
  229. {
  230. StringArray s;
  231. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  232. {
  233. SectionComponent* const section = propertyHolderComponent->getSection (i);
  234. if (section->getName().isNotEmpty())
  235. s.add (section->getName());
  236. }
  237. return s;
  238. }
  239. bool PropertyPanel::isSectionOpen (const int sectionIndex) const
  240. {
  241. int index = 0;
  242. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  243. {
  244. SectionComponent* const section = propertyHolderComponent->getSection (i);
  245. if (section->getName().isNotEmpty())
  246. {
  247. if (index == sectionIndex)
  248. return section->isOpen();
  249. ++index;
  250. }
  251. }
  252. return false;
  253. }
  254. void PropertyPanel::setSectionOpen (const int sectionIndex, const bool shouldBeOpen)
  255. {
  256. int index = 0;
  257. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  258. {
  259. SectionComponent* const section = propertyHolderComponent->getSection (i);
  260. if (section->getName().isNotEmpty())
  261. {
  262. if (index == sectionIndex)
  263. {
  264. section->setOpen (shouldBeOpen);
  265. break;
  266. }
  267. ++index;
  268. }
  269. }
  270. }
  271. void PropertyPanel::setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled)
  272. {
  273. int index = 0;
  274. for (int i = 0; i < propertyHolderComponent->getNumSections(); ++i)
  275. {
  276. SectionComponent* const section = propertyHolderComponent->getSection (i);
  277. if (section->getName().isNotEmpty())
  278. {
  279. if (index == sectionIndex)
  280. {
  281. section->setEnabled (shouldBeEnabled);
  282. break;
  283. }
  284. ++index;
  285. }
  286. }
  287. }
  288. //==============================================================================
  289. XmlElement* PropertyPanel::getOpennessState() const
  290. {
  291. XmlElement* const xml = new XmlElement ("PROPERTYPANELSTATE");
  292. xml->setAttribute ("scrollPos", viewport.getViewPositionY());
  293. const StringArray sections (getSectionNames());
  294. for (int i = 0; i < sections.size(); ++i)
  295. {
  296. if (sections[i].isNotEmpty())
  297. {
  298. XmlElement* const e = xml->createNewChildElement ("SECTION");
  299. e->setAttribute ("name", sections[i]);
  300. e->setAttribute ("open", isSectionOpen (i) ? 1 : 0);
  301. }
  302. }
  303. return xml;
  304. }
  305. void PropertyPanel::restoreOpennessState (const XmlElement& xml)
  306. {
  307. if (xml.hasTagName ("PROPERTYPANELSTATE"))
  308. {
  309. const StringArray sections (getSectionNames());
  310. forEachXmlChildElementWithTagName (xml, e, "SECTION")
  311. {
  312. setSectionOpen (sections.indexOf (e->getStringAttribute ("name")),
  313. e->getBoolAttribute ("open"));
  314. }
  315. viewport.setViewPosition (viewport.getViewPositionX(),
  316. xml.getIntAttribute ("scrollPos", viewport.getViewPositionY()));
  317. }
  318. }
  319. //==============================================================================
  320. void PropertyPanel::setMessageWhenEmpty (const String& newMessage)
  321. {
  322. if (messageWhenEmpty != newMessage)
  323. {
  324. messageWhenEmpty = newMessage;
  325. repaint();
  326. }
  327. }
  328. const String& PropertyPanel::getMessageWhenEmpty() const
  329. {
  330. return messageWhenEmpty;
  331. }