375 lines
10 KiB
C++
375 lines
10 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
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<PropertyComponent*>& 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>())
|
|
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<PropertyComponent> 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<SectionComponent> 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<PropertyComponent*>& newProperties)
|
|
{
|
|
if (isEmpty())
|
|
repaint();
|
|
|
|
propertyHolderComponent->insertSection (-1, new SectionComponent (String(), newProperties, true));
|
|
updatePropHolderLayout();
|
|
}
|
|
|
|
void PropertyPanel::addSection (const String& sectionTitle,
|
|
const Array<PropertyComponent*>& 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
|