root/src/apps/webpositive/URLInputGroup.cpp
/*
 * Copyright 2010 Stephan Aßmus <superstippi@gmx.de>
 * All rights reserved. Distributed under the terms of the MIT License.
 */

#include "URLInputGroup.h"

#include <Bitmap.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Clipboard.h>
#include <GroupLayout.h>
#include <GroupLayoutBuilder.h>
#include <Locale.h>
#include <LayoutUtils.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <SeparatorView.h>
#include <TextView.h>
#include <Window.h>

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

#include "BaseURL.h"
#include "BitmapButton.h"
#include "BrowserWindow.h"
#include "BrowsingHistory.h"
#include "IconButton.h"
#include "IconUtils.h"
#include "TextViewCompleter.h"
#include "WebView.h"
#include "WebWindow.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "URL Bar"


class URLChoice : public BAutoCompleter::Choice {
public:
        URLChoice(const BString& choiceText, const BString& displayText,
                        int32 matchPos, int32 matchLen, int32 priority)
                :
                BAutoCompleter::Choice(choiceText, displayText, matchPos, matchLen),
                fPriority(priority)
        {
        }

        bool operator<(const URLChoice& other) const
        {
                if (fPriority > other.fPriority)
                        return true;
                return DisplayText() < other.DisplayText();
        }

        bool operator==(const URLChoice& other) const
        {
                return fPriority == other.fPriority
                        && DisplayText() < other.DisplayText();
        }

private:
        int32 fPriority;
};


class BrowsingHistoryChoiceModel : public BAutoCompleter::ChoiceModel {
        virtual void FetchChoicesFor(const BString& pattern)
        {
                int32 count = CountChoices();
                for (int32 i = 0; i < count; i++) {
                        delete reinterpret_cast<BAutoCompleter::Choice*>(
                                fChoices.ItemAtFast(i));
                }
                fChoices.MakeEmpty();

                // Search through BrowsingHistory for any matches.
                BrowsingHistory* history = BrowsingHistory::DefaultInstance();
                if (!history->Lock())
                        return;

                BString lastBaseURL;
                int32 priority = INT_MAX;

                count = history->CountItems();
                for (int32 i = 0; i < count; i++) {
                        BrowsingHistoryItem item = history->HistoryItemAt(i);
                        const BString& choiceText = item.URL();
                        int32 matchPos = choiceText.IFindFirst(pattern);
                        if (matchPos < 0)
                                continue;
                        if (lastBaseURL.Length() > 0
                                && choiceText.FindFirst(lastBaseURL) >= 0) {
                                priority--;
                        } else
                                priority = INT_MAX;
                        lastBaseURL = baseURL(choiceText);
                        fChoices.AddItem(new URLChoice(choiceText,
                                choiceText, matchPos, pattern.Length(), priority));
                }

                history->Unlock();

                fChoices.SortItems(_CompareChoices);
        }

        virtual int32 CountChoices() const
        {
                return fChoices.CountItems();
        }

        virtual const BAutoCompleter::Choice* ChoiceAt(int32 index) const
        {
                return reinterpret_cast<BAutoCompleter::Choice*>(
                        fChoices.ItemAt(index));
        }

        static int _CompareChoices(const void* a, const void* b)
        {
                const URLChoice* aChoice
                        = *reinterpret_cast<const URLChoice* const *>(a);
                const URLChoice* bChoice
                        = *reinterpret_cast<const URLChoice* const *>(b);
                if (*aChoice < *bChoice)
                        return -1;
                else if (*aChoice == *bChoice)
                        return 0;
                return 1;
        }

private:
        BList fChoices;
};


// #pragma mark - URLTextView


static const float kHorizontalTextRectInset = 4.0;


class URLInputGroup::URLTextView : public BTextView {
private:
        static const uint32 MSG_CLEAR = 'cler';

public:
                                                                URLTextView(URLInputGroup* parent);
        virtual                                         ~URLTextView();

        virtual void                            MessageReceived(BMessage* message);
        virtual void                            MouseDown(BPoint where);
        virtual void                            KeyDown(const char* bytes, int32 numBytes);
        virtual void                            MakeFocus(bool focused = true);

        virtual BSize                           MinSize();
        virtual BSize                           MaxSize();

                        void                            SetUpdateAutoCompleterChoices(bool update);

protected:
        virtual void                            InsertText(const char* inText, int32 inLength,
                                                                        int32 inOffset,
                                                                        const text_run_array* inRuns);
        virtual void                            DeleteText(int32 fromOffset, int32 toOffset);

private:
                        URLInputGroup*          fURLInputGroup;
                        TextViewCompleter*      fURLAutoCompleter;
                        bool                            fUpdateAutoCompleterChoices;
};


URLInputGroup::URLTextView::URLTextView(URLInputGroup* parent)
        :
        BTextView("url"),
        fURLInputGroup(parent),
        fURLAutoCompleter(new TextViewCompleter(this,
                new BrowsingHistoryChoiceModel())),
        fUpdateAutoCompleterChoices(true)
{
        MakeResizable(true);
        SetStylable(true);
        SetInsets(be_control_look->DefaultLabelSpacing(), 2, 0, 2);
        fURLAutoCompleter->SetModificationsReported(true);
}


URLInputGroup::URLTextView::~URLTextView()
{
        delete fURLAutoCompleter;
}


void
URLInputGroup::URLTextView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MSG_CLEAR:
                        SetText("");
                        break;

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


void
URLInputGroup::URLTextView::MouseDown(BPoint where)
{
        bool wasFocus = IsFocus();
        if (!wasFocus)
                MakeFocus(true);

        int32 buttons;
        if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK)
                buttons = B_PRIMARY_MOUSE_BUTTON;

        if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
                // Display context menu
                int32 selectionStart;
                int32 selectionEnd;
                GetSelection(&selectionStart, &selectionEnd);
                bool canCutOrCopy = selectionEnd > selectionStart;

                bool canPaste = false;
                if (be_clipboard->Lock()) {
                        if (BMessage* data = be_clipboard->Data())
                                canPaste = data->HasData("text/plain", B_MIME_TYPE);
                        be_clipboard->Unlock();
                }

                BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"),
                        new BMessage(B_CUT));
                BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"),
                        new BMessage(B_COPY));
                BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
                        new BMessage(B_PASTE));
                BMenuItem* clearItem = new BMenuItem(B_TRANSLATE("Clear"),
                        new BMessage(MSG_CLEAR));
                cutItem->SetEnabled(canCutOrCopy);
                copyItem->SetEnabled(canCutOrCopy);
                pasteItem->SetEnabled(canPaste);
                clearItem->SetEnabled(strlen(Text()) > 0);

                BPopUpMenu* menu = new BPopUpMenu("url context");
                menu->AddItem(cutItem);
                menu->AddItem(copyItem);
                menu->AddItem(pasteItem);
                menu->AddItem(clearItem);

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

        // Only pass through to base class if we already have focus.
        if (!wasFocus)
                return;

        BTextView::MouseDown(where);
}


void
URLInputGroup::URLTextView::KeyDown(const char* bytes, int32 numBytes)
{
        switch (bytes[0]) {
                case B_TAB:
                        BView::KeyDown(bytes, numBytes);
                        break;

                case B_ESCAPE:
                        // Text already unlocked && replaced in BrowserWindow,
                        // now select it.
                        SelectAll();
                        break;

                case B_RETURN:
                        // Don't let this through to the text view.
                        break;

                default:
                {
                        BString currentText = Text();
                        BTextView::KeyDown(bytes, numBytes);
                        // Lock the URL input if it was modified
                        if (!fURLInputGroup->IsURLInputLocked()
                                && Text() != currentText)
                                fURLInputGroup->LockURLInput();
                        break;
                }
        }
}

void
URLInputGroup::URLTextView::MakeFocus(bool focus)
{
        // Unlock the URL input if focus was lost.
        if (!focus)
                fURLInputGroup->LockURLInput(false);

        if (focus == IsFocus())
                return;

        BTextView::MakeFocus(focus);

        if (focus)
                SelectAll();

        fURLInputGroup->Invalidate();
}


BSize
URLInputGroup::URLTextView::MinSize()
{
        BSize min;
        min.height = ceilf(LineHeight(0) + kHorizontalTextRectInset);
                // we always add at least one pixel vertical inset top/bottom for
                // the text rect.
        min.width = min.height * 3;
        return BLayoutUtils::ComposeSize(ExplicitMinSize(), min);
}


BSize
URLInputGroup::URLTextView::MaxSize()
{
        BSize max(MinSize());
        max.width = B_SIZE_UNLIMITED;
        return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
}


void
URLInputGroup::URLTextView::SetUpdateAutoCompleterChoices(bool update)
{
        fUpdateAutoCompleterChoices = update;
}


void
URLInputGroup::URLTextView::InsertText(const char* inText, int32 inLength,
        int32 inOffset, const text_run_array* inRuns)
{
        // Filter all line breaks, note that inText is not terminated.
        if (inLength == 1) {
                if (*inText == '\n' || *inText == '\r')
                        BTextView::InsertText(" ", 1, inOffset, inRuns);
                else
                        BTextView::InsertText(inText, 1, inOffset, inRuns);
        } else {
                BString filteredText(inText, inLength);
                filteredText.ReplaceAll('\n', ' ');
                filteredText.ReplaceAll('\r', ' ');
                BTextView::InsertText(filteredText.String(), inLength, inOffset,
                        inRuns);
        }

        // Make the base URL part bold.
        BString text(Text(), TextLength());
        int32 baseUrlStart = text.FindFirst("://");
        if (baseUrlStart >= 0)
                baseUrlStart += 3;
        else
                baseUrlStart = 0;
        int32 baseUrlEnd = text.FindFirst("/", baseUrlStart);
        if (baseUrlEnd < 0)
                baseUrlEnd = TextLength();

        BFont font;
        GetFont(&font);
        const rgb_color hostColor = ui_color(B_DOCUMENT_TEXT_COLOR);
        const rgb_color urlColor = tint_color(hostColor,
                (hostColor.IsDark() ? B_LIGHTEN_1_TINT : B_DARKEN_1_TINT));
        if (baseUrlStart > 0)
                SetFontAndColor(0, baseUrlStart, &font, B_FONT_ALL, &urlColor);
        if (baseUrlEnd > baseUrlStart) {
                font.SetFace(B_BOLD_FACE);
                SetFontAndColor(baseUrlStart, baseUrlEnd, &font, B_FONT_ALL,
                        &hostColor);
        }
        if (baseUrlEnd < TextLength()) {
                font.SetFace(B_REGULAR_FACE);
                SetFontAndColor(baseUrlEnd, TextLength(), &font, B_FONT_ALL,
                        &urlColor);
        }

        fURLAutoCompleter->TextModified(fUpdateAutoCompleterChoices);
}


void
URLInputGroup::URLTextView::DeleteText(int32 fromOffset, int32 toOffset)
{
        BTextView::DeleteText(fromOffset, toOffset);

        fURLAutoCompleter->TextModified(fUpdateAutoCompleterChoices);
}


const uint32 kGoBitmapWidth = 14;
const uint32 kGoBitmapHeight = 14;
const color_space kGoBitmapFormat = B_RGBA32;

const unsigned char kGoBitmapBits[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0,
        0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x2f, 0x2f, 0x2f, 0x56, 0x50, 0x50, 0x50, 0xff, 0x4d, 0x4d, 0x4d, 0xed,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21,
        0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
        0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
        0x50, 0x50, 0x50, 0xff, 0x37, 0x37, 0x37, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff,
        0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
        0x50, 0x50, 0x50, 0xff, 0x4b, 0x4b, 0x4b, 0xec, 0x37, 0x37, 0x37, 0x77, 0x00, 0x00, 0x00, 0x02,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe1, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21,
        0x49, 0x49, 0x49, 0xe1, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe1,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};


const unsigned char kPlaceholderIcon[] = {
        0x6e, 0x63, 0x69, 0x66, 0x04, 0x04, 0x00, 0x66, 0x03, 0x00, 0x3f, 0x80,
        0x02, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x3d, 0xa6, 0x64, 0xc2, 0x19,
        0x98, 0x00, 0x00, 0x00, 0x4d, 0xce, 0x64, 0x49, 0xac, 0xcc, 0x00, 0xab,
        0xd5, 0xff, 0xff, 0x00, 0x6c, 0xd9, 0x02, 0x00, 0x06, 0x02, 0x00, 0x00,
        0x00, 0x3d, 0x26, 0x64, 0xc2, 0x19, 0x98, 0x00, 0x00, 0x00, 0x4d, 0xce,
        0x64, 0x49, 0xac, 0xcc, 0x00, 0x80, 0xff, 0x80, 0xff, 0x00, 0xb2, 0x00,
        0x04, 0x02, 0x04, 0x34, 0x22, 0xbd, 0x9b, 0x22, 0xb8, 0x53, 0x22, 0x28,
        0x2e, 0x28, 0xb5, 0xef, 0x28, 0xbb, 0x37, 0x34, 0x3a, 0xb8, 0x53, 0x3a,
        0xbd, 0x9b, 0x3a, 0x40, 0x2e, 0x40, 0xbb, 0x37, 0x40, 0xb5, 0xef, 0x02,
        0x08, 0xbe, 0xb6, 0xb4, 0xac, 0xc1, 0x46, 0xb4, 0xac, 0xbc, 0x25, 0xb4,
        0xac, 0xb8, 0x09, 0xb7, 0x35, 0xb9, 0xcf, 0xb5, 0xa0, 0xb8, 0x05, 0xbe,
        0xb6, 0x35, 0xc2, 0xe5, 0x35, 0xbe, 0xb6, 0x35, 0xc5, 0x68, 0xb8, 0x09,
        0xc6, 0x36, 0xb8, 0x09, 0xc6, 0x36, 0xb9, 0xcf, 0xc7, 0xca, 0xbe, 0xb6,
        0xc8, 0xc1, 0xbc, 0x25, 0xc8, 0xc1, 0xc1, 0xb3, 0xc8, 0xc1, 0xc6, 0x3c,
        0xc5, 0x5b, 0xc4, 0x65, 0xc7, 0x70, 0xc6, 0x3e, 0xbe, 0xb6, 0xc2, 0x0f,
        0xba, 0x87, 0xc2, 0x0f, 0xbe, 0xb6, 0xc2, 0x0f, 0xb8, 0x05, 0xc5, 0x64,
        0xb7, 0x37, 0xc5, 0x64, 0xb7, 0x37, 0xc3, 0x9e, 0xb5, 0xa2, 0x02, 0x04,
        0xb8, 0x09, 0xb7, 0x35, 0xb8, 0x05, 0xbe, 0xb6, 0xb5, 0xf8, 0xb9, 0x0c,
        0xb4, 0xac, 0xbe, 0xb6, 0xb4, 0xac, 0xbb, 0xba, 0xb4, 0xac, 0xc1, 0xb1,
        0xb8, 0x09, 0xc6, 0x36, 0xb5, 0xf8, 0xc4, 0x5e, 0xb9, 0xcf, 0xc7, 0xca,
        0x35, 0xc2, 0xe5, 0x35, 0xc5, 0x68, 0x35, 0xbe, 0xb6, 0x02, 0x04, 0x4d,
        0x51, 0xc4, 0xf2, 0xbf, 0x04, 0x53, 0x4e, 0xc8, 0xc1, 0xbe, 0x58, 0xc8,
        0xc1, 0xc1, 0x55, 0xc8, 0xc1, 0xbb, 0x5d, 0xc5, 0x64, 0xb6, 0xd9, 0xc7,
        0x75, 0xb8, 0xb0, 0xc3, 0x9e, 0xb5, 0x44, 0xc2, 0x0f, 0xba, 0x29, 0xc2,
        0x0f, 0xb7, 0xa6, 0xc2, 0x0f, 0xbe, 0x58, 0x04, 0x0a, 0x00, 0x01, 0x00,
        0x12, 0x42, 0x19, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x19,
        0x98, 0xc6, 0x19, 0x93, 0x44, 0x19, 0xa2, 0x01, 0x17, 0x84, 0x00, 0x04,
        0x0a, 0x01, 0x01, 0x00, 0x12, 0x42, 0x19, 0x98, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x42, 0x19, 0x98, 0xc7, 0x26, 0x5f, 0x28, 0x96, 0xf9, 0x01,
        0x17, 0x83, 0x00, 0x04, 0x0a, 0x02, 0x01, 0x01, 0x00, 0x0a, 0x03, 0x02,
        0x02, 0x03, 0x00
};

// #pragma mark - PageIconView


class URLInputGroup::PageIconView : public BView {
public:
        PageIconView()
                :
                BView("page icon view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
                fIcon(NULL),
                fClickPoint(-1, 0),
                fPageIconSet(false)
        {
                SetDrawingMode(B_OP_ALPHA);
                SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
                SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
        }

        ~PageIconView()
        {
                delete fIcon;
        }

        virtual void Draw(BRect updateRect)
        {
                BRect bounds(Bounds());
                BRect iconBounds(0, 0, 15, 15);
                iconBounds.OffsetTo(
                        floorf((bounds.left + bounds.right
                                - (iconBounds.left + iconBounds.right)) / 2 + 0.5f),
                        floorf((bounds.top + bounds.bottom
                                - (iconBounds.top + iconBounds.bottom)) / 2 + 0.5f));
                DrawBitmap(fIcon, fIcon->Bounds(), iconBounds,
                        B_FILTER_BITMAP_BILINEAR);
        }

        virtual BSize MinSize()
        {
                return BSize(18, 18);
        }

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

        virtual BSize PreferredSize()
        {
                return MinSize();
        }

        void MouseDown(BPoint where)
        {
                int32 buttons;
                if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) == B_OK) {
                        if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
                                // Memorize click point for dragging
                                fClickPoint = where;
                        }
                }
                return;
        }

        void MouseUp(BPoint where)
        {
                fClickPoint.x = -1;
        }

        virtual void MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
        {
                if (dragMessage != NULL)
                        return;

                if (fClickPoint.x >= 0
                        && (fabs(where.x - fClickPoint.x) > 4 || fabs(where.y - fClickPoint.y) > 4)) {
                        // Start dragging
                        BPoint offset = fClickPoint - Frame().LeftTop();

                        const char* url = static_cast<URLInputGroup*>(Parent())->Text();
                        const char* title =
                                static_cast<BWebWindow*>(Window())->CurrentWebView()->MainFrameTitle();

                        // Validate the file name to be set for the clipping if user drags to Tracker.
                        BString fileName(title);
                        if (fileName.Length() == 0) {
                                fileName = url;
                                int32 leafPos = fileName.FindLast('/');
                                if (leafPos >= 0)
                                        fileName.Remove(0, leafPos + 1);
                        }
                        fileName.ReplaceAll('/', '-');
                        fileName.Truncate(B_FILE_NAME_LENGTH - 1);

                        BBitmap miniIcon(BRect(0, 0, 15, 15), B_BITMAP_NO_SERVER_LINK,
                                B_CMAP8);
                        miniIcon.ImportBits(fIcon);
                        // TODO:  obtain and send the large icon in addition to the mini icon.
                        // Currently PageUserData does not provide a function that returns this.

                        BMessage drag(B_SIMPLE_DATA);
                        drag.AddInt32("be:actions", B_COPY_TARGET);
                        drag.AddString("be:clip_name", fileName.String());
                        drag.AddString("be:filetypes", "application/x-vnd.Be-bookmark");
                        // Support the "Passing Data via File" protocol
                        drag.AddString("be:types", B_FILE_MIME_TYPE);
                        BMessage data(B_SIMPLE_DATA);
                        data.AddString("url", url);
                        data.AddString("title", title);
                                // The title may differ from the validated filename
                        if (fPageIconSet == true) {
                                // Don't bother sending the placeholder web icon, if that is all we have.
                                data.AddData("miniIcon", B_COLOR_8_BIT_TYPE, &miniIcon, sizeof(miniIcon));
                        }
                        drag.AddMessage("be:originator-data", &data);

                        BBitmap* iconClone = new BBitmap(fIcon);
                                // Needed because DragMessage will delete the bitmap when it's done.

                        DragMessage(&drag, iconClone, B_OP_ALPHA, offset);
                }
                return;
        }

        void SetIcon(const BBitmap* icon)
        {
                delete fIcon;
                if (icon) {
                        fIcon = new BBitmap(icon);
                        fPageIconSet = true;
                } else {
                        fIcon = new BBitmap(BRect(0, 0, 15, 15), B_RGB32);
                        BIconUtils::GetVectorIcon(kPlaceholderIcon,
                                sizeof(kPlaceholderIcon), fIcon);
                        fPageIconSet = false;
                }
                Invalidate();
        }

private:
        BBitmap* fIcon;
        BPoint fClickPoint;
        bool fPageIconSet;
};


// #pragma mark - URLInputGroup


URLInputGroup::URLInputGroup(BMessage* goMessage)
        :
        BGroupView(B_HORIZONTAL, 0.0),
        fWindowActive(false),
        fURLLocked(false)
{
        GroupLayout()->SetInsets(2, 2, 2, 2);

        fIconView = new PageIconView();
        GroupLayout()->AddView(fIconView, 0.0f);

        fTextView = new URLTextView(this);
        AddChild(fTextView);

        AddChild(new BSeparatorView(B_VERTICAL, B_PLAIN_BORDER));

// TODO: Fix in Haiku, no in-built support for archived BBitmaps from
// resources?
//      fGoButton = new BitmapButton("kActionGo", NULL);
        fGoButton = new BBitmapButton(kGoBitmapBits, kGoBitmapWidth,
                kGoBitmapHeight, kGoBitmapFormat, goMessage);
        GroupLayout()->AddView(fGoButton, 0.0f);

        SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
        SetLowColor(ViewColor());
        SetViewColor(fTextView->ViewColor());

        SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
                B_ALIGN_VERTICAL_CENTER));

}


URLInputGroup::~URLInputGroup()
{
}


void
URLInputGroup::AttachedToWindow()
{
        BGroupView::AttachedToWindow();
        fWindowActive = Window()->IsActive();
}


void
URLInputGroup::WindowActivated(bool active)
{
        BGroupView::WindowActivated(active);
        if (fWindowActive != active) {
                fWindowActive = active;
                Invalidate();
        }
}


void
URLInputGroup::Draw(BRect updateRect)
{
        BRect bounds(Bounds());
        rgb_color base(LowColor());
        uint32 flags = 0;
        if (fWindowActive && fTextView->IsFocus())
                flags |= BControlLook::B_FOCUSED;
        be_control_look->DrawTextControlBorder(this, bounds, updateRect, base,
                flags);
}


void
URLInputGroup::MakeFocus(bool focus)
{
        // Forward this to the text view, we never accept focus ourselves.
        fTextView->MakeFocus(focus);
}


BTextView*
URLInputGroup::TextView() const
{
        return fTextView;
}


void
URLInputGroup::SetText(const char* text)
{
        // Ignore setting the text, if the input is locked.
        if (fURLLocked)
                return;

        if (!text || !Text() || strcmp(Text(), text) != 0) {
                fTextView->SetUpdateAutoCompleterChoices(false);
                fTextView->SetText(text);
                fTextView->SetUpdateAutoCompleterChoices(true);
        }
}


const char*
URLInputGroup::Text() const
{
        return fTextView->Text();
}


BButton*
URLInputGroup::GoButton() const
{
        return fGoButton;
}


void
URLInputGroup::SetPageIcon(const BBitmap* icon)
{
        fIconView->SetIcon(icon);
}


bool
URLInputGroup::IsURLInputLocked() const
{
        return fURLLocked;
}


void
URLInputGroup::LockURLInput(bool lock)
{
        fURLLocked = lock;
}