mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
505 lines
14 KiB
C++
505 lines
14 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
|
Copyright 2004-10 by Raw Material Software Ltd.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
JUCE can be redistributed and/or modified under the terms of the GNU General
|
|
Public License (Version 2), as published by the Free Software Foundation.
|
|
A copy of the license is included in the JUCE distribution, or can be found
|
|
online 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.rawmaterialsoftware.com/juce for more information.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#include "../../../core/juce_StandardHeader.h"
|
|
|
|
BEGIN_JUCE_NAMESPACE
|
|
|
|
#include "juce_TabbedComponent.h"
|
|
#include "../menus/juce_PopupMenu.h"
|
|
#include "../lookandfeel/juce_LookAndFeel.h"
|
|
|
|
|
|
//==============================================================================
|
|
TabBarButton::TabBarButton (const String& name, TabbedButtonBar& owner_)
|
|
: Button (name),
|
|
owner (owner_),
|
|
overlapPixels (0)
|
|
{
|
|
shadow.setShadowProperties (2.2f, 0.7f, 0, 0);
|
|
setComponentEffect (&shadow);
|
|
setWantsKeyboardFocus (false);
|
|
}
|
|
|
|
TabBarButton::~TabBarButton()
|
|
{
|
|
}
|
|
|
|
int TabBarButton::getIndex() const
|
|
{
|
|
return owner.indexOfTabButton (this);
|
|
}
|
|
|
|
void TabBarButton::paintButton (Graphics& g,
|
|
bool isMouseOverButton,
|
|
bool isButtonDown)
|
|
{
|
|
const Rectangle<int> area (getActiveArea());
|
|
g.setOrigin (area.getX(), area.getY());
|
|
|
|
getLookAndFeel()
|
|
.drawTabButton (g, area.getWidth(), area.getHeight(),
|
|
owner.getTabBackgroundColour (getIndex()),
|
|
getIndex(), getButtonText(), *this,
|
|
owner.getOrientation(),
|
|
isMouseOverButton, isButtonDown,
|
|
getToggleState());
|
|
}
|
|
|
|
void TabBarButton::clicked (const ModifierKeys& mods)
|
|
{
|
|
if (mods.isPopupMenu())
|
|
owner.popupMenuClickOnTab (getIndex(), getButtonText());
|
|
else
|
|
owner.setCurrentTabIndex (getIndex());
|
|
}
|
|
|
|
bool TabBarButton::hitTest (int mx, int my)
|
|
{
|
|
const Rectangle<int> area (getActiveArea());
|
|
|
|
if (owner.getOrientation() == TabbedButtonBar::TabsAtLeft
|
|
|| owner.getOrientation() == TabbedButtonBar::TabsAtRight)
|
|
{
|
|
if (((unsigned int) mx) < (unsigned int) getWidth()
|
|
&& my >= area.getY() + overlapPixels
|
|
&& my < area.getBottom() - overlapPixels)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (mx >= area.getX() + overlapPixels && mx < area.getRight() - overlapPixels
|
|
&& ((unsigned int) my) < (unsigned int) getHeight())
|
|
return true;
|
|
}
|
|
|
|
Path p;
|
|
getLookAndFeel()
|
|
.createTabButtonShape (p, area.getWidth(), area.getHeight(), getIndex(), getButtonText(), *this,
|
|
owner.getOrientation(), false, false, getToggleState());
|
|
|
|
return p.contains ((float) (mx - area.getX()),
|
|
(float) (my - area.getY()));
|
|
}
|
|
|
|
int TabBarButton::getBestTabLength (const int depth)
|
|
{
|
|
return jlimit (depth * 2,
|
|
depth * 7,
|
|
getLookAndFeel().getTabButtonBestWidth (getIndex(), getButtonText(), depth, *this));
|
|
}
|
|
|
|
const Rectangle<int> TabBarButton::getActiveArea()
|
|
{
|
|
Rectangle<int> r (getLocalBounds());
|
|
const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage();
|
|
|
|
if (owner.getOrientation() != TabbedButtonBar::TabsAtLeft) r.removeFromRight (spaceAroundImage);
|
|
if (owner.getOrientation() != TabbedButtonBar::TabsAtRight) r.removeFromLeft (spaceAroundImage);
|
|
if (owner.getOrientation() != TabbedButtonBar::TabsAtBottom) r.removeFromTop (spaceAroundImage);
|
|
if (owner.getOrientation() != TabbedButtonBar::TabsAtTop) r.removeFromBottom (spaceAroundImage);
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
class TabbedButtonBar::BehindFrontTabComp : public Component,
|
|
public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug)
|
|
{
|
|
public:
|
|
BehindFrontTabComp (TabbedButtonBar& owner_)
|
|
: owner (owner_)
|
|
{
|
|
setInterceptsMouseClicks (false, false);
|
|
}
|
|
|
|
void paint (Graphics& g)
|
|
{
|
|
getLookAndFeel().drawTabAreaBehindFrontButton (g, getWidth(), getHeight(),
|
|
owner, owner.getOrientation());
|
|
}
|
|
|
|
void enablementChanged()
|
|
{
|
|
repaint();
|
|
}
|
|
|
|
void buttonClicked (Button* button)
|
|
{
|
|
owner.showExtraItemsMenu();
|
|
}
|
|
|
|
private:
|
|
TabbedButtonBar& owner;
|
|
|
|
BehindFrontTabComp (const BehindFrontTabComp&);
|
|
BehindFrontTabComp& operator= (const BehindFrontTabComp&);
|
|
};
|
|
|
|
|
|
//==============================================================================
|
|
TabbedButtonBar::TabbedButtonBar (const Orientation orientation_)
|
|
: orientation (orientation_),
|
|
minimumScale (0.7),
|
|
currentTabIndex (-1)
|
|
{
|
|
setInterceptsMouseClicks (false, true);
|
|
addAndMakeVisible (behindFrontTab = new BehindFrontTabComp (*this));
|
|
setFocusContainer (true);
|
|
}
|
|
|
|
TabbedButtonBar::~TabbedButtonBar()
|
|
{
|
|
tabs.clear();
|
|
extraTabsButton = 0;
|
|
}
|
|
|
|
//==============================================================================
|
|
void TabbedButtonBar::setOrientation (const Orientation newOrientation)
|
|
{
|
|
orientation = newOrientation;
|
|
|
|
for (int i = getNumChildComponents(); --i >= 0;)
|
|
getChildComponent (i)->resized();
|
|
|
|
resized();
|
|
}
|
|
|
|
TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int /*index*/)
|
|
{
|
|
return new TabBarButton (name, *this);
|
|
}
|
|
|
|
void TabbedButtonBar::setMinimumTabScaleFactor (double newMinimumScale)
|
|
{
|
|
minimumScale = newMinimumScale;
|
|
resized();
|
|
}
|
|
|
|
//==============================================================================
|
|
void TabbedButtonBar::clearTabs()
|
|
{
|
|
tabs.clear();
|
|
extraTabsButton = 0;
|
|
setCurrentTabIndex (-1);
|
|
}
|
|
|
|
void TabbedButtonBar::addTab (const String& tabName,
|
|
const Colour& tabBackgroundColour,
|
|
int insertIndex)
|
|
{
|
|
jassert (tabName.isNotEmpty()); // you have to give them all a name..
|
|
|
|
if (tabName.isNotEmpty())
|
|
{
|
|
if (((unsigned int) insertIndex) > (unsigned int) tabs.size())
|
|
insertIndex = tabs.size();
|
|
|
|
TabInfo* newTab = new TabInfo();
|
|
newTab->name = tabName;
|
|
newTab->colour = tabBackgroundColour;
|
|
newTab->component = createTabButton (tabName, insertIndex);
|
|
|
|
jassert (newTab->component != 0);
|
|
|
|
tabs.insert (insertIndex, newTab);
|
|
addAndMakeVisible (newTab->component, insertIndex);
|
|
|
|
resized();
|
|
|
|
if (currentTabIndex < 0)
|
|
setCurrentTabIndex (0);
|
|
}
|
|
}
|
|
|
|
void TabbedButtonBar::setTabName (const int tabIndex, const String& newName)
|
|
{
|
|
TabInfo* const tab = tabs [tabIndex];
|
|
|
|
if (tab != 0 && tab->name != newName)
|
|
{
|
|
tab->name = newName;
|
|
tab->component->setButtonText (newName);
|
|
resized();
|
|
}
|
|
}
|
|
|
|
void TabbedButtonBar::removeTab (const int tabIndex)
|
|
{
|
|
if (tabs [tabIndex] != 0)
|
|
{
|
|
const int oldTabIndex = currentTabIndex;
|
|
if (currentTabIndex == tabIndex)
|
|
currentTabIndex = -1;
|
|
|
|
tabs.remove (tabIndex);
|
|
resized();
|
|
|
|
setCurrentTabIndex (jlimit (0, jmax (0, tabs.size() - 1), oldTabIndex));
|
|
}
|
|
}
|
|
|
|
void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex)
|
|
{
|
|
tabs.move (currentIndex, newIndex);
|
|
resized();
|
|
}
|
|
|
|
int TabbedButtonBar::getNumTabs() const
|
|
{
|
|
return tabs.size();
|
|
}
|
|
|
|
const String TabbedButtonBar::getCurrentTabName() const
|
|
{
|
|
TabInfo* tab = tabs [currentTabIndex];
|
|
return tab == 0 ? String::empty : tab->name;
|
|
}
|
|
|
|
const StringArray TabbedButtonBar::getTabNames() const
|
|
{
|
|
StringArray names;
|
|
|
|
for (int i = 0; i < tabs.size(); ++i)
|
|
names.add (tabs.getUnchecked(i)->name);
|
|
|
|
return names;
|
|
}
|
|
|
|
void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_)
|
|
{
|
|
if (currentTabIndex != newIndex)
|
|
{
|
|
if (((unsigned int) newIndex) >= (unsigned int) tabs.size())
|
|
newIndex = -1;
|
|
|
|
currentTabIndex = newIndex;
|
|
|
|
for (int i = 0; i < tabs.size(); ++i)
|
|
{
|
|
TabBarButton* tb = tabs.getUnchecked(i)->component;
|
|
tb->setToggleState (i == newIndex, false);
|
|
}
|
|
|
|
resized();
|
|
|
|
if (sendChangeMessage_)
|
|
sendChangeMessage (this);
|
|
|
|
currentTabChanged (newIndex, getCurrentTabName());
|
|
}
|
|
}
|
|
|
|
TabBarButton* TabbedButtonBar::getTabButton (const int index) const
|
|
{
|
|
TabInfo* const tab = tabs[index];
|
|
return tab == 0 ? 0 : static_cast <TabBarButton*> (tab->component);
|
|
}
|
|
|
|
int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
|
|
{
|
|
for (int i = tabs.size(); --i >= 0;)
|
|
if (tabs.getUnchecked(i)->component == button)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
void TabbedButtonBar::lookAndFeelChanged()
|
|
{
|
|
extraTabsButton = 0;
|
|
resized();
|
|
}
|
|
|
|
void TabbedButtonBar::resized()
|
|
{
|
|
int depth = getWidth();
|
|
int length = getHeight();
|
|
|
|
if (orientation == TabsAtTop || orientation == TabsAtBottom)
|
|
swapVariables (depth, length);
|
|
|
|
const int overlap = getLookAndFeel().getTabButtonOverlap (depth)
|
|
+ getLookAndFeel().getTabButtonSpaceAroundImage() * 2;
|
|
|
|
int i, totalLength = overlap;
|
|
int numVisibleButtons = tabs.size();
|
|
|
|
for (i = 0; i < tabs.size(); ++i)
|
|
{
|
|
TabBarButton* const tb = tabs.getUnchecked(i)->component;
|
|
|
|
totalLength += tb->getBestTabLength (depth) - overlap;
|
|
tb->overlapPixels = overlap / 2;
|
|
}
|
|
|
|
double scale = 1.0;
|
|
|
|
if (totalLength > length)
|
|
scale = jmax (minimumScale, length / (double) totalLength);
|
|
|
|
const bool isTooBig = totalLength * scale > length;
|
|
int tabsButtonPos = 0;
|
|
|
|
if (isTooBig)
|
|
{
|
|
if (extraTabsButton == 0)
|
|
{
|
|
addAndMakeVisible (extraTabsButton = getLookAndFeel().createTabBarExtrasButton());
|
|
extraTabsButton->addButtonListener (behindFrontTab);
|
|
extraTabsButton->setAlwaysOnTop (true);
|
|
extraTabsButton->setTriggeredOnMouseDown (true);
|
|
}
|
|
|
|
const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
|
|
extraTabsButton->setSize (buttonSize, buttonSize);
|
|
|
|
if (orientation == TabsAtTop || orientation == TabsAtBottom)
|
|
{
|
|
tabsButtonPos = getWidth() - buttonSize / 2 - 1;
|
|
extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
|
|
}
|
|
else
|
|
{
|
|
tabsButtonPos = getHeight() - buttonSize / 2 - 1;
|
|
extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
|
|
}
|
|
|
|
totalLength = 0;
|
|
|
|
for (i = 0; i < tabs.size(); ++i)
|
|
{
|
|
TabBarButton* const tb = tabs.getUnchecked(i)->component;
|
|
|
|
const int newLength = totalLength + tb->getBestTabLength (depth);
|
|
|
|
if (i > 0 && newLength * minimumScale > tabsButtonPos)
|
|
{
|
|
totalLength += overlap;
|
|
break;
|
|
}
|
|
|
|
numVisibleButtons = i + 1;
|
|
totalLength = newLength - overlap;
|
|
}
|
|
|
|
scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
|
|
}
|
|
else
|
|
{
|
|
extraTabsButton = 0;
|
|
}
|
|
|
|
int pos = 0;
|
|
|
|
TabBarButton* frontTab = 0;
|
|
|
|
for (i = 0; i < tabs.size(); ++i)
|
|
{
|
|
TabBarButton* const tb = getTabButton (i);
|
|
|
|
if (tb != 0)
|
|
{
|
|
const int bestLength = roundToInt (scale * tb->getBestTabLength (depth));
|
|
|
|
if (i < numVisibleButtons)
|
|
{
|
|
if (orientation == TabsAtTop || orientation == TabsAtBottom)
|
|
tb->setBounds (pos, 0, bestLength, getHeight());
|
|
else
|
|
tb->setBounds (0, pos, getWidth(), bestLength);
|
|
|
|
tb->toBack();
|
|
|
|
if (i == currentTabIndex)
|
|
frontTab = tb;
|
|
|
|
tb->setVisible (true);
|
|
}
|
|
else
|
|
{
|
|
tb->setVisible (false);
|
|
}
|
|
|
|
pos += bestLength - overlap;
|
|
}
|
|
}
|
|
|
|
behindFrontTab->setBounds (getLocalBounds());
|
|
|
|
if (frontTab != 0)
|
|
{
|
|
frontTab->toFront (false);
|
|
behindFrontTab->toBehind (frontTab);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
const Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
|
|
{
|
|
TabInfo* const tab = tabs [tabIndex];
|
|
return tab == 0 ? Colours::white : tab->colour;
|
|
}
|
|
|
|
void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour)
|
|
{
|
|
TabInfo* const tab = tabs [tabIndex];
|
|
|
|
if (tab != 0 && tab->colour != newColour)
|
|
{
|
|
tab->colour = newColour;
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
void TabbedButtonBar::showExtraItemsMenu()
|
|
{
|
|
PopupMenu m;
|
|
|
|
for (int i = 0; i < tabs.size(); ++i)
|
|
{
|
|
const TabInfo* const tab = tabs.getUnchecked(i);
|
|
|
|
if (! tab->component->isVisible())
|
|
m.addItem (i + 1, tab->name, true, i == currentTabIndex);
|
|
}
|
|
|
|
const int res = m.showAt (extraTabsButton);
|
|
|
|
if (res != 0)
|
|
setCurrentTabIndex (res - 1);
|
|
}
|
|
|
|
//==============================================================================
|
|
void TabbedButtonBar::currentTabChanged (const int, const String&)
|
|
{
|
|
}
|
|
|
|
void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
|
|
{
|
|
}
|
|
|
|
END_JUCE_NAMESPACE
|