2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../../core/juce_StandardHeader.h"
30 #include "juce_TabbedComponent.h"
31 #include "../menus/juce_PopupMenu.h"
32 #include "../lookandfeel/juce_LookAndFeel.h"
35 //==============================================================================
36 TabBarButton::TabBarButton (const String
& name
, TabbedButtonBar
& owner_
)
41 shadow
.setShadowProperties (2.2f
, 0.7f
, 0, 0);
42 setComponentEffect (&shadow
);
43 setWantsKeyboardFocus (false);
46 TabBarButton::~TabBarButton()
50 int TabBarButton::getIndex() const
52 return owner
.indexOfTabButton (this);
55 void TabBarButton::paintButton (Graphics
& g
,
56 bool isMouseOverButton
,
59 const Rectangle
<int> area (getActiveArea());
60 g
.setOrigin (area
.getX(), area
.getY());
63 .drawTabButton (g
, area
.getWidth(), area
.getHeight(),
64 owner
.getTabBackgroundColour (getIndex()),
65 getIndex(), getButtonText(), *this,
66 owner
.getOrientation(),
67 isMouseOverButton
, isButtonDown
,
71 void TabBarButton::clicked (const ModifierKeys
& mods
)
73 if (mods
.isPopupMenu())
74 owner
.popupMenuClickOnTab (getIndex(), getButtonText());
76 owner
.setCurrentTabIndex (getIndex());
79 bool TabBarButton::hitTest (int mx
, int my
)
81 const Rectangle
<int> area (getActiveArea());
83 if (owner
.getOrientation() == TabbedButtonBar::TabsAtLeft
84 || owner
.getOrientation() == TabbedButtonBar::TabsAtRight
)
86 if (isPositiveAndBelow (mx
, getWidth())
87 && my
>= area
.getY() + overlapPixels
88 && my
< area
.getBottom() - overlapPixels
)
93 if (mx
>= area
.getX() + overlapPixels
&& mx
< area
.getRight() - overlapPixels
94 && isPositiveAndBelow (my
, getHeight()))
100 .createTabButtonShape (p
, area
.getWidth(), area
.getHeight(), getIndex(), getButtonText(), *this,
101 owner
.getOrientation(), false, false, getToggleState());
103 return p
.contains ((float) (mx
- area
.getX()),
104 (float) (my
- area
.getY()));
107 int TabBarButton::getBestTabLength (const int depth
)
109 return jlimit (depth
* 2,
111 getLookAndFeel().getTabButtonBestWidth (getIndex(), getButtonText(), depth
, *this));
114 const Rectangle
<int> TabBarButton::getActiveArea()
116 Rectangle
<int> r (getLocalBounds());
117 const int spaceAroundImage
= getLookAndFeel().getTabButtonSpaceAroundImage();
119 if (owner
.getOrientation() != TabbedButtonBar::TabsAtLeft
) r
.removeFromRight (spaceAroundImage
);
120 if (owner
.getOrientation() != TabbedButtonBar::TabsAtRight
) r
.removeFromLeft (spaceAroundImage
);
121 if (owner
.getOrientation() != TabbedButtonBar::TabsAtBottom
) r
.removeFromTop (spaceAroundImage
);
122 if (owner
.getOrientation() != TabbedButtonBar::TabsAtTop
) r
.removeFromBottom (spaceAroundImage
);
128 //==============================================================================
129 class TabbedButtonBar::BehindFrontTabComp
: public Component
,
130 public ButtonListener
// (can't use Button::Listener due to idiotic VC2005 bug)
133 BehindFrontTabComp (TabbedButtonBar
& owner_
)
136 setInterceptsMouseClicks (false, false);
139 void paint (Graphics
& g
)
141 getLookAndFeel().drawTabAreaBehindFrontButton (g
, getWidth(), getHeight(),
142 owner
, owner
.getOrientation());
145 void enablementChanged()
150 void buttonClicked (Button
*)
152 owner
.showExtraItemsMenu();
156 TabbedButtonBar
& owner
;
158 JUCE_DECLARE_NON_COPYABLE (BehindFrontTabComp
);
162 //==============================================================================
163 TabbedButtonBar::TabbedButtonBar (const Orientation orientation_
)
164 : orientation (orientation_
),
168 setInterceptsMouseClicks (false, true);
169 addAndMakeVisible (behindFrontTab
= new BehindFrontTabComp (*this));
170 setFocusContainer (true);
173 TabbedButtonBar::~TabbedButtonBar()
176 extraTabsButton
= nullptr;
179 //==============================================================================
180 void TabbedButtonBar::setOrientation (const Orientation newOrientation
)
182 orientation
= newOrientation
;
184 for (int i
= getNumChildComponents(); --i
>= 0;)
185 getChildComponent (i
)->resized();
190 TabBarButton
* TabbedButtonBar::createTabButton (const String
& name
, const int /*index*/)
192 return new TabBarButton (name
, *this);
195 void TabbedButtonBar::setMinimumTabScaleFactor (double newMinimumScale
)
197 minimumScale
= newMinimumScale
;
201 //==============================================================================
202 void TabbedButtonBar::clearTabs()
205 extraTabsButton
= nullptr;
206 setCurrentTabIndex (-1);
209 void TabbedButtonBar::addTab (const String
& tabName
,
210 const Colour
& tabBackgroundColour
,
213 jassert (tabName
.isNotEmpty()); // you have to give them all a name..
215 if (tabName
.isNotEmpty())
217 if (! isPositiveAndBelow (insertIndex
, tabs
.size()))
218 insertIndex
= tabs
.size();
220 TabInfo
* newTab
= new TabInfo();
221 newTab
->name
= tabName
;
222 newTab
->colour
= tabBackgroundColour
;
223 newTab
->component
= createTabButton (tabName
, insertIndex
);
225 jassert (newTab
->component
!= nullptr);
227 tabs
.insert (insertIndex
, newTab
);
228 addAndMakeVisible (newTab
->component
, insertIndex
);
232 if (currentTabIndex
< 0)
233 setCurrentTabIndex (0);
237 void TabbedButtonBar::setTabName (const int tabIndex
, const String
& newName
)
239 TabInfo
* const tab
= tabs
[tabIndex
];
241 if (tab
!= nullptr && tab
->name
!= newName
)
244 tab
->component
->setButtonText (newName
);
249 void TabbedButtonBar::removeTab (const int tabIndex
)
251 if (tabs
[tabIndex
] != nullptr)
253 const int oldTabIndex
= currentTabIndex
;
254 if (currentTabIndex
== tabIndex
)
255 currentTabIndex
= -1;
257 tabs
.remove (tabIndex
);
260 setCurrentTabIndex (jlimit (0, jmax (0, tabs
.size() - 1), oldTabIndex
));
264 void TabbedButtonBar::moveTab (const int currentIndex
, const int newIndex
)
266 tabs
.move (currentIndex
, newIndex
);
270 int TabbedButtonBar::getNumTabs() const
275 String
TabbedButtonBar::getCurrentTabName() const
277 TabInfo
* tab
= tabs
[currentTabIndex
];
278 return tab
== nullptr ? String::empty
: tab
->name
;
281 StringArray
TabbedButtonBar::getTabNames() const
285 for (int i
= 0; i
< tabs
.size(); ++i
)
286 names
.add (tabs
.getUnchecked(i
)->name
);
291 void TabbedButtonBar::setCurrentTabIndex (int newIndex
, const bool sendChangeMessage_
)
293 if (currentTabIndex
!= newIndex
)
295 if (! isPositiveAndBelow (newIndex
, tabs
.size()))
298 currentTabIndex
= newIndex
;
300 for (int i
= 0; i
< tabs
.size(); ++i
)
302 TabBarButton
* tb
= tabs
.getUnchecked(i
)->component
;
303 tb
->setToggleState (i
== newIndex
, false);
308 if (sendChangeMessage_
)
311 currentTabChanged (newIndex
, getCurrentTabName());
315 TabBarButton
* TabbedButtonBar::getTabButton (const int index
) const
317 TabInfo
* const tab
= tabs
[index
];
318 return tab
== nullptr ? nullptr : static_cast <TabBarButton
*> (tab
->component
);
321 int TabbedButtonBar::indexOfTabButton (const TabBarButton
* button
) const
323 for (int i
= tabs
.size(); --i
>= 0;)
324 if (tabs
.getUnchecked(i
)->component
== button
)
330 void TabbedButtonBar::lookAndFeelChanged()
332 extraTabsButton
= nullptr;
336 void TabbedButtonBar::resized()
338 int depth
= getWidth();
339 int length
= getHeight();
341 if (orientation
== TabsAtTop
|| orientation
== TabsAtBottom
)
342 std::swap (depth
, length
);
344 const int overlap
= getLookAndFeel().getTabButtonOverlap (depth
)
345 + getLookAndFeel().getTabButtonSpaceAroundImage() * 2;
347 int i
, totalLength
= overlap
;
348 int numVisibleButtons
= tabs
.size();
350 for (i
= 0; i
< tabs
.size(); ++i
)
352 TabBarButton
* const tb
= tabs
.getUnchecked(i
)->component
;
354 totalLength
+= tb
->getBestTabLength (depth
) - overlap
;
355 tb
->overlapPixels
= overlap
/ 2;
360 if (totalLength
> length
)
361 scale
= jmax (minimumScale
, length
/ (double) totalLength
);
363 const bool isTooBig
= totalLength
* scale
> length
;
364 int tabsButtonPos
= 0;
368 if (extraTabsButton
== nullptr)
370 addAndMakeVisible (extraTabsButton
= getLookAndFeel().createTabBarExtrasButton());
371 extraTabsButton
->addListener (behindFrontTab
);
372 extraTabsButton
->setAlwaysOnTop (true);
373 extraTabsButton
->setTriggeredOnMouseDown (true);
376 const int buttonSize
= jmin (proportionOfWidth (0.7f
), proportionOfHeight (0.7f
));
377 extraTabsButton
->setSize (buttonSize
, buttonSize
);
379 if (orientation
== TabsAtTop
|| orientation
== TabsAtBottom
)
381 tabsButtonPos
= getWidth() - buttonSize
/ 2 - 1;
382 extraTabsButton
->setCentrePosition (tabsButtonPos
, getHeight() / 2);
386 tabsButtonPos
= getHeight() - buttonSize
/ 2 - 1;
387 extraTabsButton
->setCentrePosition (getWidth() / 2, tabsButtonPos
);
392 for (i
= 0; i
< tabs
.size(); ++i
)
394 TabBarButton
* const tb
= tabs
.getUnchecked(i
)->component
;
396 const int newLength
= totalLength
+ tb
->getBestTabLength (depth
);
398 if (i
> 0 && newLength
* minimumScale
> tabsButtonPos
)
400 totalLength
+= overlap
;
404 numVisibleButtons
= i
+ 1;
405 totalLength
= newLength
- overlap
;
408 scale
= jmax (minimumScale
, tabsButtonPos
/ (double) totalLength
);
412 extraTabsButton
= nullptr;
417 TabBarButton
* frontTab
= nullptr;
419 for (i
= 0; i
< tabs
.size(); ++i
)
421 TabBarButton
* const tb
= getTabButton (i
);
425 const int bestLength
= roundToInt (scale
* tb
->getBestTabLength (depth
));
427 if (i
< numVisibleButtons
)
429 if (orientation
== TabsAtTop
|| orientation
== TabsAtBottom
)
430 tb
->setBounds (pos
, 0, bestLength
, getHeight());
432 tb
->setBounds (0, pos
, getWidth(), bestLength
);
436 if (i
== currentTabIndex
)
439 tb
->setVisible (true);
443 tb
->setVisible (false);
446 pos
+= bestLength
- overlap
;
450 behindFrontTab
->setBounds (getLocalBounds());
452 if (frontTab
!= nullptr)
454 frontTab
->toFront (false);
455 behindFrontTab
->toBehind (frontTab
);
459 //==============================================================================
460 const Colour
TabbedButtonBar::getTabBackgroundColour (const int tabIndex
)
462 TabInfo
* const tab
= tabs
[tabIndex
];
463 return tab
== nullptr ? Colours::white
: tab
->colour
;
466 void TabbedButtonBar::setTabBackgroundColour (const int tabIndex
, const Colour
& newColour
)
468 TabInfo
* const tab
= tabs
[tabIndex
];
470 if (tab
!= nullptr && tab
->colour
!= newColour
)
472 tab
->colour
= newColour
;
477 void TabbedButtonBar::extraItemsMenuCallback (int result
, TabbedButtonBar
* bar
)
479 if (bar
!= nullptr && result
> 0)
480 bar
->setCurrentTabIndex (result
- 1);
483 void TabbedButtonBar::showExtraItemsMenu()
487 for (int i
= 0; i
< tabs
.size(); ++i
)
489 const TabInfo
* const tab
= tabs
.getUnchecked(i
);
491 if (! tab
->component
->isVisible())
492 m
.addItem (i
+ 1, tab
->name
, true, i
== currentTabIndex
);
495 m
.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton
),
496 ModalCallbackFunction::forComponent (extraItemsMenuCallback
, this));
499 //==============================================================================
500 void TabbedButtonBar::currentTabChanged (const int, const String
&)
504 void TabbedButtonBar::popupMenuClickOnTab (const int, const String
&)