/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. 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(); } } //============================================================================== std::unique_ptr PropertyPanel::getOpennessState() const { auto xml = std::make_unique ("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