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 "TabManager.h"
12 #include <Application.h>
13 #include <AbstractLayoutItem.h>
16 #include <CardLayout.h>
17 #include <ControlLook.h>
19 #include <GroupView.h>
22 #include <PopUpMenu.h>
24 #include <SpaceLayoutItem.h>
27 #include "TabContainerView.h"
31 #undef B_TRANSLATION_CONTEXT
32 #define B_TRANSLATION_CONTEXT "Tab Manager"
35 const static BString kEmptyString
;
38 // #pragma mark - Helper classes
41 class TabButton
: public BButton
{
43 TabButton(BMessage
* message
)
44 : BButton("", message
)
48 virtual BSize
MinSize()
53 virtual BSize
MaxSize()
55 return BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
);
58 virtual BSize
PreferredSize()
63 virtual void Draw(BRect updateRect
)
65 BRect
bounds(Bounds());
66 rgb_color base
= ui_color(B_PANEL_BACKGROUND_COLOR
);
67 uint32 flags
= be_control_look
->Flags(this);
68 uint32 borders
= BControlLook::B_TOP_BORDER
69 | BControlLook::B_BOTTOM_BORDER
;
70 be_control_look
->DrawInactiveTab(this, bounds
, updateRect
, base
,
73 rgb_color button
= tint_color(base
, 1.07);
74 be_control_look
->DrawButtonBackground(this, bounds
, updateRect
,
78 bounds
.left
= (bounds
.left
+ bounds
.right
) / 2 - 6;
79 bounds
.top
= (bounds
.top
+ bounds
.bottom
) / 2 - 6;
80 bounds
.right
= bounds
.left
+ 12;
81 bounds
.bottom
= bounds
.top
+ 12;
82 DrawSymbol(bounds
, updateRect
, base
);
85 virtual void DrawSymbol(BRect frame
, const BRect
& updateRect
,
86 const rgb_color
& base
)
92 class ScrollLeftTabButton
: public TabButton
{
94 ScrollLeftTabButton(BMessage
* message
)
99 virtual void DrawSymbol(BRect frame
, const BRect
& updateRect
,
100 const rgb_color
& base
)
102 float tint
= IsEnabled() ? B_DARKEN_4_TINT
: B_DARKEN_1_TINT
;
103 be_control_look
->DrawArrowShape(this, frame
, updateRect
,
104 base
, BControlLook::B_LEFT_ARROW
, 0, tint
);
109 class ScrollRightTabButton
: public TabButton
{
111 ScrollRightTabButton(BMessage
* message
)
116 virtual void DrawSymbol(BRect frame
, const BRect
& updateRect
,
117 const rgb_color
& base
)
119 frame
.OffsetBy(1, 0);
120 float tint
= IsEnabled() ? B_DARKEN_4_TINT
: B_DARKEN_1_TINT
;
121 be_control_look
->DrawArrowShape(this, frame
, updateRect
,
122 base
, BControlLook::B_RIGHT_ARROW
, 0, tint
);
127 class NewTabButton
: public TabButton
{
129 NewTabButton(BMessage
* message
)
132 SetToolTip("New tab (Cmd-T)");
135 virtual BSize
MinSize()
137 return BSize(18, 12);
140 virtual void DrawSymbol(BRect frame
, const BRect
& updateRect
,
141 const rgb_color
& base
)
143 SetHighColor(tint_color(base
, B_DARKEN_4_TINT
));
148 FillRoundRect(BRect(frame
.left
, frame
.top
+ inset
,
149 frame
.right
, frame
.bottom
- inset
), 1, 1);
150 FillRoundRect(BRect(frame
.left
+ inset
, frame
.top
,
151 frame
.right
- inset
, frame
.bottom
), 1, 1);
156 class TabMenuTabButton
: public TabButton
{
158 TabMenuTabButton(BMessage
* message
)
164 virtual BSize
MinSize()
166 return BSize(18, 12);
169 virtual void DrawSymbol(BRect frame
, const BRect
& updateRect
,
170 const rgb_color
& base
)
172 be_control_look
->DrawArrowShape(this, frame
, updateRect
,
173 base
, BControlLook::B_DOWN_ARROW
, 0, B_DARKEN_4_TINT
);
176 virtual void MouseDown(BPoint point
)
178 // Don't reopen the menu if it's already open or freshly closed.
179 bigtime_t clickSpeed
= 2000000;
180 get_click_speed(&clickSpeed
);
181 bigtime_t clickTime
= Window()->CurrentMessage()->FindInt64("when");
182 if (!IsEnabled() || (Value() == B_CONTROL_ON
)
183 || clickTime
< fCloseTime
+ clickSpeed
) {
187 // Invoke must be called before setting B_CONTROL_ON
188 // for the button to stay "down"
190 SetValue(B_CONTROL_ON
);
193 virtual void MouseUp(BPoint point
)
200 fCloseTime
= system_time();
201 SetValue(B_CONTROL_OFF
);
205 bigtime_t fCloseTime
;
210 MSG_SCROLL_TABS_LEFT
= 'stlt',
211 MSG_SCROLL_TABS_RIGHT
= 'strt',
212 MSG_OPEN_TAB_MENU
= 'otmn'
216 class TabContainerGroup
: public BGroupView
{
218 TabContainerGroup(TabContainerView
* tabContainerView
)
220 BGroupView(B_HORIZONTAL
, 0.0),
221 fTabContainerView(tabContainerView
),
222 fScrollLeftTabButton(NULL
),
223 fScrollRightTabButton(NULL
),
228 virtual void AttachedToWindow()
230 if (fScrollLeftTabButton
!= NULL
)
231 fScrollLeftTabButton
->SetTarget(this);
232 if (fScrollRightTabButton
!= NULL
)
233 fScrollRightTabButton
->SetTarget(this);
234 if (fTabMenuButton
!= NULL
)
235 fTabMenuButton
->SetTarget(this);
238 virtual void MessageReceived(BMessage
* message
)
240 switch (message
->what
) {
241 case MSG_SCROLL_TABS_LEFT
:
242 fTabContainerView
->SetFirstVisibleTabIndex(
243 fTabContainerView
->FirstVisibleTabIndex() - 1);
245 case MSG_SCROLL_TABS_RIGHT
:
246 fTabContainerView
->SetFirstVisibleTabIndex(
247 fTabContainerView
->FirstVisibleTabIndex() + 1);
249 case MSG_OPEN_TAB_MENU
:
251 BPopUpMenu
* tabMenu
= new BPopUpMenu("tab menu", true, false);
252 int tabCount
= fTabContainerView
->GetLayout()->CountItems();
253 for (int i
= 0; i
< tabCount
; i
++) {
254 TabView
* tab
= fTabContainerView
->TabAt(i
);
256 BMenuItem
* item
= new BMenuItem(tab
->Label(), NULL
);
257 tabMenu
->AddItem(item
);
259 item
->SetMarked(true);
263 // Force layout to get the final menu size. InvalidateLayout()
264 // did not seem to work here.
265 tabMenu
->AttachedToWindow();
266 BRect buttonFrame
= fTabMenuButton
->Frame();
267 BRect menuFrame
= tabMenu
->Frame();
268 BPoint openPoint
= ConvertToScreen(buttonFrame
.LeftBottom());
269 // Open with the right side of the menu aligned with the right
270 // side of the button and a little below.
271 openPoint
.x
-= menuFrame
.Width() - buttonFrame
.Width();
274 BMenuItem
*selected
= tabMenu
->Go(openPoint
, false, false,
275 ConvertToScreen(buttonFrame
));
277 selected
->SetMarked(true);
278 int32 index
= tabMenu
->IndexOf(selected
);
279 if (index
!= B_ERROR
)
280 fTabContainerView
->SelectTab(index
);
282 fTabMenuButton
->MenuClosed();
288 BGroupView::MessageReceived(message
);
293 void AddScrollLeftButton(TabButton
* button
)
295 fScrollLeftTabButton
= button
;
296 GroupLayout()->AddView(button
, 0.0f
);
299 void AddScrollRightButton(TabButton
* button
)
301 fScrollRightTabButton
= button
;
302 GroupLayout()->AddView(button
, 0.0f
);
305 void AddTabMenuButton(TabMenuTabButton
* button
)
307 fTabMenuButton
= button
;
308 GroupLayout()->AddView(button
, 0.0f
);
311 void EnableScrollButtons(bool canScrollLeft
, bool canScrollRight
)
313 fScrollLeftTabButton
->SetEnabled(canScrollLeft
);
314 fScrollRightTabButton
->SetEnabled(canScrollRight
);
315 if (!canScrollLeft
&& !canScrollRight
) {
316 // hide scroll buttons
318 // show scroll buttons
323 TabContainerView
* fTabContainerView
;
324 TabButton
* fScrollLeftTabButton
;
325 TabButton
* fScrollRightTabButton
;
326 TabMenuTabButton
* fTabMenuButton
;
330 class TabButtonContainer
: public BGroupView
{
334 BGroupView(B_HORIZONTAL
, 0.0)
336 SetFlags(Flags() | B_WILL_DRAW
);
337 SetViewColor(B_TRANSPARENT_COLOR
);
338 SetLowUIColor(B_PANEL_BACKGROUND_COLOR
);
339 GroupLayout()->SetInsets(0, 6, 0, 0);
342 virtual void Draw(BRect updateRect
)
344 BRect
bounds(Bounds());
345 rgb_color base
= LowColor();
346 be_control_look
->DrawInactiveTab(this, bounds
, updateRect
,
347 base
, 0, BControlLook::B_TOP_BORDER
);
352 class TabManagerController
: public TabContainerView::Controller
{
354 TabManagerController(TabManager
* manager
);
356 virtual ~TabManagerController();
358 virtual void TabSelected(int32 index
)
360 fManager
->SelectTab(index
);
363 virtual bool HasFrames()
368 virtual TabView
* CreateTabView();
370 virtual void DoubleClickOutsideTabs();
372 virtual void UpdateTabScrollability(bool canScrollLeft
,
375 fTabContainerGroup
->EnableScrollButtons(canScrollLeft
, canScrollRight
);
378 virtual void SetToolTip(const BString
& text
)
380 if (fCurrentToolTip
== text
)
382 fCurrentToolTip
= text
;
383 fManager
->GetTabContainerView()->HideToolTip();
384 fManager
->GetTabContainerView()->SetToolTip(
385 reinterpret_cast<BToolTip
*>(NULL
));
386 fManager
->GetTabContainerView()->SetToolTip(fCurrentToolTip
.String());
389 void CloseTab(int32 index
);
391 void SetCloseButtonsAvailable(bool available
)
393 fCloseButtonsAvailable
= available
;
396 bool CloseButtonsAvailable() const
398 return fCloseButtonsAvailable
;
401 void SetDoubleClickOutsideTabsMessage(const BMessage
& message
,
402 const BMessenger
& target
);
404 void SetTabContainerGroup(TabContainerGroup
* tabContainerGroup
)
406 fTabContainerGroup
= tabContainerGroup
;
410 TabManager
* fManager
;
411 TabContainerGroup
* fTabContainerGroup
;
412 bool fCloseButtonsAvailable
;
413 BMessage
* fDoubleClickOutsideTabsMessage
;
415 BString fCurrentToolTip
;
419 // #pragma mark - WebTabView
422 class WebTabView
: public TabView
{
424 WebTabView(TabManagerController
* controller
);
427 virtual BSize
MaxSize();
429 virtual void DrawContents(BView
* owner
, BRect frame
, const BRect
& updateRect
,
430 bool isFirst
, bool isLast
, bool isFront
);
432 virtual void MouseDown(BPoint where
, uint32 buttons
);
433 virtual void MouseUp(BPoint where
);
434 virtual void MouseMoved(BPoint where
, uint32 transit
,
435 const BMessage
* dragMessage
);
437 void SetIcon(const BBitmap
* icon
);
440 void _DrawCloseButton(BView
* owner
, BRect
& frame
, const BRect
& updateRect
,
441 bool isFirst
, bool isLast
, bool isFront
);
442 BRect
_CloseRectFrame(BRect frame
) const;
446 TabManagerController
* fController
;
452 WebTabView::WebTabView(TabManagerController
* controller
)
456 fController(controller
),
457 fOverCloseRect(false),
463 WebTabView::~WebTabView()
469 static const int kIconSize
= 18;
470 static const int kIconInset
= 3;
474 WebTabView::MaxSize()
477 BSize
size(TabView::MaxSize());
478 size
.height
= max_c(size
.height
, kIconSize
+ kIconInset
* 2);
480 size
.width
+= kIconSize
+ kIconInset
* 2;
481 // Account for close button.
482 size
.width
+= size
.height
;
488 WebTabView::DrawContents(BView
* owner
, BRect frame
, const BRect
& updateRect
,
489 bool isFirst
, bool isLast
, bool isFront
)
491 if (fController
->CloseButtonsAvailable())
492 _DrawCloseButton(owner
, frame
, updateRect
, isFirst
, isLast
, isFront
);
495 BRect
iconBounds(0, 0, kIconSize
- 1, kIconSize
- 1);
496 // clip to icon bounds, if they are smaller
497 if (iconBounds
.Contains(fIcon
->Bounds()))
498 iconBounds
= fIcon
->Bounds();
500 // Try to scale down the icon by an even factor so the
501 // final size is between 14 and 18 pixel size. If this fails,
502 // the icon will simply be displayed at 18x18.
504 while ((fIcon
->Bounds().Width() + 1) / scale
> kIconSize
)
506 if ((fIcon
->Bounds().Width() + 1) / scale
>= kIconSize
- 4
507 && (fIcon
->Bounds().Height() + 1) / scale
>= kIconSize
- 4
508 && (fIcon
->Bounds().Height() + 1) / scale
<= kIconSize
) {
509 iconBounds
.right
= (fIcon
->Bounds().Width() + 1) / scale
- 1;
510 iconBounds
.bottom
= (fIcon
->Bounds().Height() + 1) / scale
- 1;
513 // account for borders
515 BPoint
iconPos(frame
.left
+ kIconInset
- 1,
516 frame
.top
+ floorf((frame
.Height() - iconBounds
.Height()) / 2));
517 iconBounds
.OffsetTo(iconPos
);
518 owner
->SetDrawingMode(B_OP_ALPHA
);
519 owner
->SetBlendingMode(B_PIXEL_ALPHA
, B_ALPHA_OVERLAY
);
520 owner
->DrawBitmap(fIcon
, fIcon
->Bounds(), iconBounds
,
521 B_FILTER_BITMAP_BILINEAR
);
522 owner
->SetDrawingMode(B_OP_COPY
);
523 frame
.left
= frame
.left
+ kIconSize
+ kIconInset
* 2;
526 TabView::DrawContents(owner
, frame
, updateRect
, isFirst
, isLast
, isFront
);
531 WebTabView::MouseDown(BPoint where
, uint32 buttons
)
533 if (buttons
& B_TERTIARY_MOUSE_BUTTON
) {
534 // Immediately close tab
535 fController
->CloseTab(ContainerView()->IndexOf(this));
539 BRect closeRect
= _CloseRectFrame(Frame());
540 if (!fController
->CloseButtonsAvailable() || !closeRect
.Contains(where
)) {
541 TabView::MouseDown(where
, buttons
);
546 ContainerView()->Invalidate(closeRect
);
551 WebTabView::MouseUp(BPoint where
)
554 TabView::MouseUp(where
);
560 if (_CloseRectFrame(Frame()).Contains(where
))
561 fController
->CloseTab(ContainerView()->IndexOf(this));
566 WebTabView::MouseMoved(BPoint where
, uint32 transit
,
567 const BMessage
* dragMessage
)
569 BRect closeRect
= _CloseRectFrame(Frame());
570 bool overCloseRect
= closeRect
.Contains(where
);
572 if (overCloseRect
!= fOverCloseRect
573 && fController
->CloseButtonsAvailable()) {
574 fOverCloseRect
= overCloseRect
;
575 ContainerView()->Invalidate(closeRect
);
579 fController
->SetToolTip(overCloseRect
? "" : Label());
581 TabView::MouseMoved(where
, transit
, dragMessage
);
586 WebTabView::SetIcon(const BBitmap
* icon
)
590 fIcon
= new BBitmap(icon
);
593 LayoutItem()->InvalidateLayout();
598 WebTabView::_CloseRectFrame(BRect frame
) const
600 frame
.left
= frame
.right
- frame
.Height();
605 void WebTabView::_DrawCloseButton(BView
* owner
, BRect
& frame
,
606 const BRect
& updateRect
, bool isFirst
, bool isLast
, bool isFront
)
608 BRect closeRect
= _CloseRectFrame(frame
);
609 frame
.right
= closeRect
.left
- be_control_look
->DefaultLabelSpacing();
611 closeRect
.left
= (closeRect
.left
+ closeRect
.right
) / 2 - 3;
612 closeRect
.right
= closeRect
.left
+ 6;
613 closeRect
.top
= (closeRect
.top
+ closeRect
.bottom
) / 2 - 3;
614 closeRect
.bottom
= closeRect
.top
+ 6;
616 rgb_color base
= ui_color(B_PANEL_BACKGROUND_COLOR
);
617 float tint
= B_DARKEN_1_TINT
;
619 base
= tint_color(base
, tint
);
628 if (fClicked
&& fOverCloseRect
) {
629 // Draw the button frame
630 BRect
buttonRect(closeRect
.InsetByCopy(-4, -4));
631 be_control_look
->DrawButtonFrame(owner
, buttonRect
, updateRect
,
633 BControlLook::B_ACTIVATED
| BControlLook::B_BLEND_FRAME
);
634 be_control_look
->DrawButtonBackground(owner
, buttonRect
, updateRect
,
635 base
, BControlLook::B_ACTIVATED
);
636 closeRect
.OffsetBy(1, 1);
641 base
= tint_color(base
, tint
);
642 owner
->SetHighColor(base
);
643 owner
->SetPenSize(2);
644 owner
->StrokeLine(closeRect
.LeftTop(), closeRect
.RightBottom());
645 owner
->StrokeLine(closeRect
.LeftBottom(), closeRect
.RightTop());
646 owner
->SetPenSize(1);
650 // #pragma mark - TabManagerController
653 TabManagerController::TabManagerController(TabManager
* manager
)
656 fTabContainerGroup(NULL
),
657 fCloseButtonsAvailable(false),
658 fDoubleClickOutsideTabsMessage(NULL
)
663 TabManagerController::~TabManagerController()
665 delete fDoubleClickOutsideTabsMessage
;
670 TabManagerController::CreateTabView()
672 return new WebTabView(this);
677 TabManagerController::DoubleClickOutsideTabs()
679 fTarget
.SendMessage(fDoubleClickOutsideTabsMessage
);
684 TabManagerController::CloseTab(int32 index
)
686 fManager
->CloseTab(index
);
691 TabManagerController::SetDoubleClickOutsideTabsMessage(const BMessage
& message
,
692 const BMessenger
& target
)
694 delete fDoubleClickOutsideTabsMessage
;
695 fDoubleClickOutsideTabsMessage
= new BMessage(message
);
700 // #pragma mark - TabManager
703 TabManager::TabManager(const BMessenger
& target
, BMessage
* newTabMessage
)
705 fController(new TabManagerController(this)),
708 fController
->SetDoubleClickOutsideTabsMessage(*newTabMessage
,
711 fContainerView
= new BView("web view container", 0);
712 fCardLayout
= new BCardLayout();
713 fContainerView
->SetLayout(fCardLayout
);
715 fTabContainerView
= new TabContainerView(fController
);
716 fTabContainerGroup
= new TabContainerGroup(fTabContainerView
);
717 fTabContainerGroup
->GroupLayout()->SetInsets(0, 3, 0, 0);
719 fController
->SetTabContainerGroup(fTabContainerGroup
);
721 #if INTEGRATE_MENU_INTO_TAB_BAR
722 fMenu
= new BMenu("Menu");
723 BMenuBar
* menuBar
= new BMenuBar("Menu bar");
724 menuBar
->AddItem(fMenu
);
725 TabButtonContainer
* menuBarContainer
= new TabButtonContainer();
726 menuBarContainer
->GroupLayout()->AddView(menuBar
);
727 fTabContainerGroup
->GroupLayout()->AddView(menuBarContainer
, 0.0f
);
730 fTabContainerGroup
->GroupLayout()->AddView(fTabContainerView
);
731 fTabContainerGroup
->AddScrollLeftButton(new ScrollLeftTabButton(
732 new BMessage(MSG_SCROLL_TABS_LEFT
)));
733 fTabContainerGroup
->AddScrollRightButton(new ScrollRightTabButton(
734 new BMessage(MSG_SCROLL_TABS_RIGHT
)));
735 NewTabButton
* newTabButton
= new NewTabButton(newTabMessage
);
736 newTabButton
->SetTarget(be_app
);
737 fTabContainerGroup
->GroupLayout()->AddView(newTabButton
, 0.0f
);
738 fTabContainerGroup
->AddTabMenuButton(new TabMenuTabButton(
739 new BMessage(MSG_OPEN_TAB_MENU
)));
743 TabManager::~TabManager()
750 TabManager::SetTarget(const BMessenger
& target
)
757 TabManager::Target() const
763 #if INTEGRATE_MENU_INTO_TAB_BAR
765 TabManager::Menu() const
773 TabManager::TabGroup() const
775 return fTabContainerGroup
;
780 TabManager::GetTabContainerView() const
782 return fTabContainerView
;
787 TabManager::ContainerView() const
789 return fContainerView
;
794 TabManager::ViewForTab(int32 tabIndex
) const
796 BLayoutItem
* item
= fCardLayout
->ItemAt(tabIndex
);
804 TabManager::TabForView(const BView
* containedView
) const
806 int32 count
= fCardLayout
->CountItems();
807 for (int32 i
= 0; i
< count
; i
++) {
808 BLayoutItem
* item
= fCardLayout
->ItemAt(i
);
809 if (item
->View() == containedView
)
817 TabManager::HasView(const BView
* containedView
) const
819 return TabForView(containedView
) >= 0;
824 TabManager::SelectTab(int32 tabIndex
)
826 fCardLayout
->SetVisibleItem(tabIndex
);
827 fTabContainerView
->SelectTab(tabIndex
);
829 BMessage
message(TAB_CHANGED
);
830 message
.AddInt32("tab index", tabIndex
);
831 fTarget
.SendMessage(&message
);
836 TabManager::SelectTab(const BView
* containedView
)
838 int32 tabIndex
= TabForView(containedView
);
845 TabManager::SelectedTabIndex() const
847 return fCardLayout
->VisibleIndex();
852 TabManager::CloseTab(int32 tabIndex
)
854 BMessage
message(CLOSE_TAB
);
855 message
.AddInt32("tab index", tabIndex
);
856 fTarget
.SendMessage(&message
);
861 TabManager::AddTab(BView
* view
, const char* label
, int32 index
)
863 fTabContainerView
->AddTab(label
, index
);
864 fCardLayout
->AddView(index
, view
);
869 TabManager::RemoveTab(int32 index
)
871 // It's important to remove the view first, since
872 // removing the tab will preliminary mess with the selected tab
873 // and then item count of card layout and tab container will not
875 BLayoutItem
* item
= fCardLayout
->RemoveItem(index
);
879 TabView
* tab
= fTabContainerView
->RemoveTab(index
);
882 BView
* view
= item
->View();
889 TabManager::CountTabs() const
891 return fCardLayout
->CountItems();
896 TabManager::SetTabLabel(int32 tabIndex
, const char* label
)
898 fTabContainerView
->SetTabLabel(tabIndex
, label
);
902 TabManager::TabLabel(int32 tabIndex
)
904 TabView
* tab
= fTabContainerView
->TabAt(tabIndex
);
912 TabManager::SetTabIcon(const BView
* containedView
, const BBitmap
* icon
)
914 WebTabView
* tab
= dynamic_cast<WebTabView
*>(fTabContainerView
->TabAt(
915 TabForView(containedView
)));
922 TabManager::SetCloseButtonsAvailable(bool available
)
924 if (available
== fController
->CloseButtonsAvailable())
926 fController
->SetCloseButtonsAvailable(available
);
927 fTabContainerView
->Invalidate();