root/src/apps/terminal/AppearPrefView.cpp
/*
 * Copyright 2001-2015, Haiku, Inc.
 * Copyright 2003-2004 Kian Duffy, myob@users.sourceforge.net
 * Parts Copyright 1998-1999 Kazuho Okui and Takashi Murai.
 * All rights reserved. Distributed under the terms of the MIT license.
 */


#include "AppearPrefView.h"

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

#include <Button.h>
#include <Catalog.h>
#include <CharacterSet.h>
#include <CharacterSetRoster.h>
#include <CheckBox.h>
#include <ColorControl.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <Menu.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <TextControl.h>
#include <View.h>

#include "Colors.h"
#include "Globals.h"
#include "PrefHandler.h"
#include "TermConst.h"
#include "TermWindow.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Terminal AppearancePrefView"


// #pragma mark -


AppearancePrefView::AppearancePrefView(const char* name,
                const BMessenger& messenger)
        :
        BGroupView(name, B_VERTICAL, 5),
        fTerminalMessenger(messenger)
{
        fBlinkCursor = new BCheckBox(
                B_TRANSLATE("Blinking cursor"),
                        new BMessage(MSG_BLINK_CURSOR_CHANGED));

        fAllowBold = new BCheckBox(
                B_TRANSLATE("Allow bold text"),
                        new BMessage(MSG_ALLOW_BOLD_CHANGED));

        fUseOptionAsMetaKey = new BCheckBox(
                B_TRANSLATE("Use left Option as Meta key"),
                        new BMessage(MSG_USE_OPTION_AS_META_CHANGED));

        fWarnOnExit = new BCheckBox(
                B_TRANSLATE("Confirm exit if active programs exist"),
                        new BMessage(MSG_WARN_ON_EXIT_CHANGED));

        BMenu* fontMenu = _MakeFontMenu(MSG_HALF_FONT_CHANGED,
                PrefHandler::Default()->getString(PREF_HALF_FONT_FAMILY),
                PrefHandler::Default()->getString(PREF_HALF_FONT_STYLE));
        fFontField = new BMenuField(B_TRANSLATE("Font:"), fontMenu);

        BMenu* sizeMenu = new (std::nothrow) BPopUpMenu(
                B_TRANSLATE_COMMENT("Custom", "Window size"));
        if (sizeMenu != NULL) {
                TermWindow::MakeWindowSizeMenu(sizeMenu);
                sizeMenu->SetLabelFromMarked(true);
        }
        fWindowSizeField = new BMenuField(B_TRANSLATE("Window size:"), sizeMenu);

        BMenu* encodingMenu = new (std::nothrow) BPopUpMenu("Text encoding");
        if (encodingMenu != NULL) {
                TermWindow::MakeEncodingMenu(encodingMenu);
                encodingMenu->SetLabelFromMarked(true);
        }
        fEncodingField = new BMenuField(B_TRANSLATE("Encoding:"), encodingMenu);

        fTabTitle = new BTextControl("tabTitle", B_TRANSLATE("Tab title:"), "",
                NULL);
        fTabTitle->SetModificationMessage(
                new BMessage(MSG_TAB_TITLE_SETTING_CHANGED));
        fTabTitle->SetToolTip(BString(B_TRANSLATE(
                "The pattern specifying the tab titles. The following placeholders\n"
                "can be used:")) << "\n" << kToolTipSetTabTitlePlaceholders
                << "\n" << kToolTipCommonTitlePlaceholders);

        fWindowTitle = new BTextControl("windowTitle", B_TRANSLATE("Window title:"),
                "", NULL);
        fWindowTitle->SetModificationMessage(
                new BMessage(MSG_WINDOW_TITLE_SETTING_CHANGED));
        fWindowTitle->SetToolTip(BString(B_TRANSLATE(
                "The pattern specifying the window titles. The following placeholders\n"
                "can be used:")) << "\n" << kToolTipSetWindowTitlePlaceholders
                << "\n" << kToolTipCommonTitlePlaceholders);

        BLayoutBuilder::Group<>(this)
                .SetInsets(5, 5, 5, 5)
                .AddGrid(5, 5)
                        .Add(fTabTitle->CreateLabelLayoutItem(), 0, 0)
                        .Add(fTabTitle->CreateTextViewLayoutItem(), 1, 0)
                        .Add(fWindowTitle->CreateLabelLayoutItem(), 0, 1)
                        .Add(fWindowTitle->CreateTextViewLayoutItem(), 1, 1)
                        .Add(fWindowSizeField->CreateLabelLayoutItem(), 0, 2)
                        .Add(fWindowSizeField->CreateMenuBarLayoutItem(), 1, 2)
                        .Add(fFontField->CreateLabelLayoutItem(), 0, 3)
                        .Add(fFontField->CreateMenuBarLayoutItem(), 1, 3)
                        .Add(fEncodingField->CreateLabelLayoutItem(), 0, 4)
                        .Add(fEncodingField->CreateMenuBarLayoutItem(), 1, 4)
                        .End()
                .AddGlue()
                .Add(fBlinkCursor)
                .Add(fAllowBold)
                .Add(fUseOptionAsMetaKey)
                .Add(fWarnOnExit);

        fTabTitle->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
        fWindowTitle->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
        fFontField->SetAlignment(B_ALIGN_RIGHT);
        fWindowSizeField->SetAlignment(B_ALIGN_RIGHT);
        fEncodingField->SetAlignment(B_ALIGN_RIGHT);

        Revert();
}


void
AppearancePrefView::Revert()
{
        PrefHandler* pref = PrefHandler::Default();

        fTabTitle->SetText(pref->getString(PREF_TAB_TITLE));
        fWindowTitle->SetText(pref->getString(PREF_WINDOW_TITLE));

        fBlinkCursor->SetValue(pref->getBool(PREF_BLINK_CURSOR));
        fAllowBold->SetValue(pref->getBool(PREF_ALLOW_BOLD));
        fUseOptionAsMetaKey->SetValue(pref->getBool(PREF_USE_OPTION_AS_META));
        fWarnOnExit->SetValue(pref->getBool(PREF_WARN_ON_EXIT));

        _SetEncoding(pref->getString(PREF_TEXT_ENCODING));
        _SetWindowSize(pref->getInt32(PREF_ROWS), pref->getInt32(PREF_COLS));

        const char* family = pref->getString(PREF_HALF_FONT_FAMILY);
        const char* style = pref->getString(PREF_HALF_FONT_STYLE);
        const char* size = pref->getString(PREF_HALF_FONT_SIZE);

        _MarkSelectedFont(family, style, size);
}


void
AppearancePrefView::AttachedToWindow()
{
        fTabTitle->SetTarget(this);
        fWindowTitle->SetTarget(this);
        fBlinkCursor->SetTarget(this);
        fAllowBold->SetTarget(this);
        fUseOptionAsMetaKey->SetTarget(this);
        fWarnOnExit->SetTarget(this);

        fFontField->Menu()->SetTargetForItems(this);
        for (int32 i = 0; i < fFontField->Menu()->CountItems(); i++) {
                BMenu* fontSizeMenu = fFontField->Menu()->SubmenuAt(i);
                if (fontSizeMenu == NULL)
                        continue;

                fontSizeMenu->SetTargetForItems(this);
        }

        fWindowSizeField->Menu()->SetTargetForItems(this);
        fEncodingField->Menu()->SetTargetForItems(this);
}


void

AppearancePrefView::MessageReceived(BMessage* msg)
{
        bool modified = false;

        switch (msg->what) {
                case MSG_HALF_FONT_CHANGED:
                {
                        const char* family = NULL;
                        const char* style = NULL;
                        const char* size = NULL;
                        if (msg->FindString("font_family", &family) != B_OK
                                || msg->FindString("font_style", &style) != B_OK
                                || msg->FindString("font_size", &size) != B_OK) {
                                break;
                        }

                        PrefHandler* pref = PrefHandler::Default();
                        const char* currentFamily
                                = pref->getString(PREF_HALF_FONT_FAMILY);
                        const char* currentStyle
                                = pref->getString(PREF_HALF_FONT_STYLE);
                        const char* currentSize
                                = pref->getString(PREF_HALF_FONT_SIZE);

                        if (currentFamily == NULL || strcmp(currentFamily, family) != 0
                                || currentStyle == NULL || strcmp(currentStyle, style) != 0
                                || currentSize == NULL || strcmp(currentSize, size) != 0) {
                                pref->setString(PREF_HALF_FONT_FAMILY, family);
                                pref->setString(PREF_HALF_FONT_STYLE, style);
                                pref->setString(PREF_HALF_FONT_SIZE, size);
                                _MarkSelectedFont(family, style, size);
                                modified = true;
                        }
                        break;
                }

                case MSG_COLS_CHANGED:
                {
                        int rows = msg->FindInt32("rows");
                        int columns = msg->FindInt32("columns");
                        _SetWindowSize(rows, columns);
                        PrefHandler* handler = PrefHandler::Default();
                        if (handler->getInt32(PREF_ROWS) != rows) {
                                PrefHandler::Default()->setInt32(PREF_ROWS, rows);
                                modified = true;
                        }
                        if (handler->getInt32(PREF_COLS) != columns) {
                                PrefHandler::Default()->setInt32(PREF_COLS, columns);
                                modified = true;
                        }

                        break;
                }

                case MSG_BLINK_CURSOR_CHANGED:
                        if (PrefHandler::Default()->getBool(PREF_BLINK_CURSOR)
                                != fBlinkCursor->Value()) {
                                        PrefHandler::Default()->setBool(PREF_BLINK_CURSOR,
                                                fBlinkCursor->Value());
                                        modified = true;
                        }
                        break;

                case MSG_ALLOW_BOLD_CHANGED:
                        if (PrefHandler::Default()->getBool(PREF_ALLOW_BOLD)
                                != fAllowBold->Value()) {
                                        PrefHandler::Default()->setBool(PREF_ALLOW_BOLD,
                                                fAllowBold->Value());
                                        modified = true;
                        }
                        break;

                case MSG_USE_OPTION_AS_META_CHANGED:
                        if (PrefHandler::Default()->getBool(PREF_USE_OPTION_AS_META)
                                != fUseOptionAsMetaKey->Value()) {
                                        PrefHandler::Default()->setBool(PREF_USE_OPTION_AS_META,
                                                fUseOptionAsMetaKey->Value());
                                        modified = true;
                        }
                        break;

                case MSG_WARN_ON_EXIT_CHANGED:
                        if (PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT)
                                != fWarnOnExit->Value()) {
                                        PrefHandler::Default()->setBool(PREF_WARN_ON_EXIT,
                                                fWarnOnExit->Value());
                                        modified = true;
                        }
                        break;

                case MSG_TAB_TITLE_SETTING_CHANGED:
                {
                        BString oldValue(PrefHandler::Default()->getString(PREF_TAB_TITLE));
                        if (oldValue != fTabTitle->Text()) {
                                PrefHandler::Default()->setString(PREF_TAB_TITLE,
                                        fTabTitle->Text());
                                modified = true;
                        }
                        break;
                }

                case MSG_WINDOW_TITLE_SETTING_CHANGED:
                {
                        BString oldValue(PrefHandler::Default()->getString(
                                PREF_WINDOW_TITLE));
                        if (oldValue != fWindowTitle->Text()) {
                                PrefHandler::Default()->setString(PREF_WINDOW_TITLE,
                                        fWindowTitle->Text());
                                modified = true;
                        }
                        break;
                }

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

        if (modified) {
                fTerminalMessenger.SendMessage(msg);

                BMessenger messenger(this);
                messenger.SendMessage(MSG_PREF_MODIFIED);
        }
}


void
AppearancePrefView::_SetEncoding(const char* name)
{
        const BPrivate::BCharacterSet* charset
                = BPrivate::BCharacterSetRoster::FindCharacterSetByName(name);
        if (charset == NULL)
                return;
        int code = charset->GetConversionID();
        for (int32 i = 0; i < fEncodingField->Menu()->CountItems(); i++) {
                BMenuItem* item = fEncodingField->Menu()->ItemAt(i);
                BMessage* msg = item->Message();
                if (msg->FindInt32("op") == code) {
                        item->SetMarked(true);
                        break;
                }
        }
}


void
AppearancePrefView::_SetWindowSize(int rows, int cols)
{
        for (int32 i = 0; i < fWindowSizeField->Menu()->CountItems(); i++) {
                BMenuItem* item = fWindowSizeField->Menu()->ItemAt(i);
                BMessage* msg = item->Message();
                if (msg->FindInt32("rows") == rows && msg->FindInt32("columns") == cols) {
                        item->SetMarked(true);
                        break;
                }
        }
}


/*static*/ BMenu*
AppearancePrefView::_MakeFontMenu(uint32 command,
        const char* defaultFamily, const char* defaultStyle)
{
        BPopUpMenu* menu = new BPopUpMenu("");
        int32 numFamilies = count_font_families();
        for (int32 i = 0; i < numFamilies; i++) {
                font_family family;
                uint32 flags;
                if (get_font_family(i, &family, &flags) == B_OK) {
                        BFont font;
                        font_style style;
                        int32 numStyles = count_font_styles(family);
                        for (int32 j = 0; j < numStyles; j++) {
                                if (get_font_style(family, j, &style) == B_OK) {
                                        font.SetFamilyAndStyle(family, style);
                                        if (IsFontUsable(font)) {
                                                BMessage* message = new BMessage(command);
                                                const char* size
                                                        = PrefHandler::Default()->getString(PREF_HALF_FONT_SIZE);
                                                message->AddString("font_family", family);
                                                message->AddString("font_style", style);
                                                message->AddString("font_size", size);
                                                char fontMenuLabel[134];
                                                snprintf(fontMenuLabel, sizeof(fontMenuLabel),
                                                        "%s - %s", family, style);
                                                BMenu* fontSizeMenu = _MakeFontSizeMenu(fontMenuLabel,
                                                        MSG_HALF_FONT_CHANGED, family, style, size);
                                                BMenuItem* item = new BMenuItem(fontSizeMenu, message);
                                                menu->AddItem(item);
                                                if (strcmp(defaultFamily, family) == 0
                                                        && strcmp(defaultStyle, style) == 0)
                                                        item->SetMarked(true);
                                        }
                                }
                        }
                }
        }

        if (menu->FindMarked() == NULL)
                menu->ItemAt(0)->SetMarked(true);

        return menu;
}


/*static*/ BMenu*
AppearancePrefView::_MakeFontSizeMenu(const char* label, uint32 command,
        const char* family, const char* style, const char* size)
{
        BMenu* menu = new BMenu(label);
        menu->SetRadioMode(true);
        menu->SetLabelFromMarked(false);

        int32 sizes[] = {
                8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28, 32, 36, 0
        };

        bool found = false;

        for (uint32 i = 0; sizes[i]; i++) {
                BString fontSize;
                fontSize << sizes[i];
                BMessage* message = new BMessage(command);
                message->AddString("font_family", family);
                message->AddString("font_style", style);
                message->AddString("font_size", fontSize.String());
                BMenuItem* item = new BMenuItem(fontSize.String(), message);
                menu->AddItem(item);
                if (sizes[i] == atoi(size)) {
                        item->SetMarked(true);
                        found = true;
                }
        }

        if (!found) {
                for (uint32 i = 0; sizes[i]; i++) {
                        if (sizes[i] > atoi(size)) {
                                BMessage* message = new BMessage(command);
                                message->AddString("font_family", family);
                                message->AddString("font_style", style);
                                message->AddString("font_size", size);
                                BMenuItem* item = new BMenuItem(size, message);
                                item->SetMarked(true);
                                menu->AddItem(item, i);
                                break;
                        }
                }
        }

        return menu;
}


/*static*/ BPopUpMenu*
AppearancePrefView::_MakeMenu(uint32 msg, const char** items,
        const char* defaultItemName)
{
        BPopUpMenu* menu = new BPopUpMenu("");

        while (*items) {
                if (strcmp((*items), "") == 0)
                        menu->AddSeparatorItem();
                else {
                        BMessage* message = new BMessage(msg);
                        message->AddString("label", *items);
                        BMenuItem* item = new BMenuItem(B_TRANSLATE(*items), message);
                        menu->AddItem(item);
                        if (strcmp(*items, defaultItemName) == 0)
                                item->SetMarked(true);
                }

                items++;
        }

        return menu;
}


void
AppearancePrefView::_MarkSelectedFont(const char* family, const char* style,
        const char* size)
{
        char fontMenuLabel[134];
        snprintf(fontMenuLabel, sizeof(fontMenuLabel), "%s - %s", family, style);

        // mark the selected font
        BMenuItem* selectedFont = fFontField->Menu()->FindItem(fontMenuLabel);
        if (selectedFont != NULL)
                selectedFont->SetMarked(true);

        // mark the selected font size on all font menus
        for (int32 i = 0; i < fFontField->Menu()->CountItems(); i++) {
                BMenu* fontSizeMenu = fFontField->Menu()->SubmenuAt(i);
                if (fontSizeMenu == NULL)
                        continue;

                BMenuItem* item = fontSizeMenu->FindItem(size);
                if (item != NULL)
                        item->SetMarked(true);
        }
}