root/src/preferences/locale/LanguageListView.cpp
/*
 * Copyright 2006-2010, Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 *              Adrien Destugues <pulkomandy@gmail.com>
 *              Axel Dörfler, axeld@pinc-software.de
 *              Oliver Tappe <zooey@hirschkaefer.de>
 */


#include "LanguageListView.h"

#include <stdio.h>

#include <new>

#include <Bitmap.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <FormattingConventions.h>
#include <GradientLinear.h>
#include <LocaleRoster.h>
#include <Region.h>
#include <Window.h>


#define MAX_DRAG_HEIGHT         200.0
#define ALPHA                           170

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "LanguageListView"


LanguageListItem::LanguageListItem(const char* text, const char* id,
        const char* languageCode)
        :
        BStringItem(text),
        fID(id),
        fCode(languageCode)
{
}


LanguageListItem::LanguageListItem(const LanguageListItem& other)
        :
        BStringItem(other.Text()),
        fID(other.fID),
        fCode(other.fCode)
{
}


void
LanguageListItem::DrawItem(BView* owner, BRect frame, bool complete)
{
        DrawItemWithTextOffset(owner, frame, complete, 0);
}


void
LanguageListItem::DrawItemWithTextOffset(BView* owner, BRect frame,
        bool complete, float textOffset)
{
        rgb_color highColor = owner->HighColor();
        rgb_color lowColor = owner->LowColor();

        if (IsSelected() || complete) {
                rgb_color color;
                if (IsSelected())
                        color = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
                else
                        color = owner->ViewColor();

                owner->SetHighColor(color);
                owner->SetLowColor(color);
                owner->FillRect(frame);
        } else
                owner->SetLowColor(owner->ViewColor());

        BString text = Text();
        if (!IsEnabled()) {
                rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
                if (textColor.red + textColor.green + textColor.blue > 128 * 3)
                        owner->SetHighColor(tint_color(textColor, B_DARKEN_2_TINT));
                else
                        owner->SetHighColor(tint_color(textColor, B_LIGHTEN_2_TINT));

                text << "   [" << B_TRANSLATE("already chosen") << "]";
        } else {
                if (IsSelected())
                        owner->SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
                else
                        owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
        }

        owner->MovePenTo(
                frame.left + be_control_look->DefaultLabelSpacing() + textOffset,
                frame.top + BaselineOffset());
        owner->DrawString(text.String());

        owner->SetHighColor(highColor);
        owner->SetLowColor(lowColor);
}


// #pragma mark -


LanguageListItemWithFlag::LanguageListItemWithFlag(const char* text,
        const char* id, const char* languageCode, const char* countryCode)
        :
        LanguageListItem(text, id, languageCode),
        fCountryCode(countryCode),
        fIcon(NULL)
{
}


LanguageListItemWithFlag::LanguageListItemWithFlag(
        const LanguageListItemWithFlag& other)
        :
        LanguageListItem(other),
        fCountryCode(other.fCountryCode),
        fIcon(other.fIcon != NULL ? new BBitmap(*other.fIcon) : NULL)
{
}


LanguageListItemWithFlag::~LanguageListItemWithFlag()
{
        delete fIcon;
}


void
LanguageListItemWithFlag::Update(BView* owner, const BFont* font)
{
        LanguageListItem::Update(owner, font);

        float iconSize = Height();
        SetWidth(Width() + iconSize + be_control_look->DefaultLabelSpacing());

        if (fCountryCode.IsEmpty())
                return;

        fIcon = new(std::nothrow) BBitmap(BRect(0, 0, iconSize - 1, iconSize - 1),
                B_RGBA32);
        if (fIcon != NULL && BLocaleRoster::Default()->GetFlagIconForCountry(fIcon,
                        fCountryCode.String()) != B_OK) {
                delete fIcon;
                fIcon = NULL;
        }
}


void
LanguageListItemWithFlag::DrawItem(BView* owner, BRect frame, bool complete)
{
        if (fIcon == NULL || !fIcon->IsValid()) {
                DrawItemWithTextOffset(owner, frame, complete, 0);
                return;
        }

        float iconSize = fIcon->Bounds().Width();
        DrawItemWithTextOffset(owner, frame, complete,
                iconSize + be_control_look->DefaultLabelSpacing());

        BRect iconFrame(frame.left + be_control_look->DefaultLabelSpacing(),
                frame.top,
                frame.left + iconSize - 1 + be_control_look->DefaultLabelSpacing(),
                frame.top + iconSize - 1);
        owner->SetDrawingMode(B_OP_OVER);
        owner->DrawBitmap(fIcon, iconFrame);
        owner->SetDrawingMode(B_OP_COPY);
}


// #pragma mark -


LanguageListView::LanguageListView(const char* name, list_view_type type)
        :
        BOutlineListView(name, type),
        fDropIndex(-1),
        fDropTargetHighlightFrame(),
        fGlobalDropTargetIndicator(false),
        fDeleteMessage(NULL),
        fDragMessage(NULL)
{
}


LanguageListView::~LanguageListView()
{
}


LanguageListItem*
LanguageListView::ItemForLanguageID(const char* id, int32* _index) const
{
        for (int32 index = 0; index < FullListCountItems(); index++) {
                LanguageListItem* item
                        = static_cast<LanguageListItem*>(FullListItemAt(index));

                if (item->ID() == id) {
                        if (_index != NULL)
                                *_index = index;
                        return item;
                }
        }

        return NULL;
}


LanguageListItem*
LanguageListView::ItemForLanguageCode(const char* code, int32* _index) const
{
        for (int32 index = 0; index < FullListCountItems(); index++) {
                LanguageListItem* item
                        = static_cast<LanguageListItem*>(FullListItemAt(index));

                if (item->Code() == code) {
                        if (_index != NULL)
                                *_index = index;
                        return item;
                }
        }

        return NULL;
}


void
LanguageListView::SetDeleteMessage(BMessage* message)
{
        delete fDeleteMessage;
        fDeleteMessage = message;
}


void
LanguageListView::SetDragMessage(BMessage* message)
{
        delete fDragMessage;
        fDragMessage = message;
}


void
LanguageListView::SetGlobalDropTargetIndicator(bool isGlobal)
{
        fGlobalDropTargetIndicator = isGlobal;
}


void
LanguageListView::AttachedToWindow()
{
        BOutlineListView::AttachedToWindow();
        ScrollToSelection();
}


void
LanguageListView::MessageReceived(BMessage* message)
{
        if (message->WasDropped() && _AcceptsDragMessage(message)) {
                // Someone just dropped something on us
                BMessage dragMessage(*message);
                dragMessage.AddInt32("drop_index", fDropIndex);
                dragMessage.AddPointer("drop_target", this);
                Messenger().SendMessage(&dragMessage);
        } else
                BOutlineListView::MessageReceived(message);
}


void
LanguageListView::Draw(BRect updateRect)
{
        BOutlineListView::Draw(updateRect);

        if (fDropIndex >= 0 && fDropTargetHighlightFrame.IsValid()) {
                // TODO: decide if drawing of a drop target indicator should be moved
                //       into ControlLook
                BGradientLinear gradient;
                int step = fGlobalDropTargetIndicator ? 64 : 128;
                for (int i = 0; i < 256; i += step)
                        gradient.AddColor(i % (step * 2) == 0
                                ? ViewColor() : ui_color(B_CONTROL_HIGHLIGHT_COLOR), i);
                gradient.AddColor(ViewColor(), 255);
                gradient.SetStart(fDropTargetHighlightFrame.LeftTop());
                gradient.SetEnd(fDropTargetHighlightFrame.RightBottom());
                if (fGlobalDropTargetIndicator) {
                        BRegion region(fDropTargetHighlightFrame);
                        region.Exclude(fDropTargetHighlightFrame.InsetByCopy(2.0, 2.0));
                        ConstrainClippingRegion(&region);
                        FillRect(fDropTargetHighlightFrame, gradient);
                        ConstrainClippingRegion(NULL);
                } else
                        FillRect(fDropTargetHighlightFrame, gradient);
        }
}


bool
LanguageListView::InitiateDrag(BPoint point, int32 dragIndex,
        bool /*wasSelected*/)
{
        if (fDragMessage == NULL)
                return false;

        BListItem* item = ItemAt(CurrentSelection(0));
        if (item == NULL) {
                // workaround for a timing problem
                // TODO: this should support extending the selection
                item = ItemAt(dragIndex);
                Select(dragIndex);
        }
        if (item == NULL)
                return false;

        // create drag message
        BMessage message = *fDragMessage;
        message.AddPointer("listview", this);

        for (int32 i = 0;; i++) {
                int32 index = CurrentSelection(i);
                if (index < 0)
                        break;

                message.AddInt32("index", index);
        }

        // figure out drag rect

        BRect dragRect(0.0, 0.0, Bounds().Width(), -1.0);
        int32 numItems = 0;
        bool fade = false;

        // figure out, how many items fit into our bitmap
        for (int32 i = 0, index; message.FindInt32("index", i, &index) == B_OK;
                        i++) {
                BListItem* item = ItemAt(index);
                if (item == NULL)
                        break;

                dragRect.bottom += ceilf(item->Height()) + 1.0;
                numItems++;

                if (dragRect.Height() > MAX_DRAG_HEIGHT) {
                        dragRect.bottom = MAX_DRAG_HEIGHT;
                        fade = true;
                        break;
                }
        }

        BBitmap* dragBitmap = new BBitmap(dragRect, B_RGB32, true);
        if (dragBitmap->IsValid()) {
                BView* view = new BView(dragBitmap->Bounds(), "helper", B_FOLLOW_NONE,
                        B_WILL_DRAW);
                dragBitmap->AddChild(view);
                dragBitmap->Lock();
                BRect itemBounds(dragRect) ;
                itemBounds.bottom = 0.0;
                // let all selected items, that fit into our drag_bitmap, draw
                for (int32 i = 0; i < numItems; i++) {
                        int32 index = message.FindInt32("index", i);
                        LanguageListItem* item
                                = static_cast<LanguageListItem*>(ItemAt(index));
                        itemBounds.bottom = itemBounds.top + ceilf(item->Height());
                        if (itemBounds.bottom > dragRect.bottom)
                                itemBounds.bottom = dragRect.bottom;
                        item->DrawItem(view, itemBounds);
                        itemBounds.top = itemBounds.bottom + 1.0;
                }
                // make a black frame around the edge
                view->SetHighColor(0, 0, 0, 255);
                view->StrokeRect(view->Bounds());
                view->Sync();

                uint8* bits = (uint8*)dragBitmap->Bits();
                int32 height = (int32)dragBitmap->Bounds().Height() + 1;
                int32 width = (int32)dragBitmap->Bounds().Width() + 1;
                int32 bpr = dragBitmap->BytesPerRow();

                if (fade) {
                        for (int32 y = 0; y < height - ALPHA / 2; y++, bits += bpr) {
                                uint8* line = bits + 3;
                                for (uint8* end = line + 4 * width; line < end; line += 4)
                                        *line = ALPHA;
                        }
                        for (int32 y = height - ALPHA / 2; y < height;
                                y++, bits += bpr) {
                                uint8* line = bits + 3;
                                for (uint8* end = line + 4 * width; line < end; line += 4)
                                        *line = (height - y) << 1;
                        }
                } else {
                        for (int32 y = 0; y < height; y++, bits += bpr) {
                                uint8* line = bits + 3;
                                for (uint8* end = line + 4 * width; line < end; line += 4)
                                        *line = ALPHA;
                        }
                }
                dragBitmap->Unlock();
        } else {
                delete dragBitmap;
                dragBitmap = NULL;
        }

        if (dragBitmap != NULL)
                DragMessage(&message, dragBitmap, B_OP_ALPHA, BPoint(0.0, 0.0));
        else
                DragMessage(&message, dragRect.OffsetToCopy(point), this);

        return true;
}


void
LanguageListView::MouseMoved(BPoint where, uint32 transit,
        const BMessage* dragMessage)
{
        if (dragMessage != NULL && _AcceptsDragMessage(dragMessage)) {
                switch (transit) {
                        case B_ENTERED_VIEW:
                        case B_INSIDE_VIEW:
                        {
                                BRect highlightFrame;

                                if (fGlobalDropTargetIndicator) {
                                        highlightFrame = Bounds();
                                        fDropIndex = 0;
                                } else {
                                        // offset where by half of item height
                                        BRect r = ItemFrame(0);
                                        where.y += r.Height() / 2.0;

                                        int32 index = IndexOf(where);
                                        if (index < 0)
                                                index = CountItems();
                                        highlightFrame = ItemFrame(index);
                                        if (highlightFrame.IsValid())
                                                highlightFrame.bottom = highlightFrame.top;
                                        else {
                                                highlightFrame = ItemFrame(index - 1);
                                                if (highlightFrame.IsValid())
                                                        highlightFrame.top = highlightFrame.bottom;
                                                else {
                                                        // empty view, show indicator at top
                                                        highlightFrame = Bounds();
                                                        highlightFrame.bottom = highlightFrame.top;
                                                }
                                        }
                                        fDropIndex = index;
                                }

                                if (fDropTargetHighlightFrame != highlightFrame) {
                                        Invalidate(fDropTargetHighlightFrame);
                                        fDropTargetHighlightFrame = highlightFrame;
                                        Invalidate(fDropTargetHighlightFrame);
                                }

                                BOutlineListView::MouseMoved(where, transit, dragMessage);
                                return;
                        }
                }
        }

        if (fDropTargetHighlightFrame.IsValid()) {
                Invalidate(fDropTargetHighlightFrame);
                fDropTargetHighlightFrame = BRect();
        }
        BOutlineListView::MouseMoved(where, transit, dragMessage);
}


void
LanguageListView::MouseUp(BPoint point)
{
        BOutlineListView::MouseUp(point);
        if (fDropTargetHighlightFrame.IsValid()) {
                Invalidate(fDropTargetHighlightFrame);
                fDropTargetHighlightFrame = BRect();
        }
}


void
LanguageListView::KeyDown(const char* bytes, int32 numBytes)
{
        if (bytes[0] == B_DELETE && fDeleteMessage != NULL) {
                Invoke(fDeleteMessage);
                return;
        }

        BOutlineListView::KeyDown(bytes, numBytes);
}


bool
LanguageListView::_AcceptsDragMessage(const BMessage* message) const
{
        LanguageListView* sourceView = NULL;
        return message != NULL
                && message->FindPointer("listview", (void**)&sourceView) == B_OK;
}