root/src/apps/debuganalyzer/gui/HeaderView.cpp
/*
 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2009, Stephan Aßmus, superstippi@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "HeaderView.h"

#include <stdio.h>

#include <algorithm>
#include <new>

#include <ControlLook.h>
#include <LayoutUtils.h>
#include <Looper.h>


// #pragma mark - HeaderRenderer


HeaderRenderer::~HeaderRenderer()
{
}


void
HeaderRenderer::DrawHeaderBackground(BView* view, BRect frame, BRect updateRect,
        uint32 flags)
{
        BRect bgRect = frame;

        rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
        view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
        view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());

        bgRect.bottom--;
        bgRect.right--;

//      if ((flags & CLICKED) != 0)
//              base = tint_color(base, B_DARKEN_1_TINT);

        be_control_look->DrawButtonBackground(view, bgRect, updateRect, base, 0,
                BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);

        view->StrokeLine(frame.RightTop(), frame.RightBottom());
}


// #pragma mark - DefaultHeaderRenderer


DefaultHeaderRenderer::DefaultHeaderRenderer()
{
}


DefaultHeaderRenderer::~DefaultHeaderRenderer()
{
}


float
DefaultHeaderRenderer::HeaderHeight(BView* view, const Header* header)
{
        BVariant value;
        if (!header->GetValue(value))
                return 0;

        if (value.Type() == B_STRING_TYPE) {
                font_height fontHeight;
                view->GetFontHeight(&fontHeight);
                return ceilf((fontHeight.ascent + fontHeight.descent) * 1.2f) + 2.0f;
        } else {
                // TODO: Support more value types.
                return 0;
        }
}


float
DefaultHeaderRenderer::PreferredHeaderWidth(BView* view, const Header* header)
{
        BVariant value;
        if (!header->GetValue(value))
                return 0;

        if (value.Type() == B_STRING_TYPE) {
                float width = view->StringWidth(value.ToString());
                return width + be_control_look->DefaultLabelSpacing() * 2.0f;
        } else {
                // TODO: Support more value types.
                return 0;
        }
}


void
DefaultHeaderRenderer::DrawHeader(BView* view, BRect frame, BRect updateRect,
        const Header* header, uint32 flags)
{
        DrawHeaderBackground(view, frame, updateRect, flags);

        BVariant value;
        if (!header->GetValue(value))
                return;

        frame.InsetBy(be_control_look->DefaultLabelSpacing(), 0);

        if (value.Type() == B_STRING_TYPE) {
                rgb_color highColor = view->HighColor();
                be_control_look->DrawLabel(view, value.ToString(), frame, updateRect,
                        view->LowColor(), 0, &highColor);
        }
}


// #pragma mark - HeaderListener


HeaderListener::~HeaderListener()
{
}


void
HeaderListener::HeaderWidthChanged(Header* header)
{
}


void
HeaderListener::HeaderWidthRestrictionsChanged(Header* header)
{
}


void
HeaderListener::HeaderValueChanged(Header* header)
{
}


void
HeaderListener::HeaderRendererChanged(Header* header)
{
}


// #pragma mark - Header


Header::Header(int32 modelIndex)
        :
        fWidth(100),
        fMinWidth(0),
        fMaxWidth(10000),
        fPreferredWidth(100),
        fValue(),
        fRenderer(NULL),
        fModelIndex(modelIndex),
        fResizable(true)
{
}


Header::Header(float width, float minWidth, float maxWidth,
        float preferredWidth, int32 modelIndex)
        :
        fWidth(width),
        fMinWidth(minWidth),
        fMaxWidth(maxWidth),
        fPreferredWidth(preferredWidth),
        fValue(),
        fRenderer(NULL),
        fModelIndex(modelIndex),
        fResizable(true)
{
}


float
Header::Width() const
{
        return fWidth;
}


float
Header::MinWidth() const
{
        return fMinWidth;
}


float
Header::MaxWidth() const
{
        return fMaxWidth;
}


float
Header::PreferredWidth() const
{
        return fPreferredWidth;
}


void
Header::SetWidth(float width)
{
        if (width != fWidth) {
                fWidth = width;
                NotifyWidthChanged();
        }
}


void
Header::SetMinWidth(float width)
{
        if (width != fMinWidth) {
                fMinWidth = width;
                NotifyWidthRestrictionsChanged();
        }
}


void
Header::SetMaxWidth(float width)
{
        if (width != fMaxWidth) {
                fMaxWidth = width;
                NotifyWidthRestrictionsChanged();
        }
}


void
Header::SetPreferredWidth(float width)
{
        if (width != fPreferredWidth) {
                fPreferredWidth = width;
                NotifyWidthRestrictionsChanged();
        }
}


bool
Header::IsResizable() const
{
        return fResizable;
}


void
Header::SetResizable(bool resizable)
{
        if (resizable != fResizable) {
                fResizable = resizable;
                NotifyWidthRestrictionsChanged();
        }
}


bool
Header::GetValue(BVariant& _value) const
{
        _value = fValue;
        return true;
}


void
Header::SetValue(const BVariant& value)
{
        fValue = value;
        NotifyValueChanged();
}


int32
Header::ModelIndex() const
{
        return fModelIndex;
}


void
Header::SetModelIndex(int32 index)
{
        fModelIndex = index;
}


HeaderRenderer*
Header::GetHeaderRenderer() const
{
        return fRenderer;
}


void
Header::SetHeaderRenderer(HeaderRenderer* renderer)
{
        if (renderer != fRenderer) {
                fRenderer = renderer;
                NotifyRendererChanged();
        }
}


void
Header::AddListener(HeaderListener* listener)
{
        fListeners.AddItem(listener);
}


void
Header::RemoveListener(HeaderListener* listener)
{
        fListeners.RemoveItem(listener);
}


void
Header::NotifyWidthChanged()
{
        for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
                HeaderListener* listener = fListeners.ItemAt(i);
                listener->HeaderWidthChanged(this);
        }
}


void
Header::NotifyWidthRestrictionsChanged()
{
        for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
                HeaderListener* listener = fListeners.ItemAt(i);
                listener->HeaderWidthRestrictionsChanged(this);
        }
}


void
Header::NotifyValueChanged()
{
        for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
                HeaderListener* listener = fListeners.ItemAt(i);
                listener->HeaderValueChanged(this);
        }
}


void
Header::NotifyRendererChanged()
{
        for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
                HeaderListener* listener = fListeners.ItemAt(i);
                listener->HeaderRendererChanged(this);
        }
}


// #pragma mark - HeaderModelListener


HeaderModelListener::~HeaderModelListener()
{
}


void
HeaderModelListener::HeaderAdded(HeaderModel* model, int32 index)
{
}


void
HeaderModelListener::HeaderRemoved(HeaderModel* model, int32 index)
{
}


void
HeaderModelListener::HeaderMoved(HeaderModel* model, int32 fromIndex,
        int32 toIndex)
{
}


// #pragma mark - HeaderModel


HeaderModel::HeaderModel()
{
}


HeaderModel::~HeaderModel()
{
}


int32
HeaderModel::CountHeaders() const
{
        return fHeaders.CountItems();
}


Header*
HeaderModel::HeaderAt(int32 index) const
{
        return fHeaders.ItemAt(index);
}


int32
HeaderModel::IndexOfHeader(Header* header) const
{
        return fHeaders.IndexOf(header);
}


bool
HeaderModel::AddHeader(Header* header)
{
        if (!fHeaders.AddItem(header))
                return false;

        NotifyHeaderAdded(fHeaders.CountItems() - 1);

        return true;
}


Header*
HeaderModel::RemoveHeader(int32 index)
{
        Header* header = fHeaders.RemoveItemAt(index);
        if (header != NULL)
                return NULL;

        NotifyHeaderRemoved(index);

        return header;
}


void
HeaderModel::RemoveHeader(Header* header)
{
        RemoveHeader(fHeaders.IndexOf(header));
}


bool
HeaderModel::MoveHeader(int32 fromIndex, int32 toIndex)
{
        int32 headerCount = fHeaders.CountItems();
        if (fromIndex < 0 || fromIndex >= headerCount
                || toIndex < 0 || toIndex >= headerCount) {
                return false;
        }

        if (fromIndex == toIndex)
                return true;

        Header* header = fHeaders.RemoveItemAt(fromIndex);
        fHeaders.AddItem(header, toIndex);
                // TODO: Might fail.

        NotifyHeaderMoved(fromIndex, toIndex);

        return true;
}


void
HeaderModel::AddListener(HeaderModelListener* listener)
{
        fListeners.AddItem(listener);
}


void
HeaderModel::RemoveListener(HeaderModelListener* listener)
{
        fListeners.RemoveItem(listener);
}


void
HeaderModel::NotifyHeaderAdded(int32 index)
{
        for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
                HeaderModelListener* listener = fListeners.ItemAt(i);
                listener->HeaderAdded(this, index);
        }
}


void
HeaderModel::NotifyHeaderRemoved(int32 index)
{
        for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
                HeaderModelListener* listener = fListeners.ItemAt(i);
                listener->HeaderRemoved(this, index);
        }
}


void
HeaderModel::NotifyHeaderMoved(int32 fromIndex, int32 toIndex)
{
        for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
                HeaderModelListener* listener = fListeners.ItemAt(i);
                listener->HeaderMoved(this, fromIndex, toIndex);
        }
}


// #pragma mark - HeaderEntry


struct HeaderView::HeaderEntry {
        Header* header;
        float   position;
        float   width;

        HeaderEntry(Header* header)
                :
                header(header)
        {
        }
};


// #pragma mark - States


class HeaderView::State {
public:
        State(HeaderView* parent)
                :
                fParent(parent)
        {
        }

        virtual ~State()
        {
        }

        virtual void Entering(State* previousState)
        {
        }

        virtual void Leaving(State* nextState)
        {
        }

        virtual void MouseDown(BPoint where, uint32 buttons, uint32 modifiers)
        {
        }

        virtual void MouseUp(BPoint where, uint32 buttons, uint32 modifiers)
        {
        }

        virtual void MouseMoved(BPoint where, uint32 transit,
                const BMessage* dragMessage)
        {
        }

protected:
        HeaderView*     fParent;
};


class HeaderView::DefaultState : public State {
public:
                                                                DefaultState(HeaderView* parent);

        virtual void                            MouseDown(BPoint where, uint32 buttons,
                                                                        uint32 modifiers);
        virtual void                            MouseMoved(BPoint where, uint32 transit,
                                                                        const BMessage* dragMessage);
};


class HeaderView::ResizeState : public State {
public:
        virtual void                            Entering(State* previousState);
        virtual void                            Leaving(State* nextState);

                                                                ResizeState(HeaderView* parent,
                                                                        int32 headerIndex, BPoint startPoint);

        virtual void                            MouseUp(BPoint where, uint32 buttons,
                                                                        uint32 modifiers);
        virtual void                            MouseMoved(BPoint where, uint32 transit,
                                                                        const BMessage* dragMessage);

private:
                        Header*                         fHeader;
                        float                           fStartX;
                        float                           fStartWidth;
};


// #pragma mark - DefaultState


HeaderView::DefaultState::DefaultState(HeaderView* parent)
        :
        State(parent)
{
}


void
HeaderView::DefaultState::MouseDown(BPoint where, uint32 buttons,
        uint32 modifiers)
{
        HeaderModel* model = fParent->Model();
        if (model == NULL)
                return;

        if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
                return;

        int32 headerIndex = fParent->HeaderIndexAt(where);
        if (headerIndex < 0) {
                int32 headerCount = model->CountHeaders();
                if (headerCount == 0)
                        return;

                headerIndex = headerCount - 1;
        }

        // Check whether the mouse is close to the left or the right side of the
        // header.
        BRect headerFrame = fParent->HeaderFrame(headerIndex);
        if (fabs(headerFrame.left - where.x) <= 3) {
                if (headerIndex == 0)
                        return;
                headerIndex--;
        } else if (fabs(headerFrame.right + 1 - where.x) > 3)
                return;

        // start resizing the header
        fParent->_SwitchState(new ResizeState(fParent, headerIndex, where));
}


void
HeaderView::DefaultState::MouseMoved(BPoint where, uint32 transit,
        const BMessage* dragMessage)
{
}


// #pragma mark - ResizeState


void
HeaderView::ResizeState::Entering(State* previousState)
{
        fParent->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
}


void
HeaderView::ResizeState::Leaving(State* nextState)
{
        fParent->SetEventMask(0, 0);
}


HeaderView::ResizeState::ResizeState(HeaderView* parent, int32 headerIndex,
        BPoint startPoint)
        :
        State(parent),
        fHeader(parent->Model()->HeaderAt(headerIndex)),
        fStartX(startPoint.x),
        fStartWidth(fHeader->Width())
{
}


void
HeaderView::ResizeState::MouseUp(BPoint where, uint32 buttons,
        uint32 modifiers)
{
        if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
                fParent->_SwitchState(NULL);
}


void
HeaderView::ResizeState::MouseMoved(BPoint where, uint32 transit,
        const BMessage* dragMessage)
{
        float width = fStartWidth + where.x - fStartX;
        width = std::max(width, fHeader->MinWidth());
        width = std::min(width, fHeader->MaxWidth());
        fHeader->SetWidth(width);
}


// #pragma mark - HeaderView


HeaderView::HeaderView()
        :
        BView("header view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
        fModel(NULL),
        fHeaderEntries(10),
        fLayoutValid(false),
        fDefaultState(new DefaultState(this)),
        fState(fDefaultState)
{
        HeaderModel* model = new(std::nothrow) HeaderModel;
        BReference<HeaderModel> modelReference(model, true);
        SetModel(model);

        SetViewColor(B_TRANSPARENT_32_BIT);
}


HeaderView::~HeaderView()
{
        SetModel(NULL);

        _SwitchState(NULL);
        delete fDefaultState;
}


void
HeaderView::Draw(BRect updateRect)
{
        if (fModel == NULL)
                return;

        _ValidateHeadersLayout();

        DefaultHeaderRenderer defaultRenderer;
        float bottom = Bounds().Height();
        float left = 0.0f;

        for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++) {
                if (Header* header = fModel->HeaderAt(i)) {
                        HeaderRenderer* renderer = header->GetHeaderRenderer();
                        if (renderer == NULL)
                                renderer = &defaultRenderer;

                        BRect frame(entry->position, 0, entry->position + entry->width - 1,
                                bottom);
                        renderer->DrawHeader(this, frame, updateRect, header, 0);
                                // TODO: flags!

                        left = entry->position + entry->width;
                }
        }

        // TODO: We are not using any custom renderer here.
        defaultRenderer.DrawHeaderBackground(this,
                BRect(left, 0, Bounds().right + 1, bottom), updateRect, 0);
}


BSize
HeaderView::MinSize()
{
        _ValidateHeadersLayout();

        return BLayoutUtils::ComposeSize(ExplicitMinSize(),
                BSize(100, fPreferredHeight - 1));
}


BSize
HeaderView::MaxSize()
{
        _ValidateHeadersLayout();

        return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
                BSize(B_SIZE_UNLIMITED, fPreferredHeight - 1));
}


BSize
HeaderView::PreferredSize()
{
        _ValidateHeadersLayout();

        return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
                BSize(fPreferredWidth - 1, fPreferredHeight - 1));
}


HeaderModel*
HeaderView::Model() const
{
        return fModel;
}


void
HeaderView::MouseDown(BPoint where)
{
        // get buttons and modifiers
        BMessage* message = Looper()->CurrentMessage();
        int32 buttons;
        if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK)
                buttons = 0;
        int32 modifiers;
        if (message == NULL || message->FindInt32("modifiers", &modifiers) != B_OK)
                modifiers = 0;

        fState->MouseDown(where, buttons, modifiers);
}


void
HeaderView::MouseUp(BPoint where)
{
        // get buttons and modifiers
        BMessage* message = Looper()->CurrentMessage();
        int32 buttons;
        if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK)
                buttons = 0;
        int32 modifiers;
        if (message == NULL || message->FindInt32("modifiers", &modifiers) != B_OK)
                modifiers = 0;

        fState->MouseUp(where, buttons, modifiers);
}


void
HeaderView::MouseMoved(BPoint where, uint32 transit,
        const BMessage* dragMessage)
{
        fState->MouseMoved(where, transit, dragMessage);
}


status_t
HeaderView::SetModel(HeaderModel* model)
{
        if (model == fModel)
                return B_OK;

        _SwitchState(NULL);

        if (fModel != NULL) {
                // remove all headers
                for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++)
                        entry->header->RemoveListener(this);
                fHeaderEntries.MakeEmpty();

                fModel->RemoveListener(this);
                fModel->ReleaseReference();
        }

        fModel = model;

        if (fModel != NULL) {
                fModel->AcquireReference();
                fModel->AddListener(this);

                // create header entries
                int32 headerCount = fModel->CountHeaders();
                for (int32 i = 0; i < headerCount; i++) {
                        HeaderEntry* entry = new(std::nothrow) HeaderEntry(
                                fModel->HeaderAt(i));
                        if (entry == NULL || !fHeaderEntries.AddItem(entry)) {
                                delete entry;
                                return B_NO_MEMORY;
                        }

                        entry->header->AddListener(this);
                }
        }

        _InvalidateHeadersLayout(0);
        Invalidate();

        return B_OK;
}


BRect
HeaderView::HeaderFrame(int32 index) const
{
        float bottom = Bounds().Height();

        if (HeaderEntry* entry = fHeaderEntries.ItemAt(index)) {
                return BRect(entry->position, 0, entry->position + entry->width - 1,
                        bottom);
        }

        return BRect();
}


int32
HeaderView::HeaderIndexAt(BPoint point) const
{
        float x = point.x;

        for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++) {
                if (x >= entry->position && x < entry->position + entry->width)
                        return i;
        }

        return -1;
}


void
HeaderView::AddListener(HeaderViewListener* listener)
{
        fListeners.AddItem(listener);
}


void
HeaderView::RemoveListener(HeaderViewListener* listener)
{
        fListeners.RemoveItem(listener);
}


void
HeaderView::HeaderAdded(HeaderModel* model, int32 index)
{
        if (Header* header = fModel->HeaderAt(index)) {
                HeaderEntry* entry = new(std::nothrow) HeaderEntry(
                        fModel->HeaderAt(index));
                if (entry == NULL || !fHeaderEntries.AddItem(entry)) {
                        delete entry;
                        return;
                }

                header->AddListener(this);
                _InvalidateHeadersLayout(index);
        }
}


void
HeaderView::HeaderRemoved(HeaderModel* model, int32 index)
{
        if (HeaderEntry* entry = fHeaderEntries.RemoveItemAt(index)) {
                entry->header->RemoveListener(this);
                _InvalidateHeadersLayout(index);
        }
}


void
HeaderView::HeaderMoved(HeaderModel* model, int32 fromIndex, int32 toIndex)
{
        _InvalidateHeadersLayout(std::min(fromIndex, toIndex));
}


void
HeaderView::HeaderWidthChanged(Header* header)
{
        _HeaderPropertiesChanged(header, true, true);
}


void
HeaderView::HeaderWidthRestrictionsChanged(Header* header)
{
        // TODO:...
}


void
HeaderView::HeaderValueChanged(Header* header)
{
        _HeaderPropertiesChanged(header, true, false);
}


void
HeaderView::HeaderRendererChanged(Header* header)
{
        _HeaderPropertiesChanged(header, true, true);
}


void
HeaderView::_HeaderPropertiesChanged(Header* header, bool redrawNeeded,
        bool relayoutNeeded)
{
        if (!redrawNeeded && !relayoutNeeded)
                return;

        int32 index = fModel->IndexOfHeader(header);

        if (relayoutNeeded)
                _InvalidateHeadersLayout(index);
        else if (redrawNeeded)
                _InvalidateHeaders(index, index + 1);
}


void
HeaderView::_InvalidateHeadersLayout(int32 firstIndex)
{
        if (!fLayoutValid)
                return;

        fLayoutValid = false;
        InvalidateLayout();
        Invalidate();
}


void
HeaderView::_InvalidateHeaders(int32 firstIndex, int32 endIndex)
{
        Invalidate();
                // TODO: Be less lazy!
}


void
HeaderView::_ValidateHeadersLayout()
{
        if (fLayoutValid)
                return;

        DefaultHeaderRenderer defaultRenderer;

        int32 headerCount = fHeaderEntries.CountItems();
        float position = 0;
        fPreferredWidth = 0;
        fPreferredHeight = 0;

        for (int32 i = 0; i < headerCount; i++) {
                HeaderEntry* entry = fHeaderEntries.ItemAt(i);
                entry->position = position;
                if (Header* header = fModel->HeaderAt(i)) {
                        entry->width = header->Width();
                        fPreferredWidth += header->PreferredWidth();
                } else
                        entry->width = 0;

                position = entry->position + entry->width;

                if (Header* header = fModel->HeaderAt(i)) {
                        HeaderRenderer* renderer = header->GetHeaderRenderer();
                        if (renderer == NULL)
                                renderer = &defaultRenderer;

                        float height = renderer->HeaderHeight(this, header);
                        if (height > fPreferredHeight)
                                fPreferredHeight = height;
                }
        }

        fLayoutValid = true;
}


void
HeaderView::_SwitchState(State* newState)
{
        if (newState == NULL)
                newState = fDefaultState;

        fState->Leaving(newState);
        newState->Entering(fState);

        if (fState != fDefaultState)
                delete fState;

        fState = newState;
}


// #pragma mark - HeaderViewListener


HeaderViewListener::~HeaderViewListener()
{
}