root/src/apps/diskusage/ControlsView.cpp
/*
 * Copyright (c) 2008 Stephan Aßmus <superstippi@gmx.de>. All rights reserved.
 * Distributed under the terms of the MIT/X11 license.
 *
 * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software
 * as long as it is accompanied by it's documentation and this copyright notice.
 * The software comes with no warranty, etc.
 */


#include "ControlsView.h"

#include <string.h>

#include <Bitmap.h>
#include <Box.h>
#include <TabView.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <String.h>
#include <SupportDefs.h>
#include <View.h>
#include <Volume.h>
#include <VolumeRoster.h>
#include <Window.h>

#include <LayoutBuilder.h>

#include "DiskUsage.h"
#include "VolumeView.h"


class VolumeTab: public BTab {
public:
                                                                VolumeTab(BVolume* volume);
        virtual                                         ~VolumeTab();

                        BVolume*                        Volume() const
                                                                        { return fVolume; }
                        float                           IconWidth() const;

        virtual void                            DrawLabel(BView* owner, BRect frame);
        virtual void                            DrawFocusMark(BView* owner, BRect frame);

private:
                        BBitmap*                        fIcon;
                        BVolume*                        fVolume;
};


VolumeTab::VolumeTab(BVolume* volume)
        :
        BTab(),
        fIcon(new BBitmap(BRect(0, 0, 15, 15), B_RGBA32)),
        fVolume(volume)
{
        if (fVolume->GetIcon(fIcon, B_MINI_ICON) < B_OK) {
                delete fIcon;
                fIcon = NULL;
        }
}


float
VolumeTab::IconWidth() const
{
        if (fIcon != NULL)
                // add a small margin
                return fIcon->Bounds().Width() + kSmallHMargin;
        else
                return 0.0f;
}


void
VolumeTab::DrawLabel(BView* owner, BRect frame)
{
        owner->SetDrawingMode(B_OP_OVER);
        if (fIcon != NULL) {
                owner->MovePenTo(frame.left + kSmallHMargin,
                        (frame.top + frame.bottom - fIcon->Bounds().Height()) / 2.0);
                owner->DrawBitmap(fIcon);
        }
        font_height fh;
        owner->GetFontHeight(&fh);

        BString label = Label();

        owner->TruncateString(&label, B_TRUNCATE_END,
                frame.Width() - IconWidth() - kSmallHMargin);

        owner->SetHighColor(ui_color(B_CONTROL_TEXT_COLOR));
        owner->DrawString(label,
                BPoint(frame.left + IconWidth() + kSmallHMargin,
                        (frame.top + frame.bottom - fh.ascent - fh.descent) / 2.0
                                + fh.ascent));
}


void
VolumeTab::DrawFocusMark(BView* owner, BRect frame)
{
        frame.left += IconWidth();
        BTab::DrawFocusMark(owner, frame);
}


VolumeTab::~VolumeTab()
{
        delete fIcon;
        delete fVolume;
}


// #pragma mark -


class ControlsView::VolumeTabView: public BTabView {
public:
                                                                VolumeTabView();
        virtual                                         ~VolumeTabView();

        virtual void                            AttachedToWindow();
        virtual void                            MessageReceived(BMessage* message);
        virtual BRect                           TabFrame(int32 index) const;

                        BVolume*                        FindDeviceFor(dev_t device,
                                                                        bool invoke = false);

private:
                        void                            _AddVolume(dev_t device);
                        void                            _RemoveVolume(dev_t device);

                        BVolumeRoster*          fVolumeRoster;
};


ControlsView::VolumeTabView::VolumeTabView()
        :
        BTabView("volume_tabs", B_WIDTH_FROM_LABEL)
{
        SetBorder(B_NO_BORDER);
}


ControlsView::VolumeTabView::~VolumeTabView()
{
        fVolumeRoster->StopWatching();
        delete fVolumeRoster;
}


BRect
ControlsView::VolumeTabView::TabFrame(int32 index) const
{
        float height = BTabView::TabFrame(index).Height();
        float x = 0.0f;
        float width = 0.0f;
        float minStringWidth = StringWidth("Haiku");
        int32 countTabs = CountTabs();

        // calculate the total width if no truncation is made at all
        float averageWidth = Frame().Width() / countTabs;

        // margins are the deltas with the average widths
        float* margins = new float[countTabs];
        for (int32 i = 0; i < countTabs; i++) {
                float tabLabelWidth = StringWidth(TabAt(i)->Label());
                if (tabLabelWidth < minStringWidth)
                        tabLabelWidth = minStringWidth;

                float tabWidth = tabLabelWidth + 3.0f * kSmallHMargin
                        + ((VolumeTab*)TabAt(i))->IconWidth();
                margins[i] = tabWidth - averageWidth;
                width += tabWidth;
        }

        // determine how much we should shave to show all tabs (truncating)
        float toShave = width - Frame().Width();

        if (toShave > 0.0f) {
                // the thinest a tab can be to hold the minimum string
                float minimumMargin = minStringWidth + 3.0f * kSmallHMargin
                        - averageWidth;

                float averageToShave;
                float oldToShave;
                /*
                        we might have to do multiple passes because of the minimum
                        tab width we are imposing.
                        we could also fail to totally fit all tabs.
                        TODO: allow paging.
                */

                do {
                        averageToShave = toShave / countTabs;
                        oldToShave = toShave;
                        for (int32 i = 0; i < countTabs; i++) {
                                float iconWidth = ((VolumeTab*)TabAt(i))->IconWidth();
                                float newMargin = max_c(margins[i] - averageToShave,
                                        minimumMargin + iconWidth);
                                toShave -= margins[i] - newMargin;
                                margins[i] = newMargin;
                        }
                } while (toShave > 0 && fabs(oldToShave - toShave) >= 1.0f);
        }

        for (int i = 0; i < index; i++)
                x += averageWidth + margins[i];

        float margin = margins[index];
        delete[] margins;

        return BRect(x, 0.0f, x + averageWidth + margin, height);
}


void
ControlsView::VolumeTabView::AttachedToWindow()
{
        // Populate the menu with the persistent volumes.
        fVolumeRoster = new BVolumeRoster();

        BVolume tempVolume;
        while (fVolumeRoster->GetNextVolume(&tempVolume) == B_OK) {
                if (!tempVolume.IsPersistent())
                        continue;

                char name[B_PATH_NAME_LENGTH];
                if (tempVolume.GetName(name) != B_OK)
                        continue;

                if (strcmp(name, "system") == 0
                        || strcmp(name, "config") == 0) {
                        // Don't include virtual volumes.
                        continue;
                }

                BVolume* volume = new BVolume(tempVolume);
                VolumeView* volumeView = new VolumeView(name, volume);
                VolumeTab* volumeTab = new VolumeTab(volume);
                AddTab(volumeView, volumeTab);
        }

        // Begin watching mount and unmount events.
        fVolumeRoster->StartWatching(BMessenger(this));
}


void
ControlsView::VolumeTabView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case B_NODE_MONITOR:
                        switch (message->FindInt32("opcode")) {
                                case B_DEVICE_MOUNTED:
                                        _AddVolume(message->FindInt32("new device"));
                                        break;

                                case B_DEVICE_UNMOUNTED:
                                        _RemoveVolume(message->FindInt32("device"));
                                        break;
                        }
                        break;

                case kBtnCancel:
                case kBtnRescan:
                        ViewForTab(Selection())->MessageReceived(message);
                        break;

                case B_SIMPLE_DATA:
                case B_REFS_RECEIVED:
                {
                        entry_ref ref;

                        for (int i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
                                BEntry entry(&ref, true);
                                BPath path;
                                entry.GetPath(&path);
                                dev_t device = dev_for_path(path.Path());

                                for (int j = 0; VolumeTab* item = (VolumeTab*)TabAt(j); j++) {
                                        if (item->Volume()->Device() == device) {
                                                Select(j);
                                                ((VolumeView*)(item->View()))->SetPath(path);
                                                break;
                                        }
                                }
                        }
                        break;
                }

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


BVolume*
ControlsView::VolumeTabView::FindDeviceFor(dev_t device, bool invoke)
{
        BVolume* volume = NULL;

        // Iterate through items looking for a BVolume representing this device.
        for (int i = 0; VolumeTab* item = (VolumeTab*)TabAt(i); i++) {
                if (item->Volume()->Device() == device) {
                        volume = item->Volume();
                        if (invoke)
                                Select(i);
                        break;
                }
        }

        return volume;
}


void
ControlsView::VolumeTabView::_AddVolume(dev_t device)
{
        // Make sure the volume is not already in the menu.
        for (int i = 0; VolumeTab* item = (VolumeTab*)TabAt(i); i++) {
                if (item->Volume()->Device() == device)
                        return;
        }

        BVolume* volume = new BVolume(device);

        VolumeTab* item = new VolumeTab(volume);
        char name[B_PATH_NAME_LENGTH];
        volume->GetName(name);

        AddTab(new VolumeView(name, volume), item);
        Invalidate();
}


void
ControlsView::VolumeTabView::_RemoveVolume(dev_t device)
{
        for (int i = 0; VolumeTab* item = (VolumeTab*)TabAt(i); i++) {
                if (item->Volume()->Device() == device) {
                        if (i == 0)
                                Select(1);
                        else
                                Select(i - 1);
                        RemoveTab(i);
                        delete item;
                        return;
                }
        }
}


// #pragma mark -


ControlsView::ControlsView()
        :
        BView(NULL, B_WILL_DRAW)
{
        SetLayout(new BGroupLayout(B_VERTICAL));
        fVolumeTabView = new VolumeTabView();
        AddChild(BLayoutBuilder::Group<>(B_VERTICAL)
                .Add(fVolumeTabView)
        );
}


void
ControlsView::MessageReceived(BMessage* msg)
{
        switch (msg->what) {
                case B_SIMPLE_DATA:
                case B_REFS_RECEIVED:
                        fVolumeTabView->MessageReceived(msg);
                        break;

                case kBtnCancel:
                case kBtnRescan:
                        fVolumeTabView->MessageReceived(msg);
                        break;

                default:
                        BView::MessageReceived(msg);
        }
}


ControlsView::~ControlsView()
{
}


void
ControlsView::ShowInfo(const FileInfo* info)
{
        ((VolumeView*)fVolumeTabView->ViewForTab(
                fVolumeTabView->Selection()))->ShowInfo(info);
}


void
ControlsView::EnableRescan()
{
        ((VolumeView*)fVolumeTabView->ViewForTab(
                fVolumeTabView->Selection()))->EnableRescan();
}


void
ControlsView::EnableCancel()
{
        ((VolumeView*)fVolumeTabView->ViewForTab(
                fVolumeTabView->Selection()))->EnableCancel();
}


BVolume*
ControlsView::FindDeviceFor(dev_t device, bool invoke)
{
        return fVolumeTabView->FindDeviceFor(device, invoke);
}