1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 namespace TabbedComponentHelpers
30 {
31     const Identifier deleteComponentId ("deleteByTabComp_");
32 
deleteIfNecessary(Component * comp)33     static void deleteIfNecessary (Component* comp)
34     {
35         if (comp != nullptr && (bool) comp->getProperties() [deleteComponentId])
36             delete comp;
37     }
38 
getTabArea(Rectangle<int> & content,BorderSize<int> & outline,TabbedButtonBar::Orientation orientation,int tabDepth)39     static Rectangle<int> getTabArea (Rectangle<int>& content, BorderSize<int>& outline,
40                                       TabbedButtonBar::Orientation orientation, int tabDepth)
41     {
42         switch (orientation)
43         {
44             case TabbedButtonBar::TabsAtTop:    outline.setTop (0);     return content.removeFromTop (tabDepth);
45             case TabbedButtonBar::TabsAtBottom: outline.setBottom (0);  return content.removeFromBottom (tabDepth);
46             case TabbedButtonBar::TabsAtLeft:   outline.setLeft (0);    return content.removeFromLeft (tabDepth);
47             case TabbedButtonBar::TabsAtRight:  outline.setRight (0);   return content.removeFromRight (tabDepth);
48             default: jassertfalse; break;
49         }
50 
51         return Rectangle<int>();
52     }
53 }
54 
55 //==============================================================================
56 struct TabbedComponent::ButtonBar  : public TabbedButtonBar
57 {
ButtonBarjuce::TabbedComponent::ButtonBar58     ButtonBar (TabbedComponent& tabComp, TabbedButtonBar::Orientation o)
59         : TabbedButtonBar (o), owner (tabComp)
60     {
61     }
62 
currentTabChangedjuce::TabbedComponent::ButtonBar63     void currentTabChanged (int newCurrentTabIndex, const String& newTabName)
64     {
65         owner.changeCallback (newCurrentTabIndex, newTabName);
66     }
67 
popupMenuClickOnTabjuce::TabbedComponent::ButtonBar68     void popupMenuClickOnTab (int tabIndex, const String& tabName)
69     {
70         owner.popupMenuClickOnTab (tabIndex, tabName);
71     }
72 
getTabBackgroundColourjuce::TabbedComponent::ButtonBar73     Colour getTabBackgroundColour (int tabIndex)
74     {
75         return owner.tabs->getTabBackgroundColour (tabIndex);
76     }
77 
createTabButtonjuce::TabbedComponent::ButtonBar78     TabBarButton* createTabButton (const String& tabName, int tabIndex)
79     {
80         return owner.createTabButton (tabName, tabIndex);
81     }
82 
83     TabbedComponent& owner;
84 
85     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonBar)
86 };
87 
88 //==============================================================================
TabbedComponent(TabbedButtonBar::Orientation orientation)89 TabbedComponent::TabbedComponent (TabbedButtonBar::Orientation orientation)
90 {
91     tabs.reset (new ButtonBar (*this, orientation));
92     addAndMakeVisible (tabs.get());
93 }
94 
~TabbedComponent()95 TabbedComponent::~TabbedComponent()
96 {
97     clearTabs();
98     tabs.reset();
99 }
100 
101 //==============================================================================
setOrientation(TabbedButtonBar::Orientation orientation)102 void TabbedComponent::setOrientation (TabbedButtonBar::Orientation orientation)
103 {
104     tabs->setOrientation (orientation);
105     resized();
106 }
107 
getOrientation() const108 TabbedButtonBar::Orientation TabbedComponent::getOrientation() const noexcept
109 {
110     return tabs->getOrientation();
111 }
112 
setTabBarDepth(int newDepth)113 void TabbedComponent::setTabBarDepth (int newDepth)
114 {
115     if (tabDepth != newDepth)
116     {
117         tabDepth = newDepth;
118         resized();
119     }
120 }
121 
createTabButton(const String & tabName,int)122 TabBarButton* TabbedComponent::createTabButton (const String& tabName, int /*tabIndex*/)
123 {
124     return new TabBarButton (tabName, *tabs);
125 }
126 
127 //==============================================================================
clearTabs()128 void TabbedComponent::clearTabs()
129 {
130     if (panelComponent != nullptr)
131     {
132         panelComponent->setVisible (false);
133         removeChildComponent (panelComponent.get());
134         panelComponent = nullptr;
135     }
136 
137     tabs->clearTabs();
138 
139     for (int i = contentComponents.size(); --i >= 0;)
140         TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (i));
141 
142     contentComponents.clear();
143 }
144 
addTab(const String & tabName,Colour tabBackgroundColour,Component * contentComponent,bool deleteComponentWhenNotNeeded,int insertIndex)145 void TabbedComponent::addTab (const String& tabName,
146                               Colour tabBackgroundColour,
147                               Component* contentComponent,
148                               bool deleteComponentWhenNotNeeded,
149                               int insertIndex)
150 {
151     contentComponents.insert (insertIndex, WeakReference<Component> (contentComponent));
152 
153     if (deleteComponentWhenNotNeeded && contentComponent != nullptr)
154         contentComponent->getProperties().set (TabbedComponentHelpers::deleteComponentId, true);
155 
156     tabs->addTab (tabName, tabBackgroundColour, insertIndex);
157     resized();
158 }
159 
setTabName(int tabIndex,const String & newName)160 void TabbedComponent::setTabName (int tabIndex, const String& newName)
161 {
162     tabs->setTabName (tabIndex, newName);
163 }
164 
removeTab(int tabIndex)165 void TabbedComponent::removeTab (int tabIndex)
166 {
167     if (isPositiveAndBelow (tabIndex, contentComponents.size()))
168     {
169         TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (tabIndex).get());
170         contentComponents.remove (tabIndex);
171         tabs->removeTab (tabIndex);
172     }
173 }
174 
moveTab(int currentIndex,int newIndex,bool animate)175 void TabbedComponent::moveTab (int currentIndex, int newIndex, bool animate)
176 {
177     contentComponents.move (currentIndex, newIndex);
178     tabs->moveTab (currentIndex, newIndex, animate);
179 }
180 
getNumTabs() const181 int TabbedComponent::getNumTabs() const
182 {
183     return tabs->getNumTabs();
184 }
185 
getTabNames() const186 StringArray TabbedComponent::getTabNames() const
187 {
188     return tabs->getTabNames();
189 }
190 
getTabContentComponent(int tabIndex) const191 Component* TabbedComponent::getTabContentComponent (int tabIndex) const noexcept
192 {
193     return contentComponents[tabIndex].get();
194 }
195 
getTabBackgroundColour(int tabIndex) const196 Colour TabbedComponent::getTabBackgroundColour (int tabIndex) const noexcept
197 {
198     return tabs->getTabBackgroundColour (tabIndex);
199 }
200 
setTabBackgroundColour(int tabIndex,Colour newColour)201 void TabbedComponent::setTabBackgroundColour (int tabIndex, Colour newColour)
202 {
203     tabs->setTabBackgroundColour (tabIndex, newColour);
204 
205     if (getCurrentTabIndex() == tabIndex)
206         repaint();
207 }
208 
setCurrentTabIndex(int newTabIndex,bool sendChangeMessage)209 void TabbedComponent::setCurrentTabIndex (int newTabIndex, bool sendChangeMessage)
210 {
211     tabs->setCurrentTabIndex (newTabIndex, sendChangeMessage);
212 }
213 
getCurrentTabIndex() const214 int TabbedComponent::getCurrentTabIndex() const
215 {
216     return tabs->getCurrentTabIndex();
217 }
218 
getCurrentTabName() const219 String TabbedComponent::getCurrentTabName() const
220 {
221     return tabs->getCurrentTabName();
222 }
223 
setOutline(int thickness)224 void TabbedComponent::setOutline (int thickness)
225 {
226     outlineThickness = thickness;
227     resized();
228     repaint();
229 }
230 
setIndent(int indentThickness)231 void TabbedComponent::setIndent (int indentThickness)
232 {
233     edgeIndent = indentThickness;
234     resized();
235     repaint();
236 }
237 
paint(Graphics & g)238 void TabbedComponent::paint (Graphics& g)
239 {
240     g.fillAll (findColour (backgroundColourId));
241 
242     auto content = getLocalBounds();
243     BorderSize<int> outline (outlineThickness);
244     TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth);
245 
246     g.reduceClipRegion (content);
247     g.fillAll (tabs->getTabBackgroundColour (getCurrentTabIndex()));
248 
249     if (outlineThickness > 0)
250     {
251         RectangleList<int> rl (content);
252         rl.subtract (outline.subtractedFrom (content));
253 
254         g.reduceClipRegion (rl);
255         g.fillAll (findColour (outlineColourId));
256     }
257 }
258 
resized()259 void TabbedComponent::resized()
260 {
261     auto content = getLocalBounds();
262     BorderSize<int> outline (outlineThickness);
263 
264     tabs->setBounds (TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth));
265     content = BorderSize<int> (edgeIndent).subtractedFrom (outline.subtractedFrom (content));
266 
267     for (auto& c : contentComponents)
268         if (auto comp = c.get())
269             comp->setBounds (content);
270 }
271 
lookAndFeelChanged()272 void TabbedComponent::lookAndFeelChanged()
273 {
274     for (auto& c : contentComponents)
275         if (auto comp = c.get())
276           comp->lookAndFeelChanged();
277 }
278 
changeCallback(int newCurrentTabIndex,const String & newTabName)279 void TabbedComponent::changeCallback (int newCurrentTabIndex, const String& newTabName)
280 {
281     auto* newPanelComp = getTabContentComponent (getCurrentTabIndex());
282 
283     if (newPanelComp != panelComponent)
284     {
285         if (panelComponent != nullptr)
286         {
287             panelComponent->setVisible (false);
288             removeChildComponent (panelComponent);
289         }
290 
291         panelComponent = newPanelComp;
292 
293         if (panelComponent != nullptr)
294         {
295             // do these ops as two stages instead of addAndMakeVisible() so that the
296             // component has always got a parent when it gets the visibilityChanged() callback
297             addChildComponent (panelComponent);
298             panelComponent->sendLookAndFeelChange();
299             panelComponent->setVisible (true);
300             panelComponent->toFront (true);
301         }
302 
303         repaint();
304     }
305 
306     resized();
307     currentTabChanged (newCurrentTabIndex, newTabName);
308 }
309 
currentTabChanged(int,const String &)310 void TabbedComponent::currentTabChanged (int, const String&) {}
popupMenuClickOnTab(int,const String &)311 void TabbedComponent::popupMenuClickOnTab (int, const String&) {}
312 
313 } // namespace juce
314