/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ namespace TabbedComponentHelpers { const Identifier deleteComponentId ("deleteByTabComp_"); static void deleteIfNecessary (Component* const comp) { if (comp != nullptr && (bool) comp->getProperties() [deleteComponentId]) delete comp; } static Rectangle getTabArea (Rectangle& content, BorderSize& outline, const TabbedButtonBar::Orientation orientation, const int tabDepth) { switch (orientation) { case TabbedButtonBar::TabsAtTop: outline.setTop (0); return content.removeFromTop (tabDepth); case TabbedButtonBar::TabsAtBottom: outline.setBottom (0); return content.removeFromBottom (tabDepth); case TabbedButtonBar::TabsAtLeft: outline.setLeft (0); return content.removeFromLeft (tabDepth); case TabbedButtonBar::TabsAtRight: outline.setRight (0); return content.removeFromRight (tabDepth); default: jassertfalse; break; } return Rectangle(); } } //============================================================================== class TabbedComponent::ButtonBar : public TabbedButtonBar { public: ButtonBar (TabbedComponent& owner_, const TabbedButtonBar::Orientation orientation_) : TabbedButtonBar (orientation_), owner (owner_) { } void currentTabChanged (int newCurrentTabIndex, const String& newTabName) { owner.changeCallback (newCurrentTabIndex, newTabName); } void popupMenuClickOnTab (int tabIndex, const String& tabName) { owner.popupMenuClickOnTab (tabIndex, tabName); } Colour getTabBackgroundColour (const int tabIndex) { return owner.tabs->getTabBackgroundColour (tabIndex); } TabBarButton* createTabButton (const String& tabName, int tabIndex) { return owner.createTabButton (tabName, tabIndex); } private: TabbedComponent& owner; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonBar) }; //============================================================================== TabbedComponent::TabbedComponent (const TabbedButtonBar::Orientation orientation) : tabDepth (30), outlineThickness (1), edgeIndent (0) { addAndMakeVisible (tabs = new ButtonBar (*this, orientation)); } TabbedComponent::~TabbedComponent() { clearTabs(); tabs = nullptr; } //============================================================================== void TabbedComponent::setOrientation (const TabbedButtonBar::Orientation orientation) { tabs->setOrientation (orientation); resized(); } TabbedButtonBar::Orientation TabbedComponent::getOrientation() const noexcept { return tabs->getOrientation(); } void TabbedComponent::setTabBarDepth (const int newDepth) { if (tabDepth != newDepth) { tabDepth = newDepth; resized(); } } TabBarButton* TabbedComponent::createTabButton (const String& tabName, const int /*tabIndex*/) { return new TabBarButton (tabName, *tabs); } //============================================================================== void TabbedComponent::clearTabs() { if (panelComponent != nullptr) { panelComponent->setVisible (false); removeChildComponent (panelComponent); panelComponent = nullptr; } tabs->clearTabs(); for (int i = contentComponents.size(); --i >= 0;) TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (i)); contentComponents.clear(); } void TabbedComponent::addTab (const String& tabName, Colour tabBackgroundColour, Component* const contentComponent, const bool deleteComponentWhenNotNeeded, const int insertIndex) { contentComponents.insert (insertIndex, WeakReference (contentComponent)); if (deleteComponentWhenNotNeeded && contentComponent != nullptr) contentComponent->getProperties().set (TabbedComponentHelpers::deleteComponentId, true); tabs->addTab (tabName, tabBackgroundColour, insertIndex); resized(); } void TabbedComponent::setTabName (const int tabIndex, const String& newName) { tabs->setTabName (tabIndex, newName); } void TabbedComponent::removeTab (const int tabIndex) { if (isPositiveAndBelow (tabIndex, contentComponents.size())) { TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (tabIndex)); contentComponents.remove (tabIndex); tabs->removeTab (tabIndex); } } int TabbedComponent::getNumTabs() const { return tabs->getNumTabs(); } StringArray TabbedComponent::getTabNames() const { return tabs->getTabNames(); } Component* TabbedComponent::getTabContentComponent (const int tabIndex) const noexcept { return contentComponents [tabIndex]; } Colour TabbedComponent::getTabBackgroundColour (const int tabIndex) const noexcept { return tabs->getTabBackgroundColour (tabIndex); } void TabbedComponent::setTabBackgroundColour (const int tabIndex, Colour newColour) { tabs->setTabBackgroundColour (tabIndex, newColour); if (getCurrentTabIndex() == tabIndex) repaint(); } void TabbedComponent::setCurrentTabIndex (const int newTabIndex, const bool sendChangeMessage) { tabs->setCurrentTabIndex (newTabIndex, sendChangeMessage); } int TabbedComponent::getCurrentTabIndex() const { return tabs->getCurrentTabIndex(); } String TabbedComponent::getCurrentTabName() const { return tabs->getCurrentTabName(); } void TabbedComponent::setOutline (const int thickness) { outlineThickness = thickness; resized(); repaint(); } void TabbedComponent::setIndent (const int indentThickness) { edgeIndent = indentThickness; resized(); repaint(); } void TabbedComponent::paint (Graphics& g) { g.fillAll (findColour (backgroundColourId)); Rectangle content (getLocalBounds()); BorderSize outline (outlineThickness); TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth); g.reduceClipRegion (content); g.fillAll (tabs->getTabBackgroundColour (getCurrentTabIndex())); if (outlineThickness > 0) { RectangleList rl (content); rl.subtract (outline.subtractedFrom (content)); g.reduceClipRegion (rl); g.fillAll (findColour (outlineColourId)); } } void TabbedComponent::resized() { Rectangle content (getLocalBounds()); BorderSize outline (outlineThickness); tabs->setBounds (TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth)); content = BorderSize (edgeIndent).subtractedFrom (outline.subtractedFrom (content)); for (int i = contentComponents.size(); --i >= 0;) if (Component* c = contentComponents.getReference(i)) c->setBounds (content); } void TabbedComponent::lookAndFeelChanged() { for (int i = contentComponents.size(); --i >= 0;) if (Component* c = contentComponents.getReference(i)) c->lookAndFeelChanged(); } void TabbedComponent::changeCallback (const int newCurrentTabIndex, const String& newTabName) { Component* const newPanelComp = getTabContentComponent (getCurrentTabIndex()); if (newPanelComp != panelComponent) { if (panelComponent != nullptr) { panelComponent->setVisible (false); removeChildComponent (panelComponent); } panelComponent = newPanelComp; if (panelComponent != nullptr) { // do these ops as two stages instead of addAndMakeVisible() so that the // component has always got a parent when it gets the visibilityChanged() callback addChildComponent (panelComponent); panelComponent->setVisible (true); panelComponent->toFront (true); } repaint(); } resized(); currentTabChanged (newCurrentTabIndex, newTabName); } void TabbedComponent::currentTabChanged (const int, const String&) {} void TabbedComponent::popupMenuClickOnTab (const int, const String&) {}