root/src/preferences/datatranslations/DataTranslationsWindow.cpp
/*
 * Copyright 2002-2017, Haiku, Inc.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *              Oliver Siebenmarck
 *              Andrew McCall, mccall@digitalparadise.co.uk
 *              Michael Wilber
 *              Maxime Simon
 */


#include "DataTranslationsWindow.h"

#include <algorithm>

#include <math.h>
#include <stdio.h>

#include <Alert.h>
#include <Alignment.h>
#include <Application.h>
#include <Bitmap.h>
#include <Box.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Entry.h>
#include <GroupView.h>
#include <IconView.h>
#include <LayoutBuilder.h>
#include <ListView.h>
#include <Path.h>
#include <Screen.h>
#include <ScrollView.h>
#include <String.h>
#include <StringView.h>
#include <SupportDefs.h>
#include <TextView.h>
#include <TranslationDefs.h>
#include <TranslatorRoster.h>


#include "DataTranslations.h"
#include "DataTranslationsSettings.h"
#include "TranslatorListView.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "DataTranslations"


const uint32 kMsgTranslatorInfo = 'trin';
const uint32 kMsgSelectedTranslator = 'trsl';


DataTranslationsWindow::DataTranslationsWindow()
        :
        BWindow(BRect(0.0f, 0.0f, 597.0f, 368.0f),
                B_TRANSLATE_SYSTEM_NAME("DataTranslations"),
                B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE
                        | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS),
        fRelease(NULL)
{
        MoveTo(DataTranslationsSettings::Instance()->WindowCorner());

        _SetupViews();

        // Make sure that the window isn't positioned off screen
        BScreen screen;
        BRect screenFrame = screen.Frame();
        if (!screenFrame.Contains(Frame()))
                CenterOnScreen();

        BTranslatorRoster* roster = BTranslatorRoster::Default();
        roster->StartWatching(this);

        Show();
}


DataTranslationsWindow::~DataTranslationsWindow()
{
        BTranslatorRoster* roster = BTranslatorRoster::Default();
        roster->StopWatching(this);
}


// Reads the installed translators and adds them to our BListView
status_t
DataTranslationsWindow::_PopulateListView()
{
        BTranslatorRoster* roster = BTranslatorRoster::Default();

        // Get all Translators on the system. Gives us the number of translators
        // installed in num_translators and a reference to the first one
        int32 numTranslators;
        translator_id* translators = NULL;
        roster->GetAllTranslators(&translators, &numTranslators);

        float maxWidth = 0;

        for (int32 i = 0; i < numTranslators; i++) {
                // Getting the first three Infos: Name, Info & Version
                int32 version;
                const char* name;
                const char* info;
                roster->GetTranslatorInfo(translators[i], &name, &info, &version);
                fTranslatorListView->AddItem(new TranslatorItem(translators[i], name));
                maxWidth = std::max(maxWidth, fTranslatorListView->StringWidth(name));
        }

        fTranslatorListView->SortItems();

        fTranslatorListView->SetExplicitSize(BSize(maxWidth + 20, B_SIZE_UNSET));

        delete[] translators;
        return B_OK;
}


status_t
DataTranslationsWindow::_GetTranslatorInfo(int32 id, const char*& name,
        const char*& info, int32& version, BPath& path)
{
        // Returns information about the translator with the given id

        if (id < 0)
                return B_BAD_VALUE;

        BTranslatorRoster* roster = BTranslatorRoster::Default();
        if (roster->GetTranslatorInfo(id, &name, &info, &version) != B_OK)
                return B_ERROR;

        // Get the translator's path
        entry_ref ref;
        if (roster->GetRefFor(id, &ref) == B_OK) {
                BEntry entry(&ref);
                path.SetTo(&entry);
        } else
                path.Unset();

        return B_OK;
}


status_t
DataTranslationsWindow::_ShowConfigView(int32 id)
{
        // Shows the config panel for the translator with the given id

        if (id < 0)
                return B_BAD_VALUE;

        BTranslatorRoster* roster = BTranslatorRoster::Default();

        if (fConfigView != NULL) {
                fRightBox->RemoveChild(fConfigView);
                delete fConfigView;
                fConfigView = NULL;
                fInfoText = NULL;
                if (fRelease != NULL) {
                        fRelease->Release();
                        fRelease = NULL;
                }
        }

        BMessage emptyMessage;
        BRect rect(0.0f, 0.0f, 200.0f, 233.0f);
        status_t result = roster->MakeConfigurationView(id, &emptyMessage,
                &fConfigView, &rect);

        if (result != B_OK)
                return result;

        fConfigView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
                // force config views to all have the same color
        fRightBox->AddChild(fConfigView);

        // for default 12pt font size: 597 ≈ (0.85 * 12 * 49)
        fConfigView->SetExplicitMinSize(
                BSize(ceilf(be_control_look->DefaultItemSpacing() * 49)
                        - fTranslatorListView->Frame().Width(), B_SIZE_UNSET));

        // Make sure the translator's image doesn't get unloaded while we are still
        // showing a config view whose code is in the image
        fRelease = roster->AcquireTranslator(id);

        return B_OK;
}


void
DataTranslationsWindow::_ShowInfoView()
{
        if (fConfigView != NULL) {
                fRightBox->RemoveChild(fConfigView);
                delete fConfigView;
                fConfigView = NULL;
                fInfoText = NULL;
                if (fRelease != NULL) {
                        fRelease->Release();
                        fRelease = NULL;
                }
        }

        fInfoText = new BTextView("info text");
        fInfoText->MakeEditable(false);
        fInfoText->MakeSelectable(false);
        fInfoText->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        fInfoText->SetText(B_TRANSLATE(
                "Use this control panel to set default values for translators, "
                "to be used when no other settings are specified by an application."));
        rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
        fInfoText->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor);

        BGroupView* group = new BGroupView(B_VERTICAL);
        group->AddChild(fInfoText);
        float spacing = be_control_look->DefaultItemSpacing();
        group->GroupLayout()->SetInsets(spacing, spacing, spacing, spacing);
        fRightBox->AddChild(group);
        fConfigView = group;

        fConfigView->SetExplicitMinSize(
                BSize(ceilf(spacing * be_plain_font->Size() * 0.7)
                        - fTranslatorListView->Frame().Width(),
                        ceilf(spacing * be_plain_font->Size() * 0.4)));
}


void
DataTranslationsWindow::_SetupViews()
{
        fInfoText = NULL;
        fConfigView = NULL;
        // This is NULL until a translator is
        // selected from the listview

        // Add the translators list view
        fTranslatorListView = new TranslatorListView("TransList");
        fTranslatorListView->SetSelectionMessage(
                new BMessage(kMsgSelectedTranslator));

        BScrollView* scrollView = new BScrollView("scroll_trans",
                fTranslatorListView, B_WILL_DRAW | B_FRAME_EVENTS, false,
                true, B_FANCY_BORDER);

        // Box around the config and info panels
        fRightBox = new BBox("Right_Side");
        fRightBox->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
                B_ALIGN_USE_FULL_HEIGHT));

        // Add the translator icon view
        fIconView = new IconView();

        // Add the translator info button
        fButton = new BButton("info", B_TRANSLATE("Info"),
                new BMessage(kMsgTranslatorInfo),
                B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE);
        fButton->SetEnabled(false);

        // Populate the translators list view
        _PopulateListView();

        // Build the layout
        BLayoutBuilder::Group<>(this, B_HORIZONTAL)
                .SetInsets(B_USE_WINDOW_SPACING)
                .Add(scrollView, 3)
                .AddGroup(B_VERTICAL)
                        .Add(fRightBox)
                        .AddGroup(B_HORIZONTAL)
                                .Add(fIconView)
                                .AddGlue()
                                .Add(fButton)
                                .End()
                        .End()
                .End();

        fTranslatorListView->MakeFocus();
        _ShowInfoView();
}


bool
DataTranslationsWindow::QuitRequested()
{
        BPoint pt(Frame().LeftTop());
        DataTranslationsSettings::Instance()->SetWindowCorner(pt);
        be_app->PostMessage(B_QUIT_REQUESTED);
        return true;
}


void
DataTranslationsWindow::_ShowInfoAlert(int32 id)
{
        const char* name = NULL;
        const char* info = NULL;
        BPath path;
        int32 version = 0;
        _GetTranslatorInfo(id, name, info, version, path);

        const char* labels[] = { B_TRANSLATE("Name:"), B_TRANSLATE("Version:"),
                B_TRANSLATE("Info:"), B_TRANSLATE("Path:"), NULL };
        int offsets[4];

        BString message;
        BString temp;

        offsets[0] = 0;
        temp.SetToFormat("%s %s\n", labels[0], name);

        message.Append(temp);

        offsets[1] = message.Length();
        // Convert the version number into a readable format
        temp.SetToFormat("%s %" B_PRId32 ".%" B_PRId32 ".%" B_PRId32 "\n\n", labels[1],
                B_TRANSLATION_MAJOR_VERSION(version),
                B_TRANSLATION_MINOR_VERSION(version),
                B_TRANSLATION_REVISION_VERSION(version));

        message.Append(temp);

        offsets[2] = message.Length();
        temp.SetToFormat("%s\n%s\n\n", labels[2], info);

        message.Append(temp);

        offsets[3] = message.Length();
        temp.SetToFormat("%s %s\n", labels[3], path.Path());

        message.Append(temp);

        BAlert* alert = new BAlert(B_TRANSLATE("Info"), message.String(),
                B_TRANSLATE("OK"));
        BTextView* view = alert->TextView();
        BFont font;

        view->SetStylable(true);

        view->GetFont(&font);
        font.SetFace(B_BOLD_FACE);

        for (int32 i = 0; labels[i]; i++) {
                view->SetFontAndColor(offsets[i], offsets[i] + strlen(labels[i]), &font);
        }

        alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
        alert->Go();
}


void
DataTranslationsWindow::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case kMsgTranslatorInfo:
                {
                        int32 selected = fTranslatorListView->CurrentSelection(0);
                        if (selected < 0)
                                break;

                        TranslatorItem* item = fTranslatorListView->TranslatorAt(selected);
                        if (item != NULL)
                                _ShowInfoAlert(item->ID());
                        break;
                }

                case kMsgSelectedTranslator:
                {
                        // Update the icon and translator info panel
                        // to match the new selection

                        int32 selected = fTranslatorListView->CurrentSelection(0);
                        if (selected < 0) {
                                // If none selected, clear the old one
                                fIconView->DrawIcon(false);
                                fButton->SetEnabled(false);
                                fRightBox->RemoveChild(fConfigView);
                                _ShowInfoView();
                                break;
                        }

                        TranslatorItem* item = fTranslatorListView->TranslatorAt(selected);
                        if (item == NULL)
                                break;

                        _ShowConfigView(item->ID());

                        const char* name = NULL;
                        const char* info = NULL;
                        int32 version = 0;
                        BPath path;
                        _GetTranslatorInfo(item->ID(), name, info, version, path);
                        fIconView->SetIcon(path);
                        fButton->SetEnabled(true);
                        break;
                }

                case B_COLORS_UPDATED:
                {
                        if (fInfoText == NULL || fInfoText->Parent() == NULL)
                                break;

                        rgb_color color;
                        if (message->FindColor(ui_color_name(B_PANEL_TEXT_COLOR), &color)
                                        == B_OK) {
                                fInfoText->SetFontAndColor(be_plain_font, B_FONT_ALL, &color);
                        }
                        break;
                }

                case B_TRANSLATOR_ADDED:
                {
                        int32 index = 0;
                        int32 id;
                        while (message->FindInt32("translator_id", index++, &id) == B_OK) {
                                const char* name;
                                const char* info;
                                int32 version;
                                BPath path;
                                if (_GetTranslatorInfo(id, name, info, version, path) == B_OK)
                                        fTranslatorListView->AddItem(new TranslatorItem(id, name));
                        }

                        fTranslatorListView->SortItems();
                        break;
                }

                case B_TRANSLATOR_REMOVED:
                {
                        int32 index = 0;
                        int32 id;
                        while (message->FindInt32("translator_id", index++, &id) == B_OK) {
                                for (int32 i = 0; i < fTranslatorListView->CountItems(); i++) {
                                        TranslatorItem* item = fTranslatorListView->TranslatorAt(i);

                                        if (item == NULL)
                                                continue;

                                        if (item->ID() == (translator_id)id) {
                                                fTranslatorListView->RemoveItem(i);
                                                delete item;
                                                break;
                                        }
                                }
                        }
                        break;
                }

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