root/src/apps/terminal/SmartTabView.cpp
/*
 * Copyright 2007-2010, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 *      Authors:
 *              Stefano Ceccherini (burton666@libero.it)
 *              Ingo Weinhold (ingo_weinhold@gmx.de)
 */


/*!     The SmartTabView class is a BTabView descendant that hides the tab bar
        as long as there is only a single tab.
        Furthermore, it provides a tab context menu, as well as allowing you to
        close buttons with the middle mouse button.
*/


#include "SmartTabView.h"

#include <stdio.h>

#include <BitmapButton.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Locale.h>
#include <Message.h>
#include <Messenger.h>
#include <Screen.h>
#include <ScrollView.h>
#include <Window.h>

#include "TermConst.h"
#include "WindowIcon.h"


// #pragma mark - SmartTabView


SmartTabView::SmartTabView(BRect frame, const char* name, button_width width,
                uint32 resizingMode, uint32 flags)
        :
        BTabView(frame, name, width, resizingMode, flags),
        fInsets(0, 0, 0, 0),
        fScrollView(NULL),
        fListener(NULL)
{
        // Resize the container view to fill the complete tab view for single-tab
        // mode. Later, when more than one tab is added, we shrink the container
        // view again.
        frame.OffsetTo(B_ORIGIN);
        ContainerView()->MoveTo(frame.LeftTop());
        ContainerView()->ResizeTo(frame.Width(), frame.Height());

        BRect buttonRect(frame);
        buttonRect.left = frame.right - be_control_look->GetScrollBarWidth(B_VERTICAL) + 1;
        buttonRect.bottom = frame.top + TabHeight() - 1;
        fFullScreenButton = new BBitmapButton(kWindowIconBits, kWindowIconWidth,
                kWindowIconHeight, kWindowIconFormat, new BMessage(FULLSCREEN));
        fFullScreenButton->SetResizingMode(B_FOLLOW_TOP | B_FOLLOW_RIGHT);
        fFullScreenButton->MoveTo(buttonRect.LeftTop());
        fFullScreenButton->ResizeTo(buttonRect.Width(), buttonRect.Height());
        fFullScreenButton->Hide();

        AddChild(fFullScreenButton);
}


SmartTabView::~SmartTabView()
{
}


void
SmartTabView::SetInsets(float left, float top, float right, float bottom)
{
        fInsets.left = left;
        fInsets.top = top;
        fInsets.right = right;
        fInsets.bottom = bottom;
}


void
SmartTabView::MouseDown(BPoint point)
{
        bool handled = false;

        if (CountTabs() > 1) {
                int32 tabIndex = _ClickedTabIndex(point);
                int32 buttons = 0;
                int32 clickCount = 0;
                Window()->CurrentMessage()->FindInt32("buttons", &buttons);
                Window()->CurrentMessage()->FindInt32("clicks", &clickCount);

                if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0 && clickCount == 2) {
                        if (fListener != NULL)
                                fListener->TabDoubleClicked(this, point, tabIndex);
                } else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
                        if (fListener != NULL)
                                fListener->TabRightClicked(this, point, tabIndex);
                        handled = true;
                } else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
                        if (fListener != NULL)
                                fListener->TabMiddleClicked(this, point, tabIndex);
                        handled = true;
                }
        }

        if (!handled)
                BTabView::MouseDown(point);
}


void
SmartTabView::AttachedToWindow()
{
        BTabView::AttachedToWindow();
}


void
SmartTabView::AllAttached()
{
        BTabView::AllAttached();
}


void
SmartTabView::Select(int32 index)
{
        BTabView::Select(index);
        BView *view = ViewForTab(index);
        if (view != NULL) {
                view->MoveTo(fInsets.LeftTop());
                view->ResizeTo(ContainerView()->Bounds().Width()
                                - fInsets.left - fInsets.right,
                        ContainerView()->Bounds().Height() - fInsets.top - fInsets.bottom);
        }

        if (fListener != NULL)
                fListener->TabSelected(this, index);
}


void
SmartTabView::AddTab(BView* target, BTab* tab)
{
        if (target == NULL)
                return;

        BTabView::AddTab(target, tab);

        if (CountTabs() == 1) {
                // Call select on the tab, since
                // we're resizing the contained view
                // inside that function
                Select(0);
        } else if (CountTabs() == 2) {
                // Need to resize the view, since we're switching from "normal"
                // to tabbed mode
                ContainerView()->ResizeBy(0, -TabHeight());
                ContainerView()->MoveBy(0, TabHeight());
                fFullScreenButton->Show();

                // Make sure the content size stays the same, but take special care
                // of full screen mode
                BScreen screen(Window());
                if (Window()->DecoratorFrame().Height() + 2 * TabHeight()
                                < screen.Frame().Height()) {
                        if (Window()->Frame().bottom + TabHeight()
                                > screen.Frame().bottom - 5) {
                                Window()->MoveBy(0, -TabHeight());
                        }

                        Window()->ResizeBy(0, TabHeight());
                }

                // Adapt scroll bar if there is one
                if (fScrollView != NULL) {
                        BScrollBar* bar = fScrollView->ScrollBar(B_VERTICAL);
                        if (bar != NULL) {
                                bar->ResizeBy(0, -1);
                                bar->MoveBy(0, 1);
                        }
                }

                SetBorder(B_NO_BORDER);
        }

        Invalidate(TabFrame(CountTabs() - 1).InsetByCopy(-2, -2));
}


BTab*
SmartTabView::RemoveTab(int32 index)
{
        if (CountTabs() == 2) {
                // Hide the tab bar again by resizing the container view

                // Make sure the content size stays the same, but take special care
                // of full screen mode
                BScreen screen(Window());
                if (Window()->DecoratorFrame().Height() + 2 * TabHeight()
                                < screen.Frame().Height()) {
                        if (Window()->Frame().bottom
                                > screen.Frame().bottom - 5 - TabHeight()) {
                                Window()->MoveBy(0, TabHeight());
                        }
                        Window()->ResizeBy(0, -TabHeight());
                }

                // Adapt scroll bar if there is one
                if (fScrollView != NULL) {
                        BScrollBar* bar = fScrollView->ScrollBar(B_VERTICAL);
                        if (bar != NULL) {
                                bar->ResizeBy(0, 1);
                                bar->MoveBy(0, -1);
                        }
                }

                ContainerView()->MoveBy(0, -TabHeight());
                ContainerView()->ResizeBy(0, TabHeight());
                fFullScreenButton->Hide();
        }

        return BTabView::RemoveTab(index);
}


void
SmartTabView::MoveTab(int32 index, int32 newIndex)
{
        // check the indexes
        int32 count = CountTabs();
        if (index == newIndex || index < 0 || newIndex < 0 || index >= count
                || newIndex >= count) {
                return;
        }

        // Remove the tab from its position and add it to the end. Then cycle the
        // tabs starting after the new position (save the tab already moved) to the
        // end.
        BTab* tab = BTabView::RemoveTab(index);
        BTabView::AddTab(tab->View(), tab);

        for (int32 i = newIndex; i < count - 1; i++) {
                tab = BTabView::RemoveTab(newIndex);
                BTabView::AddTab(tab->View(), tab);
        }
}


BRect
SmartTabView::DrawTabs()
{
        if (CountTabs() > 1)
                return BTabView::DrawTabs();

        return BRect();
}


/*!     If you have a vertical scroll view that overlaps with the menu bar, it will
        be resized automatically when the tabs are hidden/shown.
*/
void
SmartTabView::SetScrollView(BScrollView* scrollView)
{
        fScrollView = scrollView;
}


int32
SmartTabView::_ClickedTabIndex(const BPoint& point)
{
        if (point.y <= TabHeight()) {
                for (int32 i = 0; i < CountTabs(); i++) {
                        if (TabFrame(i).Contains(point))
                                return i;
                }
        }

        return -1;
}


// #pragma mark - Listener


SmartTabView::Listener::~Listener()
{
}


void
SmartTabView::Listener::TabSelected(SmartTabView* tabView, int32 index)
{
}


void
SmartTabView::Listener::TabDoubleClicked(SmartTabView* tabView, BPoint point,
        int32 index)
{
}


void
SmartTabView::Listener::TabMiddleClicked(SmartTabView* tabView, BPoint point,
        int32 index)
{
}


void
SmartTabView::Listener::TabRightClicked(SmartTabView* tabView, BPoint point,
        int32 index)
{
}