root/src/apps/stylededit/StatusView.cpp
/*
 * Copyright 2002-2012, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Vlad Slepukhin
 *              Siarzhuk Zharski
 */


#include "StatusView.h"
#include <StatusView.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <Catalog.h>
#include <CharacterSet.h>
#include <CharacterSetRoster.h>
#include <ControlLook.h>
#include <MenuItem.h>
#include <Message.h>
#include <PopUpMenu.h>
#include <ScrollView.h>
#include <StringView.h>
#include <Window.h>

#include <tracker_private.h>
#include "DirMenu.h"

#include "Constants.h"


const float kHorzSpacing = 5.f;
#define UTF8_EXPAND_ARROW "\xe2\x96\xbe"

using namespace BPrivate;


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "StatusView"


StatusView::StatusView(BScrollView* scrollView)
                        :
                        BView(BRect(), "statusview",
                                B_FOLLOW_BOTTOM | B_FOLLOW_LEFT, B_WILL_DRAW),
                        fScrollView(scrollView),
                        fPreferredSize(0., 0.),
                        fReadOnly(false),
                        fCanUnlock(false)
{
        memset(fCellWidth, 0, sizeof(fCellWidth));
}


StatusView::~StatusView()
{
}


void
StatusView::AttachedToWindow()
{
        SetFont(be_plain_font);
        BPrivate::AdoptScrollBarFontSize(this);

        BMessage message(UPDATE_STATUS);
        message.AddInt32("line", 1);
        message.AddInt32("column", 1);
        message.AddString("encoding", "");
        SetStatus(&message);

        BScrollBar* scrollBar = fScrollView->ScrollBar(B_HORIZONTAL);
        MoveTo(0., scrollBar->Frame().top);

        rgb_color color = B_TRANSPARENT_COLOR;
        BView* parent = Parent();
        if (parent != NULL)
                color = parent->ViewColor();

        if (color == B_TRANSPARENT_COLOR)
                color = ui_color(B_PANEL_BACKGROUND_COLOR);

        SetViewColor(color);

        ResizeToPreferred();
}


void
StatusView::GetPreferredSize(float* _width, float* _height)
{
        _ValidatePreferredSize();

        if (_width)
                *_width = fPreferredSize.width;

        if (_height)
                *_height = fPreferredSize.height;
}


void
StatusView::ResizeToPreferred()
{
        float width, height;
        GetPreferredSize(&width, &height);

        if (Bounds().Width() > width)
                width = Bounds().Width();

        BView::ResizeTo(width, height);
}


void
StatusView::Draw(BRect updateRect)
{
        if (fPreferredSize.width <= 0)
                return;

        if (be_control_look != NULL) {
                BRect bounds(Bounds());
                be_control_look->DrawMenuBarBackground(this,
                        bounds, updateRect,     ViewColor());
        }

        BRect bounds(Bounds());
        SetHighColor(tint_color(ViewColor(), B_DARKEN_2_TINT));
        StrokeLine(bounds.LeftTop(), bounds.RightTop());

        float x = bounds.left;
        for (size_t i = 0; i < kStatusCellCount - 1; i++) {
                x += fCellWidth[i];
                StrokeLine(BPoint(x, bounds.top + 3), BPoint(x, bounds.bottom - 3));
        }

        SetLowColor(ViewColor());
        SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

        font_height fontHeight;
        GetFontHeight(&fontHeight);

        x = bounds.left;
        float y = (bounds.bottom + bounds.top
                + ceilf(fontHeight.ascent) - ceilf(fontHeight.descent)) / 2;

        for (size_t i = 0; i < kStatusCellCount; i++) {
                if (fCellText[i].Length() == 0)
                        continue;
                DrawString(fCellText[i], BPoint(x + kHorzSpacing, y));
                x += fCellWidth[i];
        }
}


void
StatusView::MouseDown(BPoint where)
{
        if (where.x < fCellWidth[kPositionCell]) {
                _ShowDirMenu();
                return;
        }

        if (!fReadOnly || !fCanUnlock)
                return;

        float left = fCellWidth[kPositionCell] + fCellWidth[kEncodingCell];
        if (where.x < left)
                return;

        int32 clicks = 0;
        BMessage* message = Window()->CurrentMessage();
        if (message != NULL
                && message->FindInt32("clicks", &clicks) == B_OK && clicks > 1)
                        return;

        BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
        menu->AddItem(new BMenuItem(B_TRANSLATE("Unlock file"),
                                        new BMessage(UNLOCK_FILE)));
        where.x = left;
        where.y = Bounds().bottom;

        ConvertToScreen(&where);
        menu->SetTargetForItems(this);
        menu->Go(where, true, true,     true);
}


void
StatusView::SetStatus(BMessage* message)
{
        int32 line = 0, column = 0;
        if (B_OK == message->FindInt32("line", &line)
                && B_OK == message->FindInt32("column", &column))
        {
                char info[256];
                snprintf(info, sizeof(info), B_TRANSLATE("line %d, column %d"), (int)line, (int)column);
                fCellText[kPositionCell].SetTo(info);
        }

        if (B_OK == message->FindString("encoding", &fEncoding)) {
                // sometime corresponding Int-32 "encoding" attrib is read as string :(
                if (fEncoding.Length() == 0
                        || fEncoding.Compare("\xff\xff") == 0
                        || fEncoding.Compare("UTF-8") == 0)
                {
                        // do not display default UTF-8 encoding
                        fCellText[kEncodingCell].Truncate(0);
                        fEncoding.Truncate(0);
                } else {
                        const BCharacterSet* charset
                                = BCharacterSetRoster::FindCharacterSetByName(fEncoding);
                        fCellText[kEncodingCell]
                                = charset != NULL ? charset->GetPrintName() : "";
                }
        }

        bool modified = false;
        fReadOnly = false;
        fCanUnlock = false;
        if (B_OK == message->FindBool("modified", &modified) && modified) {
                fCellText[kFileStateCell] = B_TRANSLATE("Modified");
        } else if (B_OK == message->FindBool("readOnly", &fReadOnly) && fReadOnly) {
                fCellText[kFileStateCell] = B_TRANSLATE("Read-only");
            if (B_OK == message->FindBool("canUnlock", &fCanUnlock) && fCanUnlock)
                        fCellText[kFileStateCell] << " " UTF8_EXPAND_ARROW;
        } else
                fCellText[kFileStateCell].Truncate(0);

        _ValidatePreferredSize();
        Invalidate();
}


void
StatusView::SetRef(const entry_ref& ref)
{
        fRef = ref;
}


void
StatusView::_ValidatePreferredSize()
{
        float orgWidth = fPreferredSize.width;
        // width
        fPreferredSize.width = 0.f;
        for (size_t i = 0; i < kStatusCellCount; i++) {
                if (fCellText[i].Length() == 0) {
                        fCellWidth[i] = 0;
                        continue;
                }
                float width = ceilf(StringWidth(fCellText[i]));
                if (width > 0)
                        width += kHorzSpacing * 2;
                if (width > fCellWidth[i] || i != kPositionCell)
                        fCellWidth[i] = width;
                fPreferredSize.width += fCellWidth[i];
        }

        // height
        font_height fontHeight;
        GetFontHeight(&fontHeight);

        fPreferredSize.height = ceilf(fontHeight.ascent + fontHeight.descent
                + fontHeight.leading);

        if (fPreferredSize.height < B_H_SCROLL_BAR_HEIGHT)
                fPreferredSize.height = B_H_SCROLL_BAR_HEIGHT;

        float delta = fPreferredSize.width - orgWidth;
        ResizeBy(delta, 0);
        BScrollBar* scrollBar = fScrollView->ScrollBar(B_HORIZONTAL);
        scrollBar->ResizeBy(-delta, 0);
        scrollBar->MoveBy(delta, 0);
}


void
StatusView::_ShowDirMenu()
{
        BEntry entry;
        status_t status = entry.SetTo(&fRef);

        if (status != B_OK || !entry.Exists())
                return;

        BPrivate::BDirMenu* menu = new BDirMenu(NULL,
                BMessenger(kTrackerSignature), B_REFS_RECEIVED);

        menu->Populate(&entry, Window(), false, false, true, false, true);

        BPoint point = Bounds().LeftBottom();
        point.y += 3;
        ConvertToScreen(&point);
        BRect clickToOpenRect(Bounds());
        ConvertToScreen(&clickToOpenRect);
        menu->Go(point, true, true, clickToOpenRect);
        delete menu;
}