/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 5 End-User License Agreement and JUCE 5 Privacy Policy (both updated and effective as of the 27th April 2017). End User License Agreement: www.juce.com/juce-5-licence Privacy Policy: www.juce.com/juce-5-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { struct PropertyPanel::SectionComponent : public Component { SectionComponent (const String& sectionTitle, const Array& newProperties, bool sectionIsOpen) : Component (sectionTitle), titleHeight (getLookAndFeel().getPropertyPanelSectionHeaderHeight (sectionTitle)), isOpen (sectionIsOpen) { propertyComps.addArray (newProperties); for (auto* propertyComponent : propertyComps) { addAndMakeVisible (propertyComponent); propertyComponent->refresh(); } } ~SectionComponent() override { propertyComps.clear(); } void paint (Graphics& g) override { if (titleHeight > 0) getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen, getWidth(), titleHeight); } void resized() override { auto y = titleHeight; for (auto* propertyComponent : propertyComps) { propertyComponent->setBounds (1, y, getWidth() - 2, propertyComponent->getPreferredHeight()); y = propertyComponent->getBottom(); } } int getPreferredHeight() const { auto y = titleHeight; if (isOpen) for (auto* propertyComponent : propertyComps) y += propertyComponent->getPreferredHeight(); return y; } void setOpen (bool open) { if (isOpen != open) { isOpen = open; for (auto* propertyComponent : propertyComps) propertyComponent->setVisible (open); if (auto* propertyPanel = findParentComponentOfClass()) propertyPanel->resized(); } } void refreshAll() const { for (auto* propertyComponent : propertyComps) propertyComponent->refresh(); } void mouseUp (const MouseEvent& e) override { if (e.getMouseDownX() < titleHeight && e.x < titleHeight && e.getNumberOfClicks() != 2) mouseDoubleClick (e); } void mouseDoubleClick (const MouseEvent& e) override { if (e.y < titleHeight) setOpen (! isOpen); } OwnedArray propertyComps; int titleHeight; bool isOpen; JUCE_DECLARE_NON_COPYABLE (SectionComponent) }; //============================================================================== struct PropertyPanel::PropertyHolderComponent : public Component { PropertyHolderComponent() {} void paint (Graphics&) override {} void updateLayout (int width) { auto y = 0; for (auto* section : sections) { section->setBounds (0, y, width, section->getPreferredHeight()); y = section->getBottom(); } setSize (width, y); repaint(); } void refreshAll() const { for (auto* section : sections) section->refreshAll(); } void insertSection (int indexToInsertAt, SectionComponent* newSection) { sections.insert (indexToInsertAt, newSection); addAndMakeVisible (newSection, 0); } SectionComponent* getSectionWithNonEmptyName (int targetIndex) const noexcept { auto index = 0; for (auto* section : sections) { if (section->getName().isNotEmpty()) if (index++ == targetIndex) return section; } return nullptr; } OwnedArray sections; JUCE_DECLARE_NON_COPYABLE (PropertyHolderComponent) }; //============================================================================== PropertyPanel::PropertyPanel() { init(); } PropertyPanel::PropertyPanel (const String& name) : Component (name) { init(); } void PropertyPanel::init() { messageWhenEmpty = TRANS("(nothing selected)"); addAndMakeVisible (viewport); viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent()); viewport.setFocusContainer (true); } PropertyPanel::~PropertyPanel() { clear(); } //============================================================================== void PropertyPanel::paint (Graphics& g) { if (isEmpty()) { g.setColour (Colours::black.withAlpha (0.5f)); g.setFont (14.0f); g.drawText (messageWhenEmpty, getLocalBounds().withHeight (30), Justification::centred, true); } } void PropertyPanel::resized() { viewport.setBounds (getLocalBounds()); updatePropHolderLayout(); } //============================================================================== void PropertyPanel::clear() { if (! isEmpty()) { propertyHolderComponent->sections.clear(); updatePropHolderLayout(); } } bool PropertyPanel::isEmpty() const { return propertyHolderComponent->sections.size() == 0; } int PropertyPanel::getTotalContentHeight() const { return propertyHolderComponent->getHeight(); } void PropertyPanel::addProperties (const Array& newProperties) { if (isEmpty()) repaint(); propertyHolderComponent->insertSection (-1, new SectionComponent (String(), newProperties, true)); updatePropHolderLayout(); } void PropertyPanel::addSection (const String& sectionTitle, const Array& newProperties, bool shouldBeOpen, int indexToInsertAt) { jassert (sectionTitle.isNotEmpty()); if (isEmpty()) repaint(); propertyHolderComponent->insertSection (indexToInsertAt, new SectionComponent (sectionTitle, newProperties, shouldBeOpen)); updatePropHolderLayout(); } void PropertyPanel::updatePropHolderLayout() const { auto maxWidth = viewport.getMaximumVisibleWidth(); propertyHolderComponent->updateLayout (maxWidth); auto newMaxWidth = viewport.getMaximumVisibleWidth(); if (maxWidth != newMaxWidth) { // need to do this twice because of scrollbars changing the size, etc. propertyHolderComponent->updateLayout (newMaxWidth); } } void PropertyPanel::refreshAll() const { propertyHolderComponent->refreshAll(); } //============================================================================== StringArray PropertyPanel::getSectionNames() const { StringArray s; for (auto* section : propertyHolderComponent->sections) { if (section->getName().isNotEmpty()) s.add (section->getName()); } return s; } bool PropertyPanel::isSectionOpen (int sectionIndex) const { if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex)) return s->isOpen; return false; } void PropertyPanel::setSectionOpen (int sectionIndex, bool shouldBeOpen) { if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex)) s->setOpen (shouldBeOpen); } void PropertyPanel::setSectionEnabled (int sectionIndex, bool shouldBeEnabled) { if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex)) s->setEnabled (shouldBeEnabled); } void PropertyPanel::removeSection (int sectionIndex) { if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex)) { propertyHolderComponent->sections.removeObject (s); updatePropHolderLayout(); } } //============================================================================== XmlElement* PropertyPanel::getOpennessState() const { auto* xml = new XmlElement ("PROPERTYPANELSTATE"); xml->setAttribute ("scrollPos", viewport.getViewPositionY()); auto sections = getSectionNames(); for (auto s : sections) { if (s.isNotEmpty()) { auto* e = xml->createNewChildElement ("SECTION"); e->setAttribute ("name", s); e->setAttribute ("open", isSectionOpen (sections.indexOf (s)) ? 1 : 0); } } return xml; } void PropertyPanel::restoreOpennessState (const XmlElement& xml) { if (xml.hasTagName ("PROPERTYPANELSTATE")) { auto sections = getSectionNames(); forEachXmlChildElementWithTagName (xml, e, "SECTION") { setSectionOpen (sections.indexOf (e->getStringAttribute ("name")), e->getBoolAttribute ("open")); } viewport.setViewPosition (viewport.getViewPositionX(), xml.getIntAttribute ("scrollPos", viewport.getViewPositionY())); } } //============================================================================== void PropertyPanel::setMessageWhenEmpty (const String& newMessage) { if (messageWhenEmpty != newMessage) { messageWhenEmpty = newMessage; repaint(); } } const String& PropertyPanel::getMessageWhenEmpty() const noexcept { return messageWhenEmpty; } } // namespace juce