* Copyright (C) 2010 Rene Gollent <rene@gollent.com>
* Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "TabManager.h"
#include <stdio.h>
#include <new>
#include <Application.h>
#include <AbstractLayoutItem.h>
#include <Bitmap.h>
#include <Button.h>
#include <CardLayout.h>
#include <ControlLook.h>
#include <Catalog.h>
#include <GroupView.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <Rect.h>
#include <SpaceLayoutItem.h>
#include <Window.h>
#include "TabContainerView.h"
#include "TabView.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Tab Manager"
const static BString kEmptyString;
class TabButton : public BButton {
public:
TabButton(BMessage* message)
: BButton("", message)
{
}
virtual BSize MinSize()
{
return BSize(12, 12);
}
virtual BSize MaxSize()
{
return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
}
virtual BSize PreferredSize()
{
return MinSize();
}
virtual void Draw(BRect updateRect)
{
BRect bounds(Bounds());
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
uint32 flags = be_control_look->Flags(this);
uint32 borders = BControlLook::B_TOP_BORDER
| BControlLook::B_BOTTOM_BORDER;
be_control_look->DrawTabFrame(this, bounds, updateRect, base,
0, borders, B_NO_BORDER);
if (IsEnabled()) {
rgb_color button = tint_color(base, 1.07);
be_control_look->DrawButtonBackground(this, bounds, updateRect,
button, flags, 0);
}
BRect symbolRect(bounds);
symbolRect.left = (symbolRect.left + symbolRect.right) / 2 - 6;
symbolRect.top = (symbolRect.top + symbolRect.bottom) / 2 - 6;
symbolRect.right = symbolRect.left + 12;
symbolRect.bottom = symbolRect.top + 12;
DrawSymbol(symbolRect, updateRect, base);
}
virtual void DrawSymbol(BRect frame, const BRect& updateRect,
const rgb_color& base)
{
}
};
class ScrollLeftTabButton : public TabButton {
public:
ScrollLeftTabButton(BMessage* message)
: TabButton(message)
{
}
virtual void DrawSymbol(BRect frame, const BRect& updateRect,
const rgb_color& base)
{
float tint = IsEnabled() ? B_DARKEN_4_TINT : B_DARKEN_1_TINT;
be_control_look->DrawArrowShape(this, frame, updateRect,
base, BControlLook::B_LEFT_ARROW, 0, tint);
}
};
class ScrollRightTabButton : public TabButton {
public:
ScrollRightTabButton(BMessage* message)
: TabButton(message)
{
}
virtual void DrawSymbol(BRect frame, const BRect& updateRect,
const rgb_color& base)
{
frame.OffsetBy(1, 0);
float tint = IsEnabled() ? B_DARKEN_4_TINT : B_DARKEN_1_TINT;
be_control_look->DrawArrowShape(this, frame, updateRect,
base, BControlLook::B_RIGHT_ARROW, 0, tint);
}
};
class NewTabButton : public TabButton {
public:
NewTabButton(BMessage* message)
: TabButton(message)
{
SetToolTip("New tab (Cmd-T)");
}
virtual BSize MinSize()
{
return BSize(18, 12);
}
virtual void DrawSymbol(BRect frame, const BRect& updateRect,
const rgb_color& base)
{
SetHighColor(tint_color(base, B_DARKEN_4_TINT));
float inset = 3;
frame.InsetBy(2, 2);
frame.top++;
frame.left++;
FillRoundRect(BRect(frame.left, frame.top + inset,
frame.right, frame.bottom - inset), 1, 1);
FillRoundRect(BRect(frame.left + inset, frame.top,
frame.right - inset, frame.bottom), 1, 1);
}
};
class TabMenuTabButton : public TabButton {
public:
TabMenuTabButton(BMessage* message)
: TabButton(message)
, fCloseTime(0)
{
}
virtual BSize MinSize()
{
return BSize(18, 12);
}
virtual void DrawSymbol(BRect frame, const BRect& updateRect,
const rgb_color& base)
{
be_control_look->DrawArrowShape(this, frame, updateRect,
base, BControlLook::B_DOWN_ARROW, 0, B_DARKEN_4_TINT);
}
virtual void MouseDown(BPoint point)
{
bigtime_t clickSpeed = 2000000;
get_click_speed(&clickSpeed);
bigtime_t clickTime = Window()->CurrentMessage()->FindInt64("when");
if (!IsEnabled() || (Value() == B_CONTROL_ON)
|| clickTime < fCloseTime + clickSpeed) {
return;
}
Invoke();
SetValue(B_CONTROL_ON);
}
virtual void MouseUp(BPoint point)
{
}
void MenuClosed()
{
fCloseTime = system_time();
SetValue(B_CONTROL_OFF);
}
private:
bigtime_t fCloseTime;
};
enum {
MSG_SCROLL_TABS_LEFT = 'stlt',
MSG_SCROLL_TABS_RIGHT = 'strt',
MSG_OPEN_TAB_MENU = 'otmn'
};
class TabContainerGroup : public BGroupView {
public:
TabContainerGroup(TabContainerView* tabContainerView)
:
BGroupView(B_HORIZONTAL, 0.0),
fTabContainerView(tabContainerView),
fScrollLeftTabButton(NULL),
fScrollRightTabButton(NULL),
fTabMenuButton(NULL)
{
}
virtual void AttachedToWindow()
{
if (fScrollLeftTabButton != NULL)
fScrollLeftTabButton->SetTarget(this);
if (fScrollRightTabButton != NULL)
fScrollRightTabButton->SetTarget(this);
if (fTabMenuButton != NULL)
fTabMenuButton->SetTarget(this);
}
virtual void MessageReceived(BMessage* message)
{
if (fTabContainerView == NULL)
return BGroupView::MessageReceived(message);
switch (message->what) {
case MSG_SCROLL_TABS_LEFT:
fTabContainerView->SetFirstVisibleTabIndex(
fTabContainerView->FirstVisibleTabIndex() - 1);
break;
case MSG_SCROLL_TABS_RIGHT:
fTabContainerView->SetFirstVisibleTabIndex(
fTabContainerView->FirstVisibleTabIndex() + 1);
break;
case MSG_OPEN_TAB_MENU:
{
BPopUpMenu* tabMenu = new BPopUpMenu("tab menu", true, false);
int tabCount = fTabContainerView->GetLayout()->CountItems();
for (int i = 0; i < tabCount; i++) {
TabView* tab = fTabContainerView->TabAt(i);
if (tab != NULL) {
BMenuItem* item = new(std::nothrow)
BMenuItem(tab->Label(), NULL);
if (item != NULL) {
tabMenu->AddItem(item);
if (i == fTabContainerView->SelectedTabIndex())
item->SetMarked(true);
}
}
}
tabMenu->AttachedToWindow();
BRect buttonFrame = fTabMenuButton->Frame();
BRect menuFrame = tabMenu->Frame();
BPoint openPoint = ConvertToScreen(buttonFrame.LeftBottom());
openPoint.x -= menuFrame.Width() - buttonFrame.Width();
openPoint.y += 2;
BMenuItem *selected = tabMenu->Go(openPoint, false, false,
ConvertToScreen(buttonFrame));
if (selected) {
selected->SetMarked(true);
int32 index = tabMenu->IndexOf(selected);
if (index != B_ERROR)
fTabContainerView->SelectTab(index);
}
fTabMenuButton->MenuClosed();
delete tabMenu;
break;
}
default:
BGroupView::MessageReceived(message);
break;
}
}
void AddScrollLeftButton(TabButton* button)
{
fScrollLeftTabButton = button;
GroupLayout()->AddView(button, 0.0f);
}
void AddScrollRightButton(TabButton* button)
{
fScrollRightTabButton = button;
GroupLayout()->AddView(button, 0.0f);
}
void AddTabMenuButton(TabMenuTabButton* button)
{
fTabMenuButton = button;
GroupLayout()->AddView(button, 0.0f);
}
void EnableScrollButtons(bool canScrollLeft, bool canScrollRight)
{
fScrollLeftTabButton->SetEnabled(canScrollLeft);
fScrollRightTabButton->SetEnabled(canScrollRight);
if (!canScrollLeft && !canScrollRight) {
} else {
}
}
private:
TabContainerView* fTabContainerView;
TabButton* fScrollLeftTabButton;
TabButton* fScrollRightTabButton;
TabMenuTabButton* fTabMenuButton;
};
class TabButtonContainer : public BGroupView {
public:
TabButtonContainer()
:
BGroupView(B_HORIZONTAL, 0.0)
{
SetFlags(Flags() | B_WILL_DRAW);
SetViewColor(B_TRANSPARENT_COLOR);
SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
GroupLayout()->SetInsets(0, 6, 0, 0);
}
virtual void Draw(BRect updateRect)
{
}
};
class TabManagerController : public TabContainerView::Controller {
public:
TabManagerController(TabManager* manager);
virtual ~TabManagerController();
virtual void UpdateSelection(int32 index)
{
fManager->SelectTab(index);
}
virtual bool HasFrames()
{
return false;
}
virtual TabView* CreateTabView();
virtual void DoubleClickOutsideTabs();
virtual void UpdateTabScrollability(bool canScrollLeft,
bool canScrollRight)
{
fTabContainerGroup->EnableScrollButtons(canScrollLeft, canScrollRight);
}
virtual void SetToolTip(const BString& text)
{
if (fCurrentToolTip == text)
return;
fCurrentToolTip = text;
fManager->GetTabContainerView()->HideToolTip();
fManager->GetTabContainerView()->SetToolTip(
reinterpret_cast<BToolTip*>(NULL));
fManager->GetTabContainerView()->SetToolTip(fCurrentToolTip.String());
}
void CloseTab(int32 index);
void SetCloseButtonsAvailable(bool available)
{
fCloseButtonsAvailable = available;
}
bool CloseButtonsAvailable() const
{
return fCloseButtonsAvailable;
}
void SetDoubleClickOutsideTabsMessage(const BMessage& message,
const BMessenger& target);
void SetTabContainerGroup(TabContainerGroup* tabContainerGroup)
{
fTabContainerGroup = tabContainerGroup;
}
private:
TabManager* fManager;
TabContainerGroup* fTabContainerGroup;
bool fCloseButtonsAvailable;
BMessage* fDoubleClickOutsideTabsMessage;
BMessenger fTarget;
BString fCurrentToolTip;
};
class WebTabView : public TabView {
public:
WebTabView(TabManagerController* controller);
~WebTabView();
virtual BSize MaxSize();
virtual void DrawContents(BView* owner, BRect frame,
const BRect& updateRect);
virtual void MouseDown(BPoint where, uint32 buttons);
virtual void MouseUp(BPoint where);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage);
void SetIcon(const BBitmap* icon);
private:
void _DrawCloseButton(BView* owner, BRect& frame, const BRect& updateRect);
BRect _CloseRectFrame(BRect frame) const;
private:
BBitmap* fIcon;
TabManagerController* fController;
bool fOverCloseRect;
bool fClicked;
};
WebTabView::WebTabView(TabManagerController* controller)
:
TabView(),
fIcon(NULL),
fController(controller),
fOverCloseRect(false),
fClicked(false)
{
}
WebTabView::~WebTabView()
{
delete fIcon;
}
static const int kIconSize = 18;
static const int kIconInset = 3;
BSize
WebTabView::MaxSize()
{
BSize size(TabView::MaxSize());
size.height = max_c(size.height, kIconSize + kIconInset * 2);
if (fIcon)
size.width += kIconSize + kIconInset * 2;
size.width += size.height;
return size;
}
void
WebTabView::DrawContents(BView* owner, BRect frame, const BRect& updateRect)
{
if (fController->CloseButtonsAvailable())
_DrawCloseButton(owner, frame, updateRect);
if (fIcon != NULL) {
BRect iconBounds(0, 0, kIconSize - 1, kIconSize - 1);
if (iconBounds.Contains(fIcon->Bounds()))
iconBounds = fIcon->Bounds();
else {
float scale = 2;
while ((fIcon->Bounds().Width() + 1) / scale > kIconSize)
scale *= 2;
if ((fIcon->Bounds().Width() + 1) / scale >= kIconSize - 4
&& (fIcon->Bounds().Height() + 1) / scale >= kIconSize - 4
&& (fIcon->Bounds().Height() + 1) / scale <= kIconSize) {
iconBounds.right = (fIcon->Bounds().Width() + 1) / scale - 1;
iconBounds.bottom = (fIcon->Bounds().Height() + 1) / scale - 1;
}
}
frame.top -= 2.0f;
BPoint iconPos(frame.left + kIconInset - 1,
frame.top + floorf((frame.Height() - iconBounds.Height()) / 2));
iconBounds.OffsetTo(iconPos);
owner->SetDrawingMode(B_OP_ALPHA);
owner->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
owner->DrawBitmap(fIcon, fIcon->Bounds(), iconBounds,
B_FILTER_BITMAP_BILINEAR);
owner->SetDrawingMode(B_OP_COPY);
frame.left = frame.left + kIconSize + kIconInset * 2;
}
TabView::DrawContents(owner, frame, updateRect);
}
void
WebTabView::MouseDown(BPoint where, uint32 buttons)
{
if (buttons & B_TERTIARY_MOUSE_BUTTON) {
fController->CloseTab(ContainerView()->IndexOf(this));
return;
}
BRect closeRect = _CloseRectFrame(Frame());
if (!fController->CloseButtonsAvailable() || !closeRect.Contains(where)) {
TabView::MouseDown(where, buttons);
return;
}
fClicked = true;
ContainerView()->Invalidate(closeRect);
}
void
WebTabView::MouseUp(BPoint where)
{
if (!fClicked) {
TabView::MouseUp(where);
return;
}
fClicked = false;
if (_CloseRectFrame(Frame()).Contains(where))
fController->CloseTab(ContainerView()->IndexOf(this));
}
void
WebTabView::MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage)
{
BRect closeRect = _CloseRectFrame(Frame());
bool overCloseRect = closeRect.Contains(where);
if (overCloseRect != fOverCloseRect
&& fController->CloseButtonsAvailable()) {
fOverCloseRect = overCloseRect;
ContainerView()->Invalidate(closeRect);
}
fController->SetToolTip(overCloseRect ? "" : Label());
TabView::MouseMoved(where, transit, dragMessage);
}
void
WebTabView::SetIcon(const BBitmap* icon)
{
delete fIcon;
if (icon)
fIcon = new BBitmap(icon);
else
fIcon = NULL;
LayoutItem()->InvalidateLayout();
}
BRect
WebTabView::_CloseRectFrame(BRect frame) const
{
frame.left = frame.right - frame.Height();
return frame;
}
void
WebTabView::_DrawCloseButton(BView* owner, BRect& frame,
const BRect& updateRect)
{
BRect closeRect = _CloseRectFrame(frame);
frame.right = closeRect.left - be_control_look->DefaultLabelSpacing();
closeRect.left = (closeRect.left + closeRect.right) / 2 - 3;
closeRect.right = closeRect.left + 6;
closeRect.top = (closeRect.top + closeRect.bottom) / 2 - 3;
closeRect.bottom = closeRect.top + 6;
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
float tint;
if (base.IsLight())
tint = B_DARKEN_1_TINT;
else
tint = 0.50;
float isFront = ContainerView()->SelectedTab() == static_cast<TabView*>(this);
if (base.IsLight()){
if (!isFront) {
base = tint_color(base, tint);
tint *= 1.02;
}
if (fOverCloseRect)
tint *= 1.4;
else
tint *= 1.2;
} else {
if (!isFront) {
base = tint_color(base, tint);
tint *= 0.80;
}
if (fOverCloseRect)
tint *= 0.6;
else
tint *= 0.9;
}
if (fClicked && fOverCloseRect) {
BRect buttonRect(closeRect.InsetByCopy(-4, -4));
be_control_look->DrawButtonFrame(owner, buttonRect, updateRect,
base, base,
BControlLook::B_ACTIVATED | BControlLook::B_BLEND_FRAME);
be_control_look->DrawButtonBackground(owner, buttonRect, updateRect,
base, BControlLook::B_ACTIVATED);
closeRect.OffsetBy(1, 1);
tint *= 1.2;
}
base = tint_color(base, tint);
owner->SetHighColor(base);
owner->SetPenSize(2);
owner->StrokeLine(closeRect.LeftTop(), closeRect.RightBottom());
owner->StrokeLine(closeRect.LeftBottom(), closeRect.RightTop());
owner->SetPenSize(1);
}
TabManagerController::TabManagerController(TabManager* manager)
:
fManager(manager),
fTabContainerGroup(NULL),
fCloseButtonsAvailable(false),
fDoubleClickOutsideTabsMessage(NULL)
{
}
TabManagerController::~TabManagerController()
{
delete fDoubleClickOutsideTabsMessage;
}
TabView*
TabManagerController::CreateTabView()
{
return new WebTabView(this);
}
void
TabManagerController::DoubleClickOutsideTabs()
{
fTarget.SendMessage(fDoubleClickOutsideTabsMessage);
}
void
TabManagerController::CloseTab(int32 index)
{
fManager->CloseTab(index);
}
void
TabManagerController::SetDoubleClickOutsideTabsMessage(const BMessage& message,
const BMessenger& target)
{
delete fDoubleClickOutsideTabsMessage;
fDoubleClickOutsideTabsMessage = new BMessage(message);
fTarget = target;
}
TabManager::TabManager(const BMessenger& target, BMessage* newTabMessage)
:
fController(new TabManagerController(this)),
fTarget(target)
{
fController->SetDoubleClickOutsideTabsMessage(*newTabMessage,
be_app_messenger);
fContainerView = new BView("web view container", 0);
fCardLayout = new BCardLayout();
fContainerView->SetLayout(fCardLayout);
fTabContainerView = new TabContainerView(fController);
fTabContainerGroup = new TabContainerGroup(fTabContainerView);
fTabContainerGroup->GroupLayout()->SetInsets(0, 3, 0, 0);
fController->SetTabContainerGroup(fTabContainerGroup);
#if INTEGRATE_MENU_INTO_TAB_BAR
fMenuContainer = new BGroupView(B_HORIZONTAL, 0);
fMenuContainer->GroupLayout()->SetInsets(0, -3, 0, -3);
fTabContainerGroup->GroupLayout()->AddView(fMenuContainer, 0.0f);
#endif
fTabContainerGroup->GroupLayout()->AddView(fTabContainerView);
fTabContainerGroup->AddScrollLeftButton(new ScrollLeftTabButton(
new BMessage(MSG_SCROLL_TABS_LEFT)));
fTabContainerGroup->AddScrollRightButton(new ScrollRightTabButton(
new BMessage(MSG_SCROLL_TABS_RIGHT)));
NewTabButton* newTabButton = new NewTabButton(newTabMessage);
newTabButton->SetTarget(be_app);
fTabContainerGroup->GroupLayout()->AddView(newTabButton, 0.0f);
fTabContainerGroup->AddTabMenuButton(new TabMenuTabButton(
new BMessage(MSG_OPEN_TAB_MENU)));
}
TabManager::~TabManager()
{
delete fController;
}
void
TabManager::SetTarget(const BMessenger& target)
{
fTarget = target;
}
const BMessenger&
TabManager::Target() const
{
return fTarget;
}
#if INTEGRATE_MENU_INTO_TAB_BAR
BGroupLayout*
TabManager::MenuContainerLayout() const
{
return fMenuContainer->GroupLayout();
}
#endif
BView*
TabManager::TabGroup() const
{
return fTabContainerGroup;
}
BView*
TabManager::GetTabContainerView() const
{
return fTabContainerView;
}
BView*
TabManager::ContainerView() const
{
return fContainerView;
}
BView*
TabManager::ViewForTab(int32 tabIndex) const
{
BLayoutItem* item = fCardLayout->ItemAt(tabIndex);
if (item != NULL)
return item->View();
return NULL;
}
int32
TabManager::TabForView(const BView* containedView) const
{
int32 count = fCardLayout->CountItems();
for (int32 i = 0; i < count; i++) {
BLayoutItem* item = fCardLayout->ItemAt(i);
if (item->View() == containedView)
return i;
}
return -1;
}
bool
TabManager::HasView(const BView* containedView) const
{
return TabForView(containedView) >= 0;
}
void
TabManager::SelectTab(int32 tabIndex)
{
fCardLayout->SetVisibleItem(tabIndex);
fTabContainerView->SelectTab(tabIndex);
BMessage message(TAB_CHANGED);
message.AddInt32("tab index", tabIndex);
fTarget.SendMessage(&message);
}
void
TabManager::SelectTab(const BView* containedView)
{
int32 tabIndex = TabForView(containedView);
if (tabIndex >= 0)
SelectTab(tabIndex);
}
int32
TabManager::SelectedTabIndex() const
{
return fCardLayout->VisibleIndex();
}
void
TabManager::CloseTab(int32 tabIndex)
{
BMessage message(CLOSE_TAB);
message.AddInt32("tab index", tabIndex);
fTarget.SendMessage(&message);
}
void
TabManager::AddTab(BView* view, const char* label, int32 index)
{
fTabContainerView->AddTab(label, index);
fCardLayout->AddView(index, view);
}
BView*
TabManager::RemoveTab(int32 index)
{
BLayoutItem* item = fCardLayout->RemoveItem(index);
if (item == NULL)
return NULL;
TabView* tab = fTabContainerView->RemoveTab(index);
delete tab;
BView* view = item->View();
delete item;
return view;
}
int32
TabManager::CountTabs() const
{
return fCardLayout->CountItems();
}
void
TabManager::SetTabLabel(int32 tabIndex, const char* label)
{
fTabContainerView->SetTabLabel(tabIndex, label);
}
const BString&
TabManager::TabLabel(int32 tabIndex)
{
TabView* tab = fTabContainerView->TabAt(tabIndex);
if (tab)
return tab->Label();
else
return kEmptyString;
}
void
TabManager::SetTabIcon(const BView* containedView, const BBitmap* icon)
{
WebTabView* tab = dynamic_cast<WebTabView*>(fTabContainerView->TabAt(
TabForView(containedView)));
if (tab)
tab->SetIcon(icon);
}
void
TabManager::SetCloseButtonsAvailable(bool available)
{
if (available == fController->CloseButtonsAvailable())
return;
fController->SetCloseButtonsAvailable(available);
fTabContainerView->Invalidate();
}