root/src/kits/interface/TabView.cpp
/*
 * 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>
#include <private/libroot/libroot_private.h>


static property_info sPropertyList[] = {
        {
                "Selection",
                { B_GET_PROPERTY, B_SET_PROPERTY },
                { B_DIRECT_SPECIFIER },
                NULL, 0,
                { B_INT32_TYPE }
        },

        { 0 }
};


static bool
IsLayouted(BView* childView)
{
        BView* container = childView->Parent();
        if (container != NULL)
                return dynamic_cast<BCardLayout*>(container->GetLayout()) != NULL;
        return false;
}


BTab::BTab(BView* contentsView)
        :
        fEnabled(true),
        fSelected(false),
        fFocus(false),
        fView(contentsView)
{
        fTabView = NULL;
        if (fView != NULL)
                fLabel = fView->Name();
}


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
{
#ifdef __HAIKU_BEOS_COMPATIBLE
        if (__gABIVersion >= B_HAIKU_ABI_GCC_2_HAIKU)
                return fLabel;

        if (fView != NULL)
                return fView->Name();

        return NULL;
#else
        return fLabel;
#endif
}


void
BTab::SetLabel(const char* label)
{
        if (label == NULL || fView == NULL)
                return;

#ifdef __HAIKU_BEOS_COMPATIBLE
        if (__gABIVersion < B_HAIKU_ABI_GCC_2_HAIKU)
                fView->SetName(label);
#endif
        fLabel = 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.
                if (!IsLayouted(fView))
                        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;
        fLabel = fView->Name();

        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);

        rgb_color highColor = ui_color(B_PANEL_TEXT_COLOR);
        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), &highColor);
        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
        int32 tabCount = CountTabs();
        for (int32 i = 0; i < tabCount; i++) {
                BRect tabFrame = TabFrame(i);

                TabAt(i)->DrawTab(this, tabFrame,
                        i == fSelection ? B_TAB_FRONT
                                : (i == 0) ? B_TAB_FIRST : B_TAB_ANY,
                        i != fSelection - 1);
        }

        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);
}