root/src/apps/webpositive/support/FontSelectionView.cpp
/*
 * Copyright 2001-2010, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Mark Hogben
 *              DarkWyrm <bpmagic@columbus.rr.com>
 *              Axel Dörfler, axeld@pinc-software.de
 *              Philippe Saint-Pierre, stpere@gmail.com
 *              Stephan Aßmus <superstippi@gmx.de>
 */

#include "FontSelectionView.h"

#include "private/interface/FontPrivate.h"

#include <Box.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <GroupLayoutBuilder.h>
#include <LayoutItem.h>
#include <Locale.h>
#include <Looper.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <String.h>
#include <TextView.h>

#include <stdio.h>

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Font Selection view"


static const float kMinSize = 8.0;
static const float kMaxSize = 18.0;

static const char* kPreviewText = B_TRANSLATE_COMMENT(
        "The quick brown fox jumps over the lazy dog.",
        "Don't translate this literally ! Use a phrase showing all "
        "chars from A to Z.");

static const int32 kMsgSetFamily = 'fmly';
static const int32 kMsgSetStyle = 'styl';
static const int32 kMsgSetSize = 'size';


//      #pragma mark -


FontSelectionView::FontSelectionView(const char* name, const char* label,
                bool separateStyles, const BFont* currentFont)
        :
        BHandler(name),
        fMessage(NULL),
        fTarget(NULL)
{
        if (currentFont == NULL)
                fCurrentFont = _DefaultFont();
        else
                fCurrentFont = *currentFont;

        fSavedFont = fCurrentFont;

        fSizesMenu = new BPopUpMenu("size menu");
        fFontsMenu = new BPopUpMenu("font menu");

        // font menu
        fFontsMenuField = new BMenuField("fonts", label, fFontsMenu, B_WILL_DRAW);
        fFontsMenuField->SetAlignment(B_ALIGN_RIGHT);

        // styles menu, if desired
        if (separateStyles) {
                fStylesMenu = new BPopUpMenu("styles menu");
                fStylesMenuField = new BMenuField("styles", B_TRANSLATE("Style:"),
                        fStylesMenu, B_WILL_DRAW);
        } else {
                fStylesMenu = NULL;
                fStylesMenuField = NULL;
        }

        // size menu
        fSizesMenuField = new BMenuField("size", B_TRANSLATE("Size:"), fSizesMenu,
                B_WILL_DRAW);
        fSizesMenuField->SetAlignment(B_ALIGN_RIGHT);

        // preview
        // A string view would be enough if only it handled word-wrap.
        fPreviewTextView = new BTextView("preview text");
        fPreviewTextView->SetFontAndColor(&fCurrentFont);
        fPreviewTextView->SetText(kPreviewText);
        fPreviewTextView->MakeResizable(false);
        fPreviewTextView->SetWordWrap(true);
        fPreviewTextView->MakeEditable(false);
        fPreviewTextView->MakeSelectable(false);
        fPreviewTextView->SetInsets(0, 0, 0, 0);
        fPreviewTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        fPreviewTextView->SetHighUIColor(B_PANEL_TEXT_COLOR);

        // determine initial line count using fCurrentFont
        fPreviewTextWidth = be_control_look->DefaultLabelSpacing() * 58.0f;
        float lineCount = ceilf(fCurrentFont.StringWidth(kPreviewText) / fPreviewTextWidth);
        fPreviewTextView->SetExplicitSize(
                BSize(fPreviewTextWidth, fPreviewTextView->LineHeight(0) * lineCount));

        // box around preview
        fPreviewBox = new BBox("preview box", B_WILL_DRAW | B_FRAME_EVENTS);
        fPreviewBox->AddChild(BGroupLayoutBuilder(B_VERTICAL)
                .AddGroup(B_HORIZONTAL, 0)
                        .Add(fPreviewTextView)
                        .AddGlue()
                        .End()
                .SetInsets(B_USE_SMALL_SPACING, B_USE_SMALL_SPACING,
                        B_USE_SMALL_SPACING, B_USE_SMALL_SPACING)
                .TopView()
        );
        _UpdateFontPreview();
}


FontSelectionView::~FontSelectionView()
{
        // Some controls may not have been attached...
        if (!fPreviewTextView->Window())
                delete fPreviewTextView;
        if (!fSizesMenuField->Window())
                delete fSizesMenuField;
        if (fStylesMenuField && !fStylesMenuField->Window())
                delete fStylesMenuField;
        if (!fFontsMenuField->Window())
                delete fFontsMenuField;

        delete fMessage;
}


void
FontSelectionView::AttachedToLooper()
{
        _BuildSizesMenu();
        UpdateFontsMenu();
}


void
FontSelectionView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case B_COLORS_UPDATED:
                {
                        if (message->HasColor(ui_color_name(B_PANEL_TEXT_COLOR))) {
                                rgb_color textColor;
                                if (message->FindColor(ui_color_name(B_PANEL_TEXT_COLOR), &textColor) == B_OK)
                                        fPreviewTextView->SetFontAndColor(&fCurrentFont, B_FONT_ALL, &textColor);
                        }
                        break;
                }

                case kMsgSetSize:
                {
                        int32 size;
                        if (message->FindInt32("size", &size) != B_OK
                                || size == fCurrentFont.Size())
                                break;

                        fCurrentFont.SetSize(size);
                        _UpdateFontPreview();
                        _Invoke();
                        break;
                }

                case kMsgSetFamily:
                {
                        const char* family;
                        if (message->FindString("family", &family) != B_OK)
                                break;

                        font_style style;
                        fCurrentFont.GetFamilyAndStyle(NULL, &style);

                        BMenuItem* familyItem = fFontsMenu->FindItem(family);
                        if (familyItem != NULL) {
                                _SelectCurrentFont(false);

                                BMenuItem* styleItem;
                                if (fStylesMenuField != NULL)
                                        styleItem = fStylesMenuField->Menu()->FindMarked();
                                else {
                                        styleItem = familyItem->Submenu()->FindItem(style);
                                        if (styleItem == NULL)
                                                styleItem = familyItem->Submenu()->ItemAt(0);
                                }

                                if (styleItem != NULL) {
                                        styleItem->SetMarked(true);
                                        fCurrentFont.SetFamilyAndStyle(family, styleItem->Label());
                                        _UpdateFontPreview();
                                }
                                if (fStylesMenuField != NULL)
                                        _AddStylesToMenu(fCurrentFont, fStylesMenuField->Menu());
                        }

                        _Invoke();
                        break;
                }

                case kMsgSetStyle:
                {
                        const char* family;
                        const char* style;
                        if (message->FindString("family", &family) != B_OK
                                || message->FindString("style", &style) != B_OK)
                                break;

                        BMenuItem *familyItem = fFontsMenu->FindItem(family);
                        if (!familyItem)
                                break;

                        _SelectCurrentFont(false);
                        familyItem->SetMarked(true);

                        fCurrentFont.SetFamilyAndStyle(family, style);
                        _UpdateFontPreview();
                        _Invoke();
                        break;
                }

                default:
                        BHandler::MessageReceived(message);
        }
}


void
FontSelectionView::SetMessage(BMessage* message)
{
        delete fMessage;
        fMessage = message;
}


void
FontSelectionView::SetTarget(BHandler* target)
{
        fTarget = target;
}


// #pragma mark -


void
FontSelectionView::SetFont(const BFont& font, float size)
{
        BFont resizedFont(font);
        resizedFont.SetSize(size);
        SetFont(resizedFont);
}


void
FontSelectionView::SetFont(const BFont& font)
{
        if (font == fCurrentFont && font == fSavedFont)
                return;

        _SelectCurrentFont(false);
        fSavedFont = fCurrentFont = font;
        _UpdateFontPreview();

        _SelectCurrentFont(true);
        _SelectCurrentSize(true);
}


void
FontSelectionView::SetSize(float size)
{
        SetFont(fCurrentFont, size);
}


const BFont&
FontSelectionView::Font() const
{
        return fCurrentFont;
}


void
FontSelectionView::SetDefaults()
{
        BFont defaultFont = _DefaultFont();
        if (defaultFont == fCurrentFont)
                return;

        _SelectCurrentFont(false);

        fCurrentFont = defaultFont;
        _UpdateFontPreview();

        _SelectCurrentFont(true);
        _SelectCurrentSize(true);
}


void
FontSelectionView::Revert()
{
        if (!IsRevertable())
                return;

        _SelectCurrentFont(false);

        fCurrentFont = fSavedFont;
        _UpdateFontPreview();

        _SelectCurrentFont(true);
        _SelectCurrentSize(true);
}


bool
FontSelectionView::IsDefaultable()
{
        return fCurrentFont != _DefaultFont();
}


bool
FontSelectionView::IsRevertable()
{
        return fCurrentFont != fSavedFont;
}


void
FontSelectionView::UpdateFontsMenu()
{
        int32 numFamilies = count_font_families();

        fFontsMenu->RemoveItems(0, fFontsMenu->CountItems(), true);

        BFont font = fCurrentFont;

        font_family currentFamily;
        font_style currentStyle;
        font.GetFamilyAndStyle(&currentFamily, &currentStyle);

        for (int32 i = 0; i < numFamilies; i++) {
                font_family family;
                uint32 flags;
                if (get_font_family(i, &family, &flags) != B_OK)
                        continue;

                // if we're setting the fixed font, we only want to show fixed and
                // full-and-half-fixed fonts
                if (strcmp(Name(), "fixed") == 0
                        && (flags & (B_IS_FIXED | B_PRIVATE_FONT_IS_FULL_AND_HALF_FIXED)) == 0) {
                        continue;
                }

                font.SetFamilyAndFace(family, B_REGULAR_FACE);

                BMessage* message = new BMessage(kMsgSetFamily);
                message->AddString("family", family);
                message->AddString("name", Name());

                BMenuItem* familyItem;
                if (fStylesMenuField != NULL) {
                        familyItem = new BMenuItem(family, message);
                } else {
                        // Each family item has a submenu with all styles for that font.
                        BMenu* stylesMenu = new BMenu(family);
                        _AddStylesToMenu(font, stylesMenu);
                        familyItem = new BMenuItem(stylesMenu, message);
                }

                familyItem->SetMarked(strcmp(family, currentFamily) == 0);
                fFontsMenu->AddItem(familyItem);
                familyItem->SetTarget(this);
        }

        // Separate styles menu for only the current font.
        if (fStylesMenuField != NULL)
                _AddStylesToMenu(fCurrentFont, fStylesMenuField->Menu());
}


// #pragma mark - private


BLayoutItem*
FontSelectionView::CreateSizesLabelLayoutItem()
{
        return fSizesMenuField->CreateLabelLayoutItem();
}


BLayoutItem*
FontSelectionView::CreateSizesMenuBarLayoutItem()
{
        return fSizesMenuField->CreateMenuBarLayoutItem();
}


BLayoutItem*
FontSelectionView::CreateFontsLabelLayoutItem()
{
        return fFontsMenuField->CreateLabelLayoutItem();
}


BLayoutItem*
FontSelectionView::CreateFontsMenuBarLayoutItem()
{
        return fFontsMenuField->CreateMenuBarLayoutItem();
}


BLayoutItem*
FontSelectionView::CreateStylesLabelLayoutItem()
{
        if (fStylesMenuField)
                return fStylesMenuField->CreateLabelLayoutItem();
        return NULL;
}


BLayoutItem*
FontSelectionView::CreateStylesMenuBarLayoutItem()
{
        if (fStylesMenuField)
                return fStylesMenuField->CreateMenuBarLayoutItem();
        return NULL;
}


BView*
FontSelectionView::PreviewBox() const
{
        return fPreviewBox;
}


// #pragma mark - private


void
FontSelectionView::_Invoke()
{
        if (fTarget != NULL && fTarget->Looper() != NULL && fMessage != NULL) {
                BMessage message(*fMessage);
                fTarget->Looper()->PostMessage(&message, fTarget);
        }
}


BFont
FontSelectionView::_DefaultFont() const
{
        if (strcmp(Name(), "bold") == 0)
                return *be_bold_font;
        if (strcmp(Name(), "fixed") == 0)
                return *be_fixed_font;
        else
                return *be_plain_font;
}


void
FontSelectionView::_SelectCurrentFont(bool select)
{
        font_family family;
        font_style style;
        fCurrentFont.GetFamilyAndStyle(&family, &style);

        BMenuItem *item = fFontsMenu->FindItem(family);
        if (item != NULL) {
                item->SetMarked(select);

                if (item->Submenu() != NULL) {
                        item = item->Submenu()->FindItem(style);
                        if (item != NULL)
                                item->SetMarked(select);
                }
        }
}


void
FontSelectionView::_SelectCurrentSize(bool select)
{
        char label[16];
        snprintf(label, sizeof(label), "%" B_PRId32, (int32)fCurrentFont.Size());

        BMenuItem* item = fSizesMenu->FindItem(label);
        if (item != NULL)
                item->SetMarked(select);
}


void
FontSelectionView::_UpdateFontPreview()
{
        fPreviewTextView->SetFontAndColor(&fCurrentFont);
        fPreviewTextView->SetExplicitSize(
                BSize(fPreviewTextWidth, fPreviewTextView->LineHeight(0) * fPreviewTextView->CountLines()));
}


void
FontSelectionView::_BuildSizesMenu()
{
        const int32 sizes[] = {7, 8, 9, 10, 11, 12, 13, 14, 18, 21, 24, 0};

        // build size menu
        for (int32 i = 0; sizes[i]; i++) {
                int32 size = sizes[i];
                if (size < kMinSize || size > kMaxSize)
                        continue;

                char label[32];
                snprintf(label, sizeof(label), "%" B_PRId32, size);

                BMessage* message = new BMessage(kMsgSetSize);
                message->AddInt32("size", size);
                message->AddString("name", Name());

                BMenuItem* item = new BMenuItem(label, message);
                if (size == fCurrentFont.Size())
                        item->SetMarked(true);

                fSizesMenu->AddItem(item);
                item->SetTarget(this);
        }
}


void
FontSelectionView::_AddStylesToMenu(const BFont& font, BMenu* stylesMenu) const
{
        stylesMenu->RemoveItems(0, stylesMenu->CountItems(), true);
        stylesMenu->SetRadioMode(true);

        font_family family;
        font_style style;
        font.GetFamilyAndStyle(&family, &style);
        BString currentStyle(style);

        int32 numStyles = count_font_styles(family);

        for (int32 j = 0; j < numStyles; j++) {
                if (get_font_style(family, j, &style) != B_OK)
                        continue;

                BMessage* message = new BMessage(kMsgSetStyle);
                message->AddString("family", (char*)family);
                message->AddString("style", (char*)style);

                BMenuItem* item = new BMenuItem(style, message);
                item->SetMarked(currentStyle == style);

                stylesMenu->AddItem(item);
                item->SetTarget(this);
        }
}