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


#include "WindowsView.h"

#include <stdlib.h>

#include <LayoutBuilder.h>
#include <ObjectList.h>
#include <StringView.h>

#include <MessengerPrivate.h>
#include <WindowInfo.h>
#include <WindowPrivate.h>

#include "GroupListView.h"
#include "LaunchButton.h"
#include "Switcher.h"


static const uint32 kMsgActivateWindow = 'AcWn';


class WindowModel : public GroupListModel {
public:
        WindowModel(team_id team)
                :
                fWindows(true),
                fWorkspaces(0),
                fWorkspaceCount(0)
        {
                // TODO: more than one team via signature!
                int32 count;
                int32* tokens = get_token_list(team, &count);

                for (int32 i = 0; i < count; i++) {
                        client_window_info* info = get_window_info(tokens[i]);
                        if (!_WindowShouldBeListed(info)) {
                                free(info);
                                continue;
                        }

                        fWorkspaces |= info->workspaces;
                        fWindows.AddItem(info);
                }
                free(tokens);

                for (uint32 i = 0; i < 32; i++) {
                        if ((fWorkspaces & (1UL << i)) != 0)
                                fWorkspaceCount++;
                }

                fWindows.SortItems(&_CompareWindowInfo);
        }

        virtual ~WindowModel()
        {
        }

        void BringToFront(int32 index)
        {
                client_window_info* info = fWindows.ItemAt(index);
                if (info == NULL)
                        return;

                do_window_action(info->server_token, B_BRING_TO_FRONT, BRect(), false);
        }

        void Close(int32 index)
        {
                client_window_info* info = fWindows.ItemAt(index);
                if (info == NULL)
                        return;

                BMessenger window;
                BMessenger::Private(window).SetTo(info->team, info->client_port,
                        info->client_token);
                window.SendMessage(B_QUIT_REQUESTED);
        }

        virtual int32 CountItems()
        {
                return fWindows.CountItems();
        }

        virtual void* ItemAt(int32 index)
        {
                return fWindows.ItemAt(index);
        }

        virtual int32 CountGroups()
        {
                return fWorkspaceCount;
        }

        virtual addr_t GroupAt(int32 index)
        {
                return _NthSetBit(index, fWorkspaces) + 1;
        }

        virtual addr_t GroupForItemAt(int32 index)
        {
                client_window_info* info = fWindows.ItemAt(index);
                return _NthSetBit(0, info->workspaces) + 1;
        }

private:
        bool _WindowShouldBeListed(client_window_info* info)
        {
                return info != NULL
                        && (info->feel == B_NORMAL_WINDOW_FEEL
                                || info->feel == kWindowScreenFeel)
                        && (info->show_hide_level <= 0 || info->is_mini);
        }

        uint32 _NthSetBit(int32 index, uint32 mask)
        {
                for (uint32 i = 0; i < 32; i++) {
                        if ((mask & (1UL << i)) != 0) {
                                if (index-- == 0)
                                        return i;
                        }
                }
                return 0;
        }

        static int _CompareWindowInfo(const client_window_info* a,
                const client_window_info* b)
        {
                return strcasecmp(a->name, b->name);
        }

private:
        BObjectList<client_window_info> fWindows;
        uint32                                                  fWorkspaces;
        int32                                                   fWorkspaceCount;
};


class StringItemRenderer : public ListItemRenderer {
public:
        StringItemRenderer()
        {
        }

        virtual ~StringItemRenderer()
        {
        }

        void SetText(BView* owner, const BString& text)
        {
                fText = text;
                owner->TruncateString(&fText, B_TRUNCATE_MIDDLE, 200);
                SetWidth((int32)ceilf(owner->StringWidth(fText.String())));

                font_height fontHeight;
                owner->GetFontHeight(&fontHeight);

                SetBaselineOffset(
                        2 + (int32)ceilf(fontHeight.ascent + fontHeight.leading / 2));

                SetHeight((int32)ceilf(fontHeight.ascent)
                        + (int32)ceilf(fontHeight.descent)
                        + (int32)ceilf(fontHeight.leading) + 4);
        }

        virtual void SetWidth(int32 width)
        {
                fWidth = width;
        }

        virtual void SetHeight(int32 height)
        {
                fHeight = height;
        }

        virtual void SetBaselineOffset(int32 offset)
        {
                fBaselineOffset = offset;
        }

        const BString& Text() const
        {
                return fText;
        }

        virtual BSize MinSize()
        {
                return BSize(fWidth, fHeight);
        }

        virtual BSize MaxSize()
        {
                return BSize(B_SIZE_UNLIMITED, fHeight);
        }

        virtual BSize PreferredSize()
        {
                return BSize(fWidth, fHeight);
        }

        virtual void Draw(BView* owner, BRect frame, int32 index, bool selected)
        {
                owner->SetLowColor(owner->ViewColor());
                owner->MovePenTo(frame.left, frame.top + fBaselineOffset);
                owner->DrawString(fText);
        }

private:
        BString fText;
        int32   fWidth;
        int32   fHeight;
        int32   fBaselineOffset;
};


class WorkspaceRenderer : public StringItemRenderer {
public:
        virtual void SetTo(BView* owner, void* item)
        {
                fWorkspace = (uintptr_t)item;

                if ((uint32)current_workspace() == fWorkspace - 1)
                        SetText(owner, "Current workspace");
                else {
                        BString text("Workspace ");
                        text << fWorkspace;
                        SetText(owner, text);
                }
        }

        virtual void Draw(BView* owner, BRect frame, int32 index, bool selected)
        {
                owner->SetHighColor(tint_color(owner->ViewColor(), B_DARKEN_2_TINT));
                StringItemRenderer::Draw(owner, frame, index, false);
        }

private:
        uint32  fWorkspace;
};


class WindowRenderer : public StringItemRenderer {
public:
        virtual void SetTo(BView* owner, void* item)
        {
                fInfo = (client_window_info*)item;
                SetText(owner, fInfo->name);
        }

        virtual void SetWidth(int32 width)
        {
                StringItemRenderer::SetWidth(width + 20);
        }

        virtual void Draw(BView* owner, BRect frame, int32 index, bool selected)
        {
                owner->SetHighColor(0, 0, 0);
                frame.left += 20;
                StringItemRenderer::Draw(owner, frame, index, selected);
        }

private:
        client_window_info*     fInfo;
};


// #pragma mark -


WindowsView::WindowsView(team_id team, uint32 location)
        :
        BGridView("windows")
{
        app_info info;
        be_roster->GetRunningAppInfo(team, &info);

        LaunchButton* launchButton = new LaunchButton(info.signature, NULL, NULL,
                this);
        launchButton->SetTo(&info.ref);

        BStringView* nameView = new BStringView("name", info.ref.name);
        BFont font(be_plain_font);
        font.SetSize(font.Size() * 2);
        font.SetFace(B_BOLD_FACE);
        nameView->SetFont(&font);

        fListView = new GroupListView("list", new WindowModel(team),
                _Orientation(location));
        fListView->SetItemRenderer(new WindowRenderer());
        fListView->SetGroupRenderer(new WorkspaceRenderer());

        GridLayout()->SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING,
                B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING);

        if (_Orientation(location) == B_HORIZONTAL) {
                BLayoutBuilder::Grid<>(this)
                        .Add(launchButton, 0, 0)
                        .Add(nameView, 1, 0)
                        .Add(fListView, 2, 0);
        } else {
                BLayoutBuilder::Grid<>(this)
                        .Add(launchButton, 0, 0)
                        .Add(nameView, 1, 0)
                        .Add(fListView, 0, 1, 2);
        }
}


WindowsView::~WindowsView()
{
}


void
WindowsView::AttachedToWindow()
{
        fListView->SetSelectionMessage(new BMessage(kMsgActivateWindow), this);
}


void
WindowsView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case kMsgActivateWindow:
                {
                        int32 index;
                        if (message->FindInt32("index", &index) == B_OK
                                && message->HasPointer("item")) {
                                WindowModel* model = (WindowModel*)fListView->Model();

                                if (message->FindInt32("buttons") == B_SECONDARY_MOUSE_BUTTON)
                                        model->Close(index);
                                else
                                        model->BringToFront(index);
                        }
                        break;
                }

                default:
                        BGridView::MessageReceived(message);
                        break;
        }
}


orientation
WindowsView::_Orientation(uint32 location)
{
        return (location & (kTopEdge | kBottomEdge)) != 0
                ? B_HORIZONTAL : B_VERTICAL;
}