2 * Copyright (C) 2010 Rene Gollent <rene@gollent.com>
3 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
5 * All rights reserved. Distributed under the terms of the MIT License.
8 #include "TabContainerView.h"
12 #include <Application.h>
13 #include <AbstractLayoutItem.h>
16 #include <CardLayout.h>
18 #include <ControlLook.h>
19 #include <GroupView.h>
20 #include <SpaceLayoutItem.h>
26 #undef B_TRANSLATION_CONTEXT
27 #define B_TRANSLATION_CONTEXT "Tab Manager"
30 static const float kLeftTabInset
= 4;
33 TabContainerView::TabContainerView(Controller
* controller
)
35 BGroupView(B_HORIZONTAL
, 0.0),
36 fLastMouseEventTab(NULL
),
40 fController(controller
),
41 fFirstVisibleTabIndex(0)
43 SetFlags(Flags() | B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
);
44 SetViewColor(B_TRANSPARENT_COLOR
);
45 GroupLayout()->SetInsets(kLeftTabInset
, 0, 0, 1);
46 GroupLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0.0f
);
50 TabContainerView::~TabContainerView()
56 TabContainerView::MinSize()
58 // Eventually, we want to be scrolling if the tabs don't fit.
59 BSize
size(BGroupView::MinSize());
66 TabContainerView::MessageReceived(BMessage
* message
)
68 switch (message
->what
) {
70 BGroupView::MessageReceived(message
);
76 TabContainerView::Draw(BRect updateRect
)
78 rgb_color base
= ui_color(B_PANEL_BACKGROUND_COLOR
);
79 BRect
frame(Bounds());
81 // Draw empty area before first tab.
82 uint32 borders
= BControlLook::B_TOP_BORDER
| BControlLook::B_BOTTOM_BORDER
;
83 BRect
leftFrame(frame
.left
, frame
.top
, kLeftTabInset
, frame
.bottom
);
84 be_control_look
->DrawInactiveTab(this, leftFrame
, updateRect
, base
, 0,
87 // Draw all tabs, keeping track of where they end.
88 BGroupLayout
* layout
= GroupLayout();
89 int32 count
= layout
->CountItems() - 1;
90 for (int32 i
= 0; i
< count
; i
++) {
91 TabLayoutItem
* item
= dynamic_cast<TabLayoutItem
*>(
93 if (!item
|| !item
->IsVisible())
95 item
->Parent()->Draw(updateRect
);
96 frame
.left
= item
->Frame().right
+ 1;
99 // Draw empty area after last tab.
100 be_control_look
->DrawInactiveTab(this, frame
, updateRect
, base
, 0, borders
);
105 TabContainerView::MouseDown(BPoint where
)
108 if (Window()->CurrentMessage()->FindInt32("buttons", (int32
*)&buttons
) != B_OK
)
109 buttons
= B_PRIMARY_MOUSE_BUTTON
;
111 if (Window()->CurrentMessage()->FindInt32("clicks", (int32
*)&clicks
) != B_OK
)
114 SetMouseEventMask(B_POINTER_EVENTS
, B_LOCK_WINDOW_FOCUS
);
115 if (fLastMouseEventTab
)
116 fLastMouseEventTab
->MouseDown(where
, buttons
);
118 if ((buttons
& B_TERTIARY_MOUSE_BUTTON
) != 0) {
119 // Middle click outside tabs should always open a new tab.
120 fController
->DoubleClickOutsideTabs();
121 } else if (clicks
> 1)
130 TabContainerView::MouseUp(BPoint where
)
133 if (fLastMouseEventTab
) {
134 fLastMouseEventTab
->MouseUp(where
);
136 } else if (fClickCount
> 1) {
137 // NOTE: fClickCount is >= 1 only if the first click was outside
138 // any tab. So even if fLastMouseEventTab has been reset to NULL
139 // because this tab was removed during mouse down, we wouldn't
140 // run the "outside tabs" code below.
141 fController
->DoubleClickOutsideTabs();
144 // Always check the tab under the mouse again, since we don't update
145 // it with fMouseDown == true.
146 _SendFakeMouseMoved();
151 TabContainerView::MouseMoved(BPoint where
, uint32 transit
,
152 const BMessage
* dragMessage
)
154 _MouseMoved(where
, transit
, dragMessage
);
159 TabContainerView::DoLayout()
161 BGroupView::DoLayout();
163 _ValidateTabVisibility();
164 _SendFakeMouseMoved();
168 TabContainerView::AddTab(const char* label
, int32 index
)
172 tab
= fController
->CreateTabView();
175 tab
->SetLabel(label
);
181 TabContainerView::AddTab(TabView
* tab
, int32 index
)
183 tab
->SetContainerView(this);
186 index
= GroupLayout()->CountItems() - 1;
188 bool hasFrames
= fController
!= NULL
&& fController
->HasFrames();
189 bool isFirst
= index
== 0 && hasFrames
;
190 bool isLast
= index
== GroupLayout()->CountItems() - 1 && hasFrames
;
191 bool isFront
= fSelectedTab
== NULL
;
192 tab
->Update(isFirst
, isLast
, isFront
);
194 GroupLayout()->AddItem(index
, tab
->LayoutItem());
200 = dynamic_cast<TabLayoutItem
*>(GroupLayout()->ItemAt(index
- 1));
202 item
->Parent()->SetIsLast(false);
205 SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex());
206 _ValidateTabVisibility();
211 TabContainerView::RemoveTab(int32 index
)
214 = dynamic_cast<TabLayoutItem
*>(GroupLayout()->RemoveItem(index
));
219 BRect
dirty(Bounds());
220 dirty
.left
= item
->Frame().left
;
221 TabView
* removedTab
= item
->Parent();
222 removedTab
->SetContainerView(NULL
);
224 if (removedTab
== fLastMouseEventTab
)
225 fLastMouseEventTab
= NULL
;
227 // Update tabs after or before the removed tab.
228 bool hasFrames
= fController
!= NULL
&& fController
->HasFrames();
229 item
= dynamic_cast<TabLayoutItem
*>(GroupLayout()->ItemAt(index
));
231 // This tab is behind the removed tab.
232 TabView
* tab
= item
->Parent();
233 tab
->Update(index
== 0 && hasFrames
,
234 index
== GroupLayout()->CountItems() - 2 && hasFrames
,
235 tab
== fSelectedTab
);
236 if (removedTab
== fSelectedTab
) {
239 } else if (fController
&& tab
== fSelectedTab
)
240 fController
->TabSelected(index
);
242 // The removed tab was the last tab.
243 item
= dynamic_cast<TabLayoutItem
*>(GroupLayout()->ItemAt(index
- 1));
245 TabView
* tab
= item
->Parent();
246 tab
->Update(index
== 0 && hasFrames
,
247 index
== GroupLayout()->CountItems() - 2 && hasFrames
,
248 tab
== fSelectedTab
);
249 if (removedTab
== fSelectedTab
) {
257 _ValidateTabVisibility();
264 TabContainerView::TabAt(int32 index
) const
266 TabLayoutItem
* item
= dynamic_cast<TabLayoutItem
*>(
267 GroupLayout()->ItemAt(index
));
269 return item
->Parent();
275 TabContainerView::IndexOf(TabView
* tab
) const
277 return GroupLayout()->IndexOfItem(tab
->LayoutItem());
282 TabContainerView::SelectTab(int32 index
)
285 TabLayoutItem
* item
= dynamic_cast<TabLayoutItem
*>(
286 GroupLayout()->ItemAt(index
));
288 tab
= item
->Parent();
295 TabContainerView::SelectTab(TabView
* tab
)
297 if (tab
== fSelectedTab
)
301 fSelectedTab
->SetIsFront(false);
306 fSelectedTab
->SetIsFront(true);
308 if (fController
!= NULL
) {
310 if (fSelectedTab
!= NULL
)
311 index
= GroupLayout()->IndexOfItem(tab
->LayoutItem());
313 if (!tab
->LayoutItem()->IsVisible())
314 SetFirstVisibleTabIndex(index
);
316 fController
->TabSelected(index
);
322 TabContainerView::SetTabLabel(int32 tabIndex
, const char* label
)
324 TabLayoutItem
* item
= dynamic_cast<TabLayoutItem
*>(
325 GroupLayout()->ItemAt(tabIndex
));
329 item
->Parent()->SetLabel(label
);
334 TabContainerView::SetFirstVisibleTabIndex(int32 index
)
338 if (index
> MaxFirstVisibleTabIndex())
339 index
= MaxFirstVisibleTabIndex();
340 if (fFirstVisibleTabIndex
== index
)
343 fFirstVisibleTabIndex
= index
;
345 _UpdateTabVisibility();
350 TabContainerView::FirstVisibleTabIndex() const
352 return fFirstVisibleTabIndex
;
357 TabContainerView::MaxFirstVisibleTabIndex() const
359 float availableWidth
= _AvailableWidthForTabs();
360 if (availableWidth
< 0)
362 float visibleTabsWidth
= 0;
364 BGroupLayout
* layout
= GroupLayout();
365 int32 i
= layout
->CountItems() - 2;
366 for (; i
>= 0; i
--) {
367 TabLayoutItem
* item
= dynamic_cast<TabLayoutItem
*>(
372 float itemWidth
= item
->MinSize().width
;
373 if (availableWidth
>= visibleTabsWidth
+ itemWidth
)
374 visibleTabsWidth
+= itemWidth
;
376 // The tab before this tab is the last one that can be visible.
386 TabContainerView::CanScrollLeft() const
388 return fFirstVisibleTabIndex
< MaxFirstVisibleTabIndex();
393 TabContainerView::CanScrollRight() const
395 BGroupLayout
* layout
= GroupLayout();
396 int32 count
= layout
->CountItems() - 1;
398 TabLayoutItem
* item
= dynamic_cast<TabLayoutItem
*>(
399 layout
->ItemAt(count
- 1));
400 return !item
->IsVisible();
410 TabContainerView::_TabAt(const BPoint
& where
) const
412 BGroupLayout
* layout
= GroupLayout();
413 int32 count
= layout
->CountItems() - 1;
414 for (int32 i
= 0; i
< count
; i
++) {
415 TabLayoutItem
* item
= dynamic_cast<TabLayoutItem
*>(layout
->ItemAt(i
));
416 if (item
== NULL
|| !item
->IsVisible())
418 // Account for the fact that the tab frame does not contain the
419 // visible bottom border.
420 BRect frame
= item
->Frame();
422 if (frame
.Contains(where
))
423 return item
->Parent();
430 TabContainerView::_MouseMoved(BPoint where
, uint32 _transit
,
431 const BMessage
* dragMessage
)
433 TabView
* tab
= _TabAt(where
);
435 uint32 transit
= tab
== fLastMouseEventTab
436 ? B_INSIDE_VIEW
: B_OUTSIDE_VIEW
;
437 if (fLastMouseEventTab
)
438 fLastMouseEventTab
->MouseMoved(where
, transit
, dragMessage
);
442 if (fLastMouseEventTab
!= NULL
&& fLastMouseEventTab
== tab
)
443 fLastMouseEventTab
->MouseMoved(where
, B_INSIDE_VIEW
, dragMessage
);
445 if (fLastMouseEventTab
)
446 fLastMouseEventTab
->MouseMoved(where
, B_EXITED_VIEW
, dragMessage
);
447 fLastMouseEventTab
= tab
;
448 if (fLastMouseEventTab
)
449 fLastMouseEventTab
->MouseMoved(where
, B_ENTERED_VIEW
, dragMessage
);
451 fController
->SetToolTip(
452 B_TRANSLATE("Double-click or middle-click to open new tab."));
459 TabContainerView::_ValidateTabVisibility()
461 if (fFirstVisibleTabIndex
> MaxFirstVisibleTabIndex())
462 SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex());
464 _UpdateTabVisibility();
469 TabContainerView::_UpdateTabVisibility()
471 float availableWidth
= _AvailableWidthForTabs();
472 if (availableWidth
< 0)
474 float visibleTabsWidth
= 0;
476 bool canScrollTabsLeft
= fFirstVisibleTabIndex
> 0;
477 bool canScrollTabsRight
= false;
479 BGroupLayout
* layout
= GroupLayout();
480 int32 count
= layout
->CountItems() - 1;
481 for (int32 i
= 0; i
< count
; i
++) {
482 TabLayoutItem
* item
= dynamic_cast<TabLayoutItem
*>(
484 if (i
< fFirstVisibleTabIndex
)
485 item
->SetVisible(false);
487 float itemWidth
= item
->MinSize().width
;
488 bool visible
= availableWidth
>= visibleTabsWidth
+ itemWidth
;
489 item
->SetVisible(visible
&& !canScrollTabsRight
);
490 visibleTabsWidth
+= itemWidth
;
492 canScrollTabsRight
= true;
495 fController
->UpdateTabScrollability(canScrollTabsLeft
, canScrollTabsRight
);
500 TabContainerView::_AvailableWidthForTabs() const
502 float width
= Bounds().Width() - 10;
503 // TODO: Don't really know why -10 is needed above.
507 GroupLayout()->GetInsets(&left
, NULL
, &right
, NULL
);
508 width
-= left
+ right
;
515 TabContainerView::_SendFakeMouseMoved()
519 GetMouse(&where
, &buttons
, false);
520 if (Bounds().Contains(where
))
521 _MouseMoved(where
, B_INSIDE_VIEW
, NULL
);