haiku/src/kits/interface/TabView.cpp
Augustin Cavalier f74699a934 BTabView: Compose padding from BControlLook::DefaultLabelSpacing.
Greatly improves appearance on HiDPI.
2022-08-26 13:59:19 -04:00

1596 lines
31 KiB
C++

/*
* Copyright 2001-2015 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Marc Flerackers (mflerackers@androme.be)
* Jérôme Duval (korli@users.berlios.de)
* Stephan Aßmus <superstippi@gmx.de>
* Artur Wyszynski
* Rene Gollent (rene@gollent.com)
*/
#include <TabView.h>
#include <TabViewPrivate.h>
#include <new>
#include <math.h>
#include <string.h>
#include <CardLayout.h>
#include <ControlLook.h>
#include <GroupLayout.h>
#include <LayoutUtils.h>
#include <List.h>
#include <Message.h>
#include <PropertyInfo.h>
#include <Rect.h>
#include <Region.h>
#include <String.h>
#include <Window.h>
#include <binary_compatibility/Support.h>
static property_info sPropertyList[] = {
{
"Selection",
{ B_GET_PROPERTY, B_SET_PROPERTY },
{ B_DIRECT_SPECIFIER },
NULL, 0,
{ B_INT32_TYPE }
},
{ 0 }
};
BTab::BTab(BView* contentsView)
:
fEnabled(true),
fSelected(false),
fFocus(false),
fView(contentsView),
fTabView(NULL)
{
}
BTab::BTab(BMessage* archive)
:
BArchivable(archive),
fSelected(false),
fFocus(false),
fView(NULL),
fTabView(NULL)
{
bool disable;
if (archive->FindBool("_disable", &disable) != B_OK)
SetEnabled(true);
else
SetEnabled(!disable);
}
BTab::~BTab()
{
if (fView == NULL)
return;
if (fSelected)
fView->RemoveSelf();
delete fView;
}
BArchivable*
BTab::Instantiate(BMessage* archive)
{
if (validate_instantiation(archive, "BTab"))
return new BTab(archive);
return NULL;
}
status_t
BTab::Archive(BMessage* data, bool deep) const
{
status_t result = BArchivable::Archive(data, deep);
if (result != B_OK)
return result;
if (!fEnabled)
result = data->AddBool("_disable", false);
return result;
}
status_t
BTab::Perform(uint32 d, void* arg)
{
return BArchivable::Perform(d, arg);
}
const char*
BTab::Label() const
{
if (fView != NULL)
return fView->Name();
else
return NULL;
}
void
BTab::SetLabel(const char* label)
{
if (label == NULL || fView == NULL)
return;
fView->SetName(label);
if (fTabView != NULL)
fTabView->Invalidate();
}
bool
BTab::IsSelected() const
{
return fSelected;
}
void
BTab::Select(BView* owner)
{
fSelected = true;
if (owner == NULL || fView == NULL)
return;
// NOTE: Views are not added/removed, if there is layout,
// they are made visible/invisible in that case.
if (owner->GetLayout() == NULL && fView->Parent() == NULL)
owner->AddChild(fView);
}
void
BTab::Deselect()
{
if (fView != NULL) {
// NOTE: Views are not added/removed, if there is layout,
// they are made visible/invisible in that case.
bool removeView = false;
BView* container = fView->Parent();
if (container != NULL)
removeView =
dynamic_cast<BCardLayout*>(container->GetLayout()) == NULL;
if (removeView)
fView->RemoveSelf();
}
fSelected = false;
}
void
BTab::SetEnabled(bool enable)
{
fEnabled = enable;
}
bool
BTab::IsEnabled() const
{
return fEnabled;
}
void
BTab::MakeFocus(bool focus)
{
fFocus = focus;
}
bool
BTab::IsFocus() const
{
return fFocus;
}
void
BTab::SetView(BView* view)
{
if (view == NULL || fView == view)
return;
if (fView != NULL) {
fView->RemoveSelf();
delete fView;
}
fView = view;
if (fTabView != NULL && fSelected) {
Select(fTabView->ContainerView());
fTabView->Invalidate();
}
}
BView*
BTab::View() const
{
return fView;
}
void
BTab::DrawFocusMark(BView* owner, BRect frame)
{
float width = owner->StringWidth(Label());
owner->SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
float offset = IsSelected() ? 3 : 2;
switch (fTabView->TabSide()) {
case BTabView::kTopSide:
owner->StrokeLine(BPoint((frame.left + frame.right - width) / 2.0,
frame.bottom - offset),
BPoint((frame.left + frame.right + width) / 2.0,
frame.bottom - offset));
break;
case BTabView::kBottomSide:
owner->StrokeLine(BPoint((frame.left + frame.right - width) / 2.0,
frame.top + offset),
BPoint((frame.left + frame.right + width) / 2.0,
frame.top + offset));
break;
case BTabView::kLeftSide:
owner->StrokeLine(BPoint(frame.right - offset,
(frame.top + frame.bottom - width) / 2.0),
BPoint(frame.right - offset,
(frame.top + frame.bottom + width) / 2.0));
break;
case BTabView::kRightSide:
owner->StrokeLine(BPoint(frame.left + offset,
(frame.top + frame.bottom - width) / 2.0),
BPoint(frame.left + offset,
(frame.top + frame.bottom + width) / 2.0));
break;
}
}
void
BTab::DrawLabel(BView* owner, BRect frame)
{
float rotation = 0.0f;
BPoint center(frame.left + frame.Width() / 2,
frame.top + frame.Height() / 2);
switch (fTabView->TabSide()) {
case BTabView::kTopSide:
case BTabView::kBottomSide:
rotation = 0.0f;
break;
case BTabView::kLeftSide:
rotation = 270.0f;
break;
case BTabView::kRightSide:
rotation = 90.0f;
break;
}
if (rotation != 0.0f) {
// DrawLabel doesn't allow rendering rotated text
// rotate frame first and BAffineTransform will handle the rotation
// we can't give "unrotated" frame because it comes from
// BTabView::TabFrame and it is also used to handle mouse clicks
BRect originalFrame(frame);
frame.top = center.y - originalFrame.Width() / 2;
frame.bottom = center.y + originalFrame.Width() / 2;
frame.left = center.x - originalFrame.Height() / 2;
frame.right = center.x + originalFrame.Height() / 2;
}
BAffineTransform transform;
transform.RotateBy(center, rotation * M_PI / 180.0f);
owner->SetTransform(transform);
be_control_look->DrawLabel(owner, Label(), frame, frame,
ui_color(B_PANEL_BACKGROUND_COLOR),
IsEnabled() ? 0 : BControlLook::B_DISABLED,
BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
owner->SetTransform(BAffineTransform());
}
void
BTab::DrawTab(BView* owner, BRect frame, tab_position, bool)
{
if (fTabView == NULL)
return;
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
uint32 flags = 0;
uint32 borders = _Borders(owner, frame);
int32 index = fTabView->IndexOf(this);
int32 selected = fTabView->Selection();
int32 first = 0;
int32 last = fTabView->CountTabs() - 1;
if (index == selected) {
be_control_look->DrawActiveTab(owner, frame, frame, base, flags,
borders, fTabView->TabSide(), index, selected, first, last);
} else {
be_control_look->DrawInactiveTab(owner, frame, frame, base, flags,
borders, fTabView->TabSide(), index, selected, first, last);
}
DrawLabel(owner, frame);
}
// #pragma mark - BTab private methods
uint32
BTab::_Borders(BView* owner, BRect frame)
{
uint32 borders = 0;
if (owner == NULL || fTabView == NULL)
return borders;
if (fTabView->TabSide() == BTabView::kTopSide
|| fTabView->TabSide() == BTabView::kBottomSide) {
borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
if (frame.left == owner->Bounds().left)
borders |= BControlLook::B_LEFT_BORDER;
if (frame.right == owner->Bounds().right)
borders |= BControlLook::B_RIGHT_BORDER;
} else if (fTabView->TabSide() == BTabView::kLeftSide
|| fTabView->TabSide() == BTabView::kRightSide) {
borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
if (frame.top == owner->Bounds().top)
borders |= BControlLook::B_TOP_BORDER;
if (frame.bottom == owner->Bounds().bottom)
borders |= BControlLook::B_BOTTOM_BORDER;
}
return borders;
}
// #pragma mark - FBC padding and private methods
void BTab::_ReservedTab1() {}
void BTab::_ReservedTab2() {}
void BTab::_ReservedTab3() {}
void BTab::_ReservedTab4() {}
void BTab::_ReservedTab5() {}
void BTab::_ReservedTab6() {}
void BTab::_ReservedTab7() {}
void BTab::_ReservedTab8() {}
void BTab::_ReservedTab9() {}
void BTab::_ReservedTab10() {}
void BTab::_ReservedTab11() {}
void BTab::_ReservedTab12() {}
BTab &BTab::operator=(const BTab &)
{
// this is private and not functional, but exported
return *this;
}
// #pragma mark - BTabView
BTabView::BTabView(const char* name, button_width width, uint32 flags)
:
BView(name, flags)
{
_InitObject(true, width);
}
BTabView::BTabView(BRect frame, const char* name, button_width width,
uint32 resizeMask, uint32 flags)
:
BView(frame, name, resizeMask, flags)
{
_InitObject(false, width);
}
BTabView::~BTabView()
{
for (int32 i = 0; i < CountTabs(); i++)
delete TabAt(i);
delete fTabList;
}
BTabView::BTabView(BMessage* archive)
:
BView(BUnarchiver::PrepareArchive(archive)),
fTabList(new BList),
fContainerView(NULL),
fFocus(-1)
{
BUnarchiver unarchiver(archive);
int16 width;
if (archive->FindInt16("_but_width", &width) == B_OK)
fTabWidthSetting = (button_width)width;
else
fTabWidthSetting = B_WIDTH_AS_USUAL;
if (archive->FindFloat("_high", &fTabHeight) != B_OK) {
font_height fh;
GetFontHeight(&fh);
fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f);
}
if (archive->FindInt32("_sel", &fSelection) != B_OK)
fSelection = -1;
if (archive->FindInt32("_border_style", (int32*)&fBorderStyle) != B_OK)
fBorderStyle = B_FANCY_BORDER;
if (archive->FindInt32("_TabSide", (int32*)&fTabSide) != B_OK)
fTabSide = kTopSide;
int32 i = 0;
BMessage tabMsg;
if (BUnarchiver::IsArchiveManaged(archive)) {
int32 tabCount;
archive->GetInfo("_l_items", NULL, &tabCount);
for (int32 i = 0; i < tabCount; i++) {
unarchiver.EnsureUnarchived("_l_items", i);
unarchiver.EnsureUnarchived("_view_list", i);
}
return;
}
fContainerView = ChildAt(0);
_InitContainerView(Flags() & B_SUPPORTS_LAYOUT);
while (archive->FindMessage("_l_items", i, &tabMsg) == B_OK) {
BArchivable* archivedTab = instantiate_object(&tabMsg);
if (archivedTab) {
BTab* tab = dynamic_cast<BTab*>(archivedTab);
BMessage viewMsg;
if (archive->FindMessage("_view_list", i, &viewMsg) == B_OK) {
BArchivable* archivedView = instantiate_object(&viewMsg);
if (archivedView)
AddTab(dynamic_cast<BView*>(archivedView), tab);
}
}
tabMsg.MakeEmpty();
i++;
}
}
BArchivable*
BTabView::Instantiate(BMessage* archive)
{
if ( validate_instantiation(archive, "BTabView"))
return new BTabView(archive);
return NULL;
}
status_t
BTabView::Archive(BMessage* archive, bool deep) const
{
BArchiver archiver(archive);
status_t result = BView::Archive(archive, deep);
if (result == B_OK)
result = archive->AddInt16("_but_width", fTabWidthSetting);
if (result == B_OK)
result = archive->AddFloat("_high", fTabHeight);
if (result == B_OK)
result = archive->AddInt32("_sel", fSelection);
if (result == B_OK && fBorderStyle != B_FANCY_BORDER)
result = archive->AddInt32("_border_style", fBorderStyle);
if (result == B_OK && fTabSide != kTopSide)
result = archive->AddInt32("_TabSide", fTabSide);
if (result == B_OK && deep) {
for (int32 i = 0; i < CountTabs(); i++) {
BTab* tab = TabAt(i);
if ((result = archiver.AddArchivable("_l_items", tab, deep))
!= B_OK) {
break;
}
result = archiver.AddArchivable("_view_list", tab->View(), deep);
}
}
return archiver.Finish(result);
}
status_t
BTabView::AllUnarchived(const BMessage* archive)
{
status_t err = BView::AllUnarchived(archive);
if (err != B_OK)
return err;
fContainerView = ChildAt(0);
_InitContainerView(Flags() & B_SUPPORTS_LAYOUT);
BUnarchiver unarchiver(archive);
int32 tabCount;
archive->GetInfo("_l_items", NULL, &tabCount);
for (int32 i = 0; i < tabCount && err == B_OK; i++) {
BTab* tab;
err = unarchiver.FindObject("_l_items", i, tab);
if (err == B_OK && tab) {
BView* view;
if ((err = unarchiver.FindObject("_view_list", i,
BUnarchiver::B_DONT_ASSUME_OWNERSHIP, view)) != B_OK)
break;
tab->SetView(view);
fTabList->AddItem(tab);
}
}
if (err == B_OK)
Select(fSelection);
return err;
}
status_t
BTabView::Perform(perform_code code, void* _data)
{
switch (code) {
case PERFORM_CODE_ALL_UNARCHIVED:
{
perform_data_all_unarchived* data
= (perform_data_all_unarchived*)_data;
data->return_value = BTabView::AllUnarchived(data->archive);
return B_OK;
}
}
return BView::Perform(code, _data);
}
void
BTabView::AttachedToWindow()
{
BView::AttachedToWindow();
if (fSelection < 0 && CountTabs() > 0)
Select(0);
}
void
BTabView::DetachedFromWindow()
{
BView::DetachedFromWindow();
}
void
BTabView::AllAttached()
{
BView::AllAttached();
}
void
BTabView::AllDetached()
{
BView::AllDetached();
}
// #pragma mark -
void
BTabView::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_GET_PROPERTY:
case B_SET_PROPERTY:
{
BMessage reply(B_REPLY);
bool handled = false;
BMessage specifier;
int32 index;
int32 form;
const char* property;
if (message->GetCurrentSpecifier(&index, &specifier, &form,
&property) == B_OK) {
if (strcmp(property, "Selection") == 0) {
if (message->what == B_GET_PROPERTY) {
reply.AddInt32("result", fSelection);
handled = true;
} else {
// B_GET_PROPERTY
int32 selection;
if (message->FindInt32("data", &selection) == B_OK) {
Select(selection);
reply.AddInt32("error", B_OK);
handled = true;
}
}
}
}
if (handled)
message->SendReply(&reply);
else
BView::MessageReceived(message);
break;
}
#if 0
// TODO this would be annoying as-is, but maybe it makes sense with
// a modifier or using only deltaX (not the main mouse wheel)
case B_MOUSE_WHEEL_CHANGED:
{
float deltaX = 0.0f;
float deltaY = 0.0f;
message->FindFloat("be:wheel_delta_x", &deltaX);
message->FindFloat("be:wheel_delta_y", &deltaY);
if (deltaX == 0.0f && deltaY == 0.0f)
return;
if (deltaY == 0.0f)
deltaY = deltaX;
int32 selection = Selection();
int32 numTabs = CountTabs();
if (deltaY > 0 && selection < numTabs - 1) {
// move to the right tab.
Select(Selection() + 1);
} else if (deltaY < 0 && selection > 0 && numTabs > 1) {
// move to the left tab.
Select(selection - 1);
}
break;
}
#endif
default:
BView::MessageReceived(message);
break;
}
}
void
BTabView::KeyDown(const char* bytes, int32 numBytes)
{
if (IsHidden())
return;
switch (bytes[0]) {
case B_DOWN_ARROW:
case B_LEFT_ARROW: {
int32 focus = fFocus - 1;
if (focus < 0)
focus = CountTabs() - 1;
SetFocusTab(focus, true);
break;
}
case B_UP_ARROW:
case B_RIGHT_ARROW: {
int32 focus = fFocus + 1;
if (focus >= CountTabs())
focus = 0;
SetFocusTab(focus, true);
break;
}
case B_RETURN:
case B_SPACE:
Select(FocusTab());
break;
default:
BView::KeyDown(bytes, numBytes);
}
}
void
BTabView::MouseDown(BPoint where)
{
// Which button is pressed?
uint32 buttons = 0;
BMessage* currentMessage = Window()->CurrentMessage();
if (currentMessage != NULL) {
currentMessage->FindInt32("buttons", (int32*)&buttons);
}
int32 selection = Selection();
int32 numTabs = CountTabs();
if (buttons & B_MOUSE_BUTTON(4)) {
// The "back" mouse button moves to previous tab
if (selection > 0 && numTabs > 1)
Select(Selection() - 1);
} else if (buttons & B_MOUSE_BUTTON(5)) {
// The "forward" mouse button moves to next tab
if (selection < numTabs - 1)
Select(Selection() + 1);
} else {
// Other buttons are used to select a tab by clicking directly on it
for (int32 i = 0; i < CountTabs(); i++) {
if (TabFrame(i).Contains(where)
&& i != Selection()) {
Select(i);
return;
}
}
}
BView::MouseDown(where);
}
void
BTabView::MouseUp(BPoint where)
{
BView::MouseUp(where);
}
void
BTabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
{
BView::MouseMoved(where, transit, dragMessage);
}
void
BTabView::Pulse()
{
BView::Pulse();
}
void
BTabView::Select(int32 index)
{
if (index == Selection())
return;
if (index < 0 || index >= CountTabs())
index = Selection();
BTab* tab = TabAt(Selection());
if (tab)
tab->Deselect();
tab = TabAt(index);
if (tab != NULL && fContainerView != NULL) {
if (index == 0)
fTabOffset = 0.0f;
tab->Select(fContainerView);
fSelection = index;
// make the view visible through the layout if there is one
BCardLayout* layout
= dynamic_cast<BCardLayout*>(fContainerView->GetLayout());
if (layout != NULL)
layout->SetVisibleItem(index);
}
Invalidate();
if (index != 0 && !Bounds().Contains(TabFrame(index))){
if (!Bounds().Contains(TabFrame(index).LeftTop()))
fTabOffset += TabFrame(index).left - Bounds().left - 20.0f;
else
fTabOffset += TabFrame(index).right - Bounds().right + 20.0f;
Invalidate();
}
SetFocusTab(index, true);
}
int32
BTabView::Selection() const
{
return fSelection;
}
void
BTabView::WindowActivated(bool active)
{
BView::WindowActivated(active);
if (IsFocus())
Invalidate();
}
void
BTabView::MakeFocus(bool focus)
{
BView::MakeFocus(focus);
SetFocusTab(Selection(), focus);
}
void
BTabView::SetFocusTab(int32 tab, bool focus)
{
if (tab >= CountTabs())
tab = 0;
if (tab < 0)
tab = CountTabs() - 1;
if (focus) {
if (tab == fFocus)
return;
if (fFocus != -1){
if (TabAt (fFocus) != NULL)
TabAt(fFocus)->MakeFocus(false);
Invalidate(TabFrame(fFocus));
}
if (TabAt(tab) != NULL){
TabAt(tab)->MakeFocus(true);
Invalidate(TabFrame(tab));
fFocus = tab;
}
} else if (fFocus != -1) {
TabAt(fFocus)->MakeFocus(false);
Invalidate(TabFrame(fFocus));
fFocus = -1;
}
}
int32
BTabView::FocusTab() const
{
return fFocus;
}
void
BTabView::Draw(BRect updateRect)
{
DrawTabs();
DrawBox(TabFrame(fSelection));
if (IsFocus() && fFocus != -1)
TabAt(fFocus)->DrawFocusMark(this, TabFrame(fFocus));
}
BRect
BTabView::DrawTabs()
{
BRect bounds(Bounds());
BRect tabFrame(bounds);
uint32 borders = 0;
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
// set tabFrame to area around tabs
if (fTabSide == kTopSide || fTabSide == kBottomSide) {
if (fTabSide == kTopSide)
tabFrame.bottom = fTabHeight;
else
tabFrame.top = tabFrame.bottom - fTabHeight;
} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
if (fTabSide == kLeftSide)
tabFrame.right = fTabHeight;
else
tabFrame.left = tabFrame.right - fTabHeight;
}
// draw frame behind tabs
be_control_look->DrawTabFrame(this, tabFrame, bounds, base, 0,
borders, fBorderStyle, fTabSide);
// draw the tabs on top of the tab frame
BRect activeTabFrame;
int32 tabCount = CountTabs();
for (int32 i = 0; i < tabCount; i++) {
BRect tabFrame = TabFrame(i);
if (i == fSelection)
activeTabFrame = tabFrame;
TabAt(i)->DrawTab(this, tabFrame,
i == fSelection ? B_TAB_FRONT
: (i == 0) ? B_TAB_FIRST : B_TAB_ANY,
i != fSelection - 1);
}
BRect tabsBounds;
float last = 0.0f;
float lastTab = 0.0f;
if (fTabSide == kTopSide || fTabSide == kBottomSide) {
lastTab = TabFrame(tabCount - 1).right;
last = tabFrame.right;
tabsBounds.left = tabsBounds.right = lastTab;
borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
lastTab = TabFrame(tabCount - 1).bottom;
last = tabFrame.bottom;
tabsBounds.top = tabsBounds.bottom = lastTab;
borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
}
if (lastTab < last) {
// draw a 1px right border on the last tab
be_control_look->DrawInactiveTab(this, tabsBounds, tabsBounds, base, 0,
borders, fTabSide);
}
return fSelection < CountTabs() ? TabFrame(fSelection) : BRect();
}
void
BTabView::DrawBox(BRect selectedTabRect)
{
BRect rect(Bounds());
uint32 bordersToDraw = BControlLook::B_ALL_BORDERS;
switch (fTabSide) {
case kTopSide:
bordersToDraw &= ~BControlLook::B_TOP_BORDER;
rect.top = fTabHeight;
break;
case kBottomSide:
bordersToDraw &= ~BControlLook::B_BOTTOM_BORDER;
rect.bottom -= fTabHeight;
break;
case kLeftSide:
bordersToDraw &= ~BControlLook::B_LEFT_BORDER;
rect.left = fTabHeight;
break;
case kRightSide:
bordersToDraw &= ~BControlLook::B_RIGHT_BORDER;
rect.right -= fTabHeight;
break;
}
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
if (fBorderStyle == B_FANCY_BORDER)
be_control_look->DrawGroupFrame(this, rect, rect, base, bordersToDraw);
else if (fBorderStyle == B_PLAIN_BORDER) {
be_control_look->DrawBorder(this, rect, rect, base, B_PLAIN_BORDER,
0, bordersToDraw);
} else
; // B_NO_BORDER draws no box
}
BRect
BTabView::TabFrame(int32 index) const
{
if (index >= CountTabs() || index < 0)
return BRect();
const float padding = ceilf(be_control_look->DefaultLabelSpacing() * 3.3f);
const float height = fTabHeight;
const float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING);
const BRect bounds(Bounds());
float width = padding * 5.0f;
switch (fTabWidthSetting) {
case B_WIDTH_FROM_LABEL:
{
float x = 0.0f;
for (int32 i = 0; i < index; i++){
x += StringWidth(TabAt(i)->Label()) + padding;
}
switch (fTabSide) {
case kTopSide:
return BRect(offset + x, 0.0f,
offset + x + StringWidth(TabAt(index)->Label()) + padding,
height);
case kBottomSide:
return BRect(offset + x, bounds.bottom - height,
offset + x + StringWidth(TabAt(index)->Label()) + padding,
bounds.bottom);
case kLeftSide:
return BRect(0.0f, offset + x, height, offset + x
+ StringWidth(TabAt(index)->Label()) + padding);
case kRightSide:
return BRect(bounds.right - height, offset + x,
bounds.right, offset + x
+ StringWidth(TabAt(index)->Label()) + padding);
default:
return BRect();
}
}
case B_WIDTH_FROM_WIDEST:
width = 0.0;
for (int32 i = 0; i < CountTabs(); i++) {
float tabWidth = StringWidth(TabAt(i)->Label()) + padding;
if (tabWidth > width)
width = tabWidth;
}
// fall through
case B_WIDTH_AS_USUAL:
default:
switch (fTabSide) {
case kTopSide:
return BRect(offset + index * width, 0.0f,
offset + index * width + width, height);
case kBottomSide:
return BRect(offset + index * width, bounds.bottom - height,
offset + index * width + width, bounds.bottom);
case kLeftSide:
return BRect(0.0f, offset + index * width, height,
offset + index * width + width);
case kRightSide:
return BRect(bounds.right - height, offset + index * width,
bounds.right, offset + index * width + width);
default:
return BRect();
}
}
}
void
BTabView::SetFlags(uint32 flags)
{
BView::SetFlags(flags);
}
void
BTabView::SetResizingMode(uint32 mode)
{
BView::SetResizingMode(mode);
}
// #pragma mark -
void
BTabView::ResizeToPreferred()
{
BView::ResizeToPreferred();
}
void
BTabView::GetPreferredSize(float* _width, float* _height)
{
BView::GetPreferredSize(_width, _height);
}
BSize
BTabView::MinSize()
{
BSize size;
if (GetLayout())
size = GetLayout()->MinSize();
else {
size = _TabsMinSize();
BSize containerSize = fContainerView->MinSize();
containerSize.width += 2 * _BorderWidth();
containerSize.height += 2 * _BorderWidth();
if (containerSize.width > size.width)
size.width = containerSize.width;
size.height += containerSize.height;
}
return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
}
BSize
BTabView::MaxSize()
{
BSize size;
if (GetLayout())
size = GetLayout()->MaxSize();
else {
size = _TabsMinSize();
BSize containerSize = fContainerView->MaxSize();
containerSize.width += 2 * _BorderWidth();
containerSize.height += 2 * _BorderWidth();
if (containerSize.width > size.width)
size.width = containerSize.width;
size.height += containerSize.height;
}
return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
}
BSize
BTabView::PreferredSize()
{
BSize size;
if (GetLayout() != NULL)
size = GetLayout()->PreferredSize();
else {
size = _TabsMinSize();
BSize containerSize = fContainerView->PreferredSize();
containerSize.width += 2 * _BorderWidth();
containerSize.height += 2 * _BorderWidth();
if (containerSize.width > size.width)
size.width = containerSize.width;
size.height += containerSize.height;
}
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
}
void
BTabView::FrameMoved(BPoint newPosition)
{
BView::FrameMoved(newPosition);
}
void
BTabView::FrameResized(float newWidth, float newHeight)
{
BView::FrameResized(newWidth, newHeight);
}
// #pragma mark -
BHandler*
BTabView::ResolveSpecifier(BMessage* message, int32 index,
BMessage* specifier, int32 what, const char* property)
{
BPropertyInfo propInfo(sPropertyList);
if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
return this;
return BView::ResolveSpecifier(message, index, specifier, what, property);
}
status_t
BTabView::GetSupportedSuites(BMessage* message)
{
message->AddString("suites", "suite/vnd.Be-tab-view");
BPropertyInfo propInfo(sPropertyList);
message->AddFlat("messages", &propInfo);
return BView::GetSupportedSuites(message);
}
// #pragma mark -
void
BTabView::AddTab(BView* target, BTab* tab)
{
if (tab == NULL)
tab = new BTab(target);
else
tab->SetView(target);
if (fContainerView->GetLayout())
fContainerView->GetLayout()->AddView(CountTabs(), target);
fTabList->AddItem(tab);
BTab::Private(tab).SetTabView(this);
// When we haven't had a any tabs before, but are already attached to the
// window, select this one.
if (CountTabs() == 1 && Window() != NULL)
Select(0);
}
BTab*
BTabView::RemoveTab(int32 index)
{
if (index < 0 || index >= CountTabs())
return NULL;
BTab* tab = (BTab*)fTabList->RemoveItem(index);
if (tab == NULL)
return NULL;
tab->Deselect();
BTab::Private(tab).SetTabView(NULL);
if (fContainerView->GetLayout())
fContainerView->GetLayout()->RemoveItem(index);
if (CountTabs() == 0)
fFocus = -1;
else if (index <= fSelection)
Select(fSelection - 1);
if (fFocus >= 0) {
if (fFocus == CountTabs() - 1 || CountTabs() == 0)
SetFocusTab(fFocus, false);
else
SetFocusTab(fFocus, true);
}
return tab;
}
BTab*
BTabView::TabAt(int32 index) const
{
return (BTab*)fTabList->ItemAt(index);
}
void
BTabView::SetTabWidth(button_width width)
{
fTabWidthSetting = width;
Invalidate();
}
button_width
BTabView::TabWidth() const
{
return fTabWidthSetting;
}
void
BTabView::SetTabHeight(float height)
{
if (fTabHeight == height)
return;
fTabHeight = height;
_LayoutContainerView(GetLayout() != NULL);
Invalidate();
}
float
BTabView::TabHeight() const
{
return fTabHeight;
}
void
BTabView::SetBorder(border_style borderStyle)
{
if (fBorderStyle == borderStyle)
return;
fBorderStyle = borderStyle;
_LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0);
}
border_style
BTabView::Border() const
{
return fBorderStyle;
}
void
BTabView::SetTabSide(tab_side tabSide)
{
if (fTabSide == tabSide)
return;
fTabSide = tabSide;
_LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT);
}
BTabView::tab_side
BTabView::TabSide() const
{
return fTabSide;
}
BView*
BTabView::ContainerView() const
{
return fContainerView;
}
int32
BTabView::CountTabs() const
{
return fTabList->CountItems();
}
BView*
BTabView::ViewForTab(int32 tabIndex) const
{
BTab* tab = TabAt(tabIndex);
if (tab != NULL)
return tab->View();
return NULL;
}
int32
BTabView::IndexOf(BTab* tab) const
{
if (tab != NULL) {
int32 tabCount = CountTabs();
for (int32 index = 0; index < tabCount; index++) {
if (TabAt(index) == tab)
return index;
}
}
return -1;
}
void
BTabView::_InitObject(bool layouted, button_width width)
{
fTabList = new BList;
fTabWidthSetting = width;
fSelection = -1;
fFocus = -1;
fTabOffset = 0.0f;
fBorderStyle = B_FANCY_BORDER;
fTabSide = kTopSide;
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
font_height fh;
GetFontHeight(&fh);
fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading +
(be_control_look->DefaultLabelSpacing() * 1.3f));
fContainerView = NULL;
_InitContainerView(layouted);
}
void
BTabView::_InitContainerView(bool layouted)
{
bool needsLayout = false;
bool createdContainer = false;
if (layouted) {
if (GetLayout() == NULL) {
SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL));
needsLayout = true;
}
if (fContainerView == NULL) {
fContainerView = new BView("view container", B_WILL_DRAW);
fContainerView->SetLayout(new(std::nothrow) BCardLayout());
createdContainer = true;
}
} else if (fContainerView == NULL) {
fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL,
B_WILL_DRAW);
createdContainer = true;
}
if (needsLayout || createdContainer)
_LayoutContainerView(layouted);
if (createdContainer) {
fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
AddChild(fContainerView);
}
}
BSize
BTabView::_TabsMinSize() const
{
BSize size(0.0f, TabHeight());
int32 count = min_c(2, CountTabs());
for (int32 i = 0; i < count; i++) {
BRect frame = TabFrame(i);
size.width += frame.Width();
}
if (count < CountTabs()) {
// TODO: Add size for yet to be implemented buttons that allow
// "scrolling" the displayed tabs left/right.
}
return size;
}
float
BTabView::_BorderWidth() const
{
switch (fBorderStyle) {
default:
case B_FANCY_BORDER:
return 3.0f;
case B_PLAIN_BORDER:
return 1.0f;
case B_NO_BORDER:
return 0.0f;
}
}
void
BTabView::_LayoutContainerView(bool layouted)
{
float borderWidth = _BorderWidth();
if (layouted) {
float topBorderOffset;
switch (fBorderStyle) {
default:
case B_FANCY_BORDER:
topBorderOffset = 1.0f;
break;
case B_PLAIN_BORDER:
topBorderOffset = 0.0f;
break;
case B_NO_BORDER:
topBorderOffset = -1.0f;
break;
}
BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout());
if (layout != NULL) {
float inset = borderWidth + TabHeight() - topBorderOffset;
switch (fTabSide) {
case kTopSide:
layout->SetInsets(borderWidth, inset, borderWidth,
borderWidth);
break;
case kBottomSide:
layout->SetInsets(borderWidth, borderWidth, borderWidth,
inset);
break;
case kLeftSide:
layout->SetInsets(inset, borderWidth, borderWidth,
borderWidth);
break;
case kRightSide:
layout->SetInsets(borderWidth, borderWidth, inset,
borderWidth);
break;
}
}
} else {
BRect bounds = Bounds();
switch (fTabSide) {
case kTopSide:
bounds.top += TabHeight();
break;
case kBottomSide:
bounds.bottom -= TabHeight();
break;
case kLeftSide:
bounds.left += TabHeight();
break;
case kRightSide:
bounds.right -= TabHeight();
break;
}
bounds.InsetBy(borderWidth, borderWidth);
fContainerView->MoveTo(bounds.left, bounds.top);
fContainerView->ResizeTo(bounds.Width(), bounds.Height());
}
}
// #pragma mark - FBC and forbidden
void BTabView::_ReservedTabView3() {}
void BTabView::_ReservedTabView4() {}
void BTabView::_ReservedTabView5() {}
void BTabView::_ReservedTabView6() {}
void BTabView::_ReservedTabView7() {}
void BTabView::_ReservedTabView8() {}
void BTabView::_ReservedTabView9() {}
void BTabView::_ReservedTabView10() {}
void BTabView::_ReservedTabView11() {}
void BTabView::_ReservedTabView12() {}
BTabView::BTabView(const BTabView& tabView)
: BView(tabView)
{
// this is private and not functional, but exported
}
BTabView&
BTabView::operator=(const BTabView&)
{
// this is private and not functional, but exported
return *this;
}
// #pragma mark - binary compatibility
extern "C" void
B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)(
BTabView* tabView, border_style borderStyle)
{
tabView->BTabView::SetBorder(borderStyle);
}
extern "C" void
B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)(
BTabView* tabView, BTabView::tab_side tabSide)
{
tabView->BTabView::SetTabSide(tabSide);
}