root/src/apps/switcher/GroupListView.cpp
/*
 * Copyright 2011, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "GroupListView.h"

#include <map>

#include <Window.h>


class RendererLayoutItem : public BAbstractLayout {
public:
        RendererLayoutItem(BView* owner, int32 index, void* item,
                ListItemRenderer*& renderer)
                :
                fOwner(owner),
                fIndex(index),
                fItem(item),
                fRenderer(renderer)
        {
        }

        ListItemRenderer* Renderer() const
        {
                fRenderer->SetTo(fOwner, fItem);
                return fRenderer;
        }

        int32 Index() const
        {
                return fIndex;
        }

        void* Item() const
        {
                return fItem;
        }

        virtual BSize BaseMinSize()
        {
                fRenderer->SetTo(fOwner, fItem);
                return fRenderer->MinSize();
        }

        virtual BSize BasePreferredSize()
        {
                fRenderer->SetTo(fOwner, fItem);
                return fRenderer->PreferredSize();
        }

protected:
        virtual void DoLayout()
        {
                // shouldn't ever be called?
        }

private:
        BView*                          fOwner;
        int32                           fIndex;
        void*                           fItem;
        ListItemRenderer*&      fRenderer;
};


GroupListView::GroupListView(const char* name, GroupListModel* model,
        enum orientation orientation, float spacing)
        :
        BView(NULL, B_WILL_DRAW, new BGroupLayout(orientation, spacing)),
        fModel(NULL),
        fItemRenderer(NULL),
        fGroupRenderer(NULL),
        fSelectionMessage(NULL)
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        SetModel(model);
}


GroupListView::~GroupListView()
{
        delete fModel;
        delete fItemRenderer;
        delete fGroupRenderer;
        delete fSelectionMessage;
}


void
GroupListView::SetModel(GroupListModel* model)
{
        // TODO: remove all previous
        // TODO: add change mechanism
        // TODO: use a "virtual" BGroupLayout (ie. one that create its layout items
        //              on the fly).
        fModel = model;

        std::map<addr_t, BGroupLayout*> groupMap;

        int32 groupCount = model->CountGroups();
        for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
                BGroupLayout* groupItem = new BGroupLayout(B_VERTICAL, 0);
                groupItem->SetVisible(false);
                AddChild(groupItem);

                addr_t group = model->GroupAt(groupIndex);
                groupMap[group] = groupItem;
                groupItem->AddItem(new RendererLayoutItem(this, groupIndex, (void*)group,
                        fGroupRenderer));
        }

        int32 itemCount = model->CountItems();
        for (int itemIndex = 0; itemIndex < itemCount; itemIndex++) {
                addr_t group = model->GroupForItemAt(itemIndex);
                if (group == 0)
                        continue;

                BGroupLayout* groupItem = groupMap[group];
                if (groupItem == NULL)
                        continue;

                groupItem->SetVisible(true);

                RendererLayoutItem* rendererItem = new RendererLayoutItem(this,
                        itemIndex, model->ItemAt(itemIndex), fItemRenderer);
                groupItem->AddItem(rendererItem);
        }
}


void
GroupListView::SetItemRenderer(ListItemRenderer* renderer)
{
        fItemRenderer = renderer;
        InvalidateLayout();
}


void
GroupListView::SetGroupRenderer(ListItemRenderer* renderer)
{
        fGroupRenderer = renderer;
        InvalidateLayout();
}


void
GroupListView::SetSelectionMessage(BMessage* message, BMessenger target)
{
        fSelectionMessage = message;
        fSelectionTarget = target;
}


void
GroupListView::AttachedToWindow()
{
}


void
GroupListView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                default:
                        BView::MessageReceived(message);
                        break;
        }
}


void
GroupListView::MouseDown(BPoint point)
{
        if (fSelectionMessage == NULL)
                return;

        BLayoutItem* item = _ItemAt(GetLayout(), point);

        if (RendererLayoutItem* rendererItem
                        = dynamic_cast<RendererLayoutItem*>(item)) {
                BMessage message(*fSelectionMessage);

                int32 buttons = 0;
                if (Window()->CurrentMessage() != NULL)
                        buttons = Window()->CurrentMessage()->FindInt32("buttons");
                if (buttons != 0)
                        message.AddInt32("buttons", buttons);
                message.AddInt32("index", rendererItem->Index());
                message.AddPointer(rendererItem->Renderer() == fGroupRenderer
                        ? "group" : "item", rendererItem->Item());

                fSelectionTarget.SendMessage(&message);
        }
}


void
GroupListView::Draw(BRect updateRect)
{
        _Draw(GetLayout(), updateRect);
}


void
GroupListView::_Draw(BLayoutItem* item, BRect updateRect)
{
        if (RendererLayoutItem* rendererItem
                        = dynamic_cast<RendererLayoutItem*>(item)) {
                ListItemRenderer* renderer = rendererItem->Renderer();
                renderer->Draw(this, rendererItem->Frame(), rendererItem->Index(),
                        false);
        } else if (BLayout* layout = dynamic_cast<BLayout*>(item)) {
                for (int i = 0; i < layout->CountItems(); i++) {
                        item = layout->ItemAt(i);
                        if (!item->IsVisible() || !item->Frame().Intersects(updateRect))
                                continue;

                        _Draw(item, updateRect);
                }
        }
}


BLayoutItem*
GroupListView::_ItemAt(BLayoutItem* item, BPoint point)
{
        if (RendererLayoutItem* rendererItem
                        = dynamic_cast<RendererLayoutItem*>(item))
                return rendererItem;

        if (BLayout* layout = dynamic_cast<BLayout*>(item)) {
                for (int i = 0; i < layout->CountItems(); i++) {
                        item = layout->ItemAt(i);
                        if (!item->IsVisible() || !item->Frame().Contains(point))
                                continue;

                        return _ItemAt(item, point);
                }
        }

        return item;
}