* 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 "TabContainerView.h"
#include <stdio.h>
#include <Application.h>
#include <AbstractLayoutItem.h>
#include <Bitmap.h>
#include <Button.h>
#include <CardLayout.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <GroupView.h>
#include <SpaceLayoutItem.h>
#include <Window.h>
#include "TabView.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Tab Manager"
static const float kLeftTabInset = 4;
TabContainerView::TabContainerView(Controller* controller)
:
BGroupView(B_HORIZONTAL, 0.0),
fLastMouseEventTab(NULL),
fMouseDown(false),
fClickCount(0),
fSelectedTab(NULL),
fController(controller),
fFirstVisibleTabIndex(0)
{
SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
SetViewColor(B_TRANSPARENT_COLOR);
GroupLayout()->SetInsets(kLeftTabInset, 0, 0, 1);
GroupLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0.0f);
}
TabContainerView::~TabContainerView()
{
}
BSize
TabContainerView::MinSize()
{
BSize size(BGroupView::MinSize());
size.width = 300;
return size;
}
void
TabContainerView::MessageReceived(BMessage* message)
{
switch (message->what) {
default:
BGroupView::MessageReceived(message);
}
}
void
TabContainerView::Draw(BRect updateRect)
{
BRect rect(Bounds());
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
uint32 borders = BControlLook::B_TOP_BORDER
| BControlLook::B_BOTTOM_BORDER;
be_control_look->DrawTabFrame(this, rect, updateRect, base, 0,
borders, B_NO_BORDER);
BGroupLayout* layout = GroupLayout();
int32 count = layout->CountItems() - 1;
for (int32 i = 0; i < count; i++) {
TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(layout->ItemAt(i));
if (item == NULL || !item->IsVisible())
continue;
item->Parent()->Draw(item->Frame());
}
}
void
TabContainerView::MouseDown(BPoint where)
{
if (Window() == NULL)
return;
BMessage* currentMessage = Window()->CurrentMessage();
if (currentMessage == NULL)
return;
uint32 buttons;
if (currentMessage->FindInt32("buttons", (int32*)&buttons) != B_OK)
buttons = B_PRIMARY_MOUSE_BUTTON;
uint32 clicks;
if (currentMessage->FindInt32("clicks", (int32*)&clicks) != B_OK)
clicks = 1;
fMouseDown = true;
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
if (fLastMouseEventTab != NULL)
fLastMouseEventTab->MouseDown(where, buttons);
else {
if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
fController->DoubleClickOutsideTabs();
} else if (clicks > 1)
fClickCount++;
else
fClickCount = 1;
}
}
void
TabContainerView::MouseUp(BPoint where)
{
fMouseDown = false;
if (fLastMouseEventTab) {
fLastMouseEventTab->MouseUp(where);
fClickCount = 0;
} else if (fClickCount > 1) {
fController->DoubleClickOutsideTabs();
fClickCount = 0;
}
_SendFakeMouseMoved();
}
void
TabContainerView::MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage)
{
_MouseMoved(where, transit, dragMessage);
}
void
TabContainerView::DoLayout()
{
BGroupView::DoLayout();
_ValidateTabVisibility();
_SendFakeMouseMoved();
}
void
TabContainerView::AddTab(const char* label, int32 index)
{
TabView* tab;
if (fController != NULL)
tab = fController->CreateTabView();
else
tab = new TabView();
tab->SetLabel(label);
AddTab(tab, index);
}
void
TabContainerView::AddTab(TabView* tab, int32 index)
{
tab->SetContainerView(this);
if (index == -1)
index = GroupLayout()->CountItems() - 1;
tab->Update();
GroupLayout()->AddItem(index, tab->LayoutItem());
if (fSelectedTab == NULL)
SelectTab(tab);
bool isLast = index == GroupLayout()->CountItems() - 1;
if (isLast) {
TabLayoutItem* item
= dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index - 1));
if (item != NULL)
item->Parent()->Update();
}
SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex());
_ValidateTabVisibility();
}
TabView*
TabContainerView::RemoveTab(int32 index)
{
TabLayoutItem* item
= dynamic_cast<TabLayoutItem*>(GroupLayout()->RemoveItem(index));
if (item == NULL)
return NULL;
BRect dirty(Bounds());
dirty.left = item->Frame().left;
TabView* removedTab = item->Parent();
removedTab->SetContainerView(NULL);
if (removedTab == fLastMouseEventTab)
fLastMouseEventTab = NULL;
item = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index));
if (item != NULL) {
TabView* tab = item->Parent();
tab->Update();
if (removedTab == fSelectedTab) {
fSelectedTab = NULL;
SelectTab(tab);
} else if (fController != NULL && tab == fSelectedTab)
fController->UpdateSelection(index);
} else {
item = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index - 1));
if (item != NULL) {
TabView* tab = item->Parent();
tab->Update();
if (removedTab == fSelectedTab) {
fSelectedTab = NULL;
SelectTab(tab);
}
}
}
Invalidate(dirty);
_ValidateTabVisibility();
return removedTab;
}
TabView*
TabContainerView::TabAt(int32 index) const
{
TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
GroupLayout()->ItemAt(index));
if (item != NULL)
return item->Parent();
return NULL;
}
int32
TabContainerView::IndexOf(TabView* tab) const
{
if (tab == NULL || GroupLayout() == NULL)
return -1;
return GroupLayout()->IndexOfItem(tab->LayoutItem());
}
void
TabContainerView::SelectTab(int32 index)
{
TabView* tab = NULL;
TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
GroupLayout()->ItemAt(index));
if (item != NULL)
tab = item->Parent();
SelectTab(tab);
}
void
TabContainerView::SelectTab(TabView* tab)
{
if (tab == fSelectedTab)
return;
if (fSelectedTab != NULL)
fSelectedTab->Update();
fSelectedTab = tab;
if (fSelectedTab != NULL)
fSelectedTab->Update();
int32 index = -1;
if (fSelectedTab != NULL)
index = GroupLayout()->IndexOfItem(tab->LayoutItem());
if (!tab->LayoutItem()->IsVisible())
SetFirstVisibleTabIndex(index);
if (fController != NULL)
fController->UpdateSelection(index);
}
void
TabContainerView::SetTabLabel(int32 index, const char* label)
{
TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
GroupLayout()->ItemAt(index));
if (item == NULL)
return;
item->Parent()->SetLabel(label);
}
void
TabContainerView::SetFirstVisibleTabIndex(int32 index)
{
if (index < 0)
index = 0;
if (index > MaxFirstVisibleTabIndex())
index = MaxFirstVisibleTabIndex();
if (fFirstVisibleTabIndex == index)
return;
fFirstVisibleTabIndex = index;
_UpdateTabVisibility();
}
int32
TabContainerView::FirstVisibleTabIndex() const
{
return fFirstVisibleTabIndex;
}
int32
TabContainerView::MaxFirstVisibleTabIndex() const
{
float availableWidth = _AvailableWidthForTabs();
if (availableWidth < 0)
return 0;
float visibleTabsWidth = 0;
BGroupLayout* layout = GroupLayout();
int32 i = layout->CountItems() - 2;
for (; i >= 0; i--) {
TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
layout->ItemAt(i));
if (item == NULL)
continue;
float itemWidth = item->MinSize().width;
if (availableWidth >= visibleTabsWidth + itemWidth)
visibleTabsWidth += itemWidth;
else {
return i + 1;
}
}
return 0;
}
bool
TabContainerView::CanScrollLeft() const
{
return fFirstVisibleTabIndex < MaxFirstVisibleTabIndex();
}
bool
TabContainerView::CanScrollRight() const
{
BGroupLayout* layout = GroupLayout();
int32 count = layout->CountItems() - 1;
if (count > 0) {
TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
layout->ItemAt(count - 1));
return !item->IsVisible();
}
return false;
}
TabView*
TabContainerView::_TabAt(const BPoint& where) const
{
BGroupLayout* layout = GroupLayout();
int32 count = layout->CountItems() - 1;
for (int32 i = 0; i < count; i++) {
TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(layout->ItemAt(i));
if (item == NULL || !item->IsVisible())
continue;
BRect frame = item->Frame();
frame.bottom++;
if (frame.Contains(where))
return item->Parent();
}
return NULL;
}
void
TabContainerView::_MouseMoved(BPoint where, uint32 _transit,
const BMessage* dragMessage)
{
TabView* tab = _TabAt(where);
if (fMouseDown) {
uint32 transit = tab == fLastMouseEventTab
? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
if (fLastMouseEventTab)
fLastMouseEventTab->MouseMoved(where, transit, dragMessage);
return;
}
if (fLastMouseEventTab != NULL && fLastMouseEventTab == tab)
fLastMouseEventTab->MouseMoved(where, B_INSIDE_VIEW, dragMessage);
else {
if (fLastMouseEventTab)
fLastMouseEventTab->MouseMoved(where, B_EXITED_VIEW, dragMessage);
fLastMouseEventTab = tab;
if (fLastMouseEventTab)
fLastMouseEventTab->MouseMoved(where, B_ENTERED_VIEW, dragMessage);
else {
fController->SetToolTip(
B_TRANSLATE("Double-click or middle-click to open new tab."));
}
}
}
void
TabContainerView::_ValidateTabVisibility()
{
if (fFirstVisibleTabIndex > MaxFirstVisibleTabIndex())
SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex());
else
_UpdateTabVisibility();
}
void
TabContainerView::_UpdateTabVisibility()
{
float availableWidth = _AvailableWidthForTabs();
if (availableWidth < 0)
return;
float visibleTabsWidth = 0;
bool canScrollTabsLeft = fFirstVisibleTabIndex > 0;
bool canScrollTabsRight = false;
BGroupLayout* layout = GroupLayout();
int32 count = layout->CountItems() - 1;
for (int32 i = 0; i < count; i++) {
TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
layout->ItemAt(i));
if (i < fFirstVisibleTabIndex)
item->SetVisible(false);
else {
float itemWidth = item->MinSize().width;
bool visible = availableWidth >= visibleTabsWidth + itemWidth;
item->SetVisible(visible && !canScrollTabsRight);
visibleTabsWidth += itemWidth;
if (!visible)
canScrollTabsRight = true;
}
}
fController->UpdateTabScrollability(canScrollTabsLeft, canScrollTabsRight);
}
float
TabContainerView::_AvailableWidthForTabs() const
{
float width = Bounds().Width() - 10;
float left;
float right;
GroupLayout()->GetInsets(&left, NULL, &right, NULL);
width -= left + right;
return width;
}
void
TabContainerView::_SendFakeMouseMoved()
{
BPoint where;
uint32 buttons;
GetMouse(&where, &buttons, false);
if (Bounds().Contains(where))
_MouseMoved(where, B_INSIDE_VIEW, NULL);
}