root/src/preferences/media/MediaViews.cpp
/*
 * Copyright 2003-2011, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Sikosis
 *              Jérôme Duval
 */


#include "MediaViews.h"

#include <AutoDeleter.h>
#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <Deskbar.h>
#include <Entry.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MediaAddOn.h>
#include <MediaRoster.h>
#include <MenuField.h>
#include <PopUpMenu.h>
#include <String.h>
#include <StringView.h>
#include <TextView.h>

#include <assert.h>
#include <stdio.h>

#include "MediaWindow.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Media views"

#define MEDIA_DEFAULT_INPUT_CHANGE 'dich'
#define MEDIA_DEFAULT_OUTPUT_CHANGE 'doch'
#define MEDIA_SHOW_HIDE_VOLUME_CONTROL 'shvc'


SettingsView::SettingsView()
        :
        BGroupView(B_VERTICAL, B_USE_DEFAULT_SPACING),
        fInputMenu(NULL),
        fOutputMenu(NULL)
{
        // input menu
        fInputMenu = new BPopUpMenu(B_TRANSLATE_ALL("<none>",
                "VideoInputMenu", "Used when no video input is available"));
        fInputMenu->SetLabelFromMarked(true);

        // output menu
        fOutputMenu = new BPopUpMenu(B_TRANSLATE_ALL("<none>",
                "VideoOutputMenu", "Used when no video output is available"));
        fOutputMenu->SetLabelFromMarked(true);
}


BButton*
SettingsView::MakeRestartButton()
{
        return new BButton("restartButton",
                B_TRANSLATE("Restart media services"),
                new BMessage(ML_RESTART_MEDIA_SERVER));
}



void
SettingsView::AddInputNodes(NodeList& list)
{
        _EmptyMenu(fInputMenu);

        BMessage message(MEDIA_DEFAULT_INPUT_CHANGE);
        _PopulateMenu(fInputMenu, list, message);
}


void
SettingsView::AddOutputNodes(NodeList& list)
{
        _EmptyMenu(fOutputMenu);

        BMessage message(MEDIA_DEFAULT_OUTPUT_CHANGE);
        _PopulateMenu(fOutputMenu, list, message);
}


void
SettingsView::SetDefaultInput(const dormant_node_info* info)
{
        _ClearMenuSelection(fInputMenu);
        NodeMenuItem* item = _FindNodeItem(fInputMenu, info);
        if (item)
                item->SetMarked(true);
}


void
SettingsView::SetDefaultOutput(const dormant_node_info* info)
{
        _ClearMenuSelection(fOutputMenu);
        NodeMenuItem* item = _FindNodeItem(fOutputMenu, info);
        if (item)
                item->SetMarked(true);
}


void
SettingsView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MEDIA_DEFAULT_INPUT_CHANGE:
                {
                        int32 index;
                        if (message->FindInt32("index", &index)!=B_OK)
                                break;
                        NodeMenuItem* item
                                = static_cast<NodeMenuItem*>(fInputMenu->ItemAt(index));
                        SetDefaultInput(item->NodeInfo());
                        break;
                }
                case MEDIA_DEFAULT_OUTPUT_CHANGE:
                {
                        int32 index;
                        if (message->FindInt32("index", &index)!=B_OK)
                                break;
                        NodeMenuItem* item
                                = static_cast<NodeMenuItem*>(fOutputMenu->ItemAt(index));
                        SetDefaultOutput(item->NodeInfo());
                        break;
                }
                default:
                        BGroupView::MessageReceived(message);
        }
}


void
SettingsView::AttachedToWindow()
{
        BMessenger thisMessenger(this);
        fInputMenu->SetTargetForItems(thisMessenger);
        fOutputMenu->SetTargetForItems(thisMessenger);
}


MediaWindow*
SettingsView::_MediaWindow() const
{
        return static_cast<MediaWindow*>(Window());
}


void
SettingsView::_EmptyMenu(BMenu* menu)
{
        while (menu->CountItems() > 0)
                delete menu->RemoveItem((int32)0);
}


void
SettingsView::_PopulateMenu(BMenu* menu, NodeList& nodes,
        const BMessage& message)
{
        for (int32 i = 0; i < nodes.CountItems(); i++) {
                dormant_node_info* info = nodes.ItemAt(i);
                menu->AddItem(new NodeMenuItem(info, new BMessage(message)));
        }

        if (Window() != NULL)
                menu->SetTargetForItems(BMessenger(this));
}


NodeMenuItem*
SettingsView::_FindNodeItem(BMenu* menu, const dormant_node_info* nodeInfo)
{
        for (int32 i = 0; i < menu->CountItems(); i++) {
                NodeMenuItem* item = static_cast<NodeMenuItem*>(menu->ItemAt(i));
                const dormant_node_info* itemInfo = item->NodeInfo();
                if (itemInfo && itemInfo->addon == nodeInfo->addon
                        && itemInfo->flavor_id == nodeInfo->flavor_id) {
                        return item;
                }
        }
        return NULL;
}


void
SettingsView::_ClearMenuSelection(BMenu* menu)
{
        for (int32 i = 0; i < menu->CountItems(); i++) {
                BMenuItem* item = menu->ItemAt(i);
                item->SetMarked(false);
        }
}


NodeMenuItem::NodeMenuItem(const dormant_node_info* info, BMessage* message,
        char shortcut, uint32 modifiers)
        :
        BMenuItem(info->name, message, shortcut, modifiers),
        fInfo(info)
{

}


status_t
NodeMenuItem::Invoke(BMessage* message)
{
        if (IsMarked())
                return B_OK;
        return BMenuItem::Invoke(message);
}


ChannelMenuItem::ChannelMenuItem(media_input* input, BMessage* message,
        char shortcut, uint32 modifiers)
        :
        BMenuItem(input->name, message, shortcut, modifiers),
        fInput(input)
{
}


ChannelMenuItem::~ChannelMenuItem()
{
        delete fInput;
}


int32
ChannelMenuItem::DestinationID()
{
        return fInput->destination.id;
}


media_input*
ChannelMenuItem::Input()
{
        return fInput;
}


status_t
ChannelMenuItem::Invoke(BMessage* message)
{
        if (IsMarked())
                return B_OK;
        return BMenuItem::Invoke(message);
}


AudioSettingsView::AudioSettingsView()
{
        BBox* defaultsBox = new BBox("defaults");
        defaultsBox->SetLabel(B_TRANSLATE("Defaults"));
        BGridView* defaultsGridView = new BGridView();

        BMenuField* inputMenuField = new BMenuField("inputMenuField",
                B_TRANSLATE("Audio input:"), InputMenu());

        BMenuField* outputMenuField = new BMenuField("outputMenuField",
                B_TRANSLATE("Audio output:"), OutputMenu());

        BLayoutBuilder::Grid<>(defaultsGridView)
                .SetInsets(B_USE_DEFAULT_SPACING, 0, B_USE_DEFAULT_SPACING,
                        B_USE_DEFAULT_SPACING)
                .AddMenuField(inputMenuField, 0, 0, B_ALIGN_HORIZONTAL_UNSET, 1, 3, 1)
                .AddMenuField(outputMenuField, 0, 1)
                .AddMenuField(_MakeChannelMenu(), 2, 1);

        defaultsBox->AddChild(defaultsGridView);

        BLayoutBuilder::Group<>(this)
                .SetInsets(0, 0, 0, 0)
                .Add(defaultsBox)
                .AddGroup(B_HORIZONTAL)
                        .Add(_MakeVolumeCheckBox())
                        .AddGlue()
                        .Add(MakeRestartButton())
                        .End()
                .AddGlue();
}


void
AudioSettingsView::SetDefaultChannel(int32 channelID)
{
        for (int32 i = 0; i < fChannelMenu->CountItems(); i++) {
                ChannelMenuItem* item = _ChannelMenuItemAt(i);
                item->SetMarked(item->DestinationID() == channelID);
        }
}


void
AudioSettingsView::AttachedToWindow()
{
        SettingsView::AttachedToWindow();

        BMessenger thisMessenger(this);
        fChannelMenu->SetTargetForItems(thisMessenger);
        fVolumeCheckBox->SetTarget(thisMessenger);
}


void
AudioSettingsView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case ML_DEFAULT_CHANNEL_CHANGED:
                        {
                                int32 index;
                                if (message->FindInt32("index", &index) != B_OK)
                                        break;
                                ChannelMenuItem* item = _ChannelMenuItemAt(index);

                                if (item) {
                                        BMediaRoster* roster = BMediaRoster::Roster();
                                        roster->SetAudioOutput(*item->Input());
                                } else
                                        fprintf(stderr, "ChannelMenuItem not found\n");
                        }
                        break;
                case MEDIA_SHOW_HIDE_VOLUME_CONTROL:
                {
                        if (fVolumeCheckBox->Value() == B_CONTROL_ON)
                                _ShowDeskbarVolumeControl();
                        else
                                _HideDeskbarVolumeControl();
                        break;
                }

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


void
AudioSettingsView::SetDefaultInput(const dormant_node_info* info)
{
        SettingsView::SetDefaultInput(info);
        _MediaWindow()->UpdateInputListItem(MediaListItem::AUDIO_TYPE, info);
        BMediaRoster::Roster()->SetAudioInput(*info);
}


void
AudioSettingsView::SetDefaultOutput(const dormant_node_info* info)
{
        SettingsView::SetDefaultOutput(info);
        _MediaWindow()->UpdateOutputListItem(MediaListItem::AUDIO_TYPE, info);
        _FillChannelMenu(info);
        BMediaRoster::Roster()->SetAudioOutput(*info);
}


BMenuField*
AudioSettingsView::_MakeChannelMenu()
{
        fChannelMenu = new BPopUpMenu(B_TRANSLATE("<none>"));
        fChannelMenu->SetLabelFromMarked(true);
        BMenuField* channelMenuField = new BMenuField("channelMenuField",
                B_TRANSLATE("Channel:"), fChannelMenu);
        return channelMenuField;
}


BCheckBox*
AudioSettingsView::_MakeVolumeCheckBox()
{
        fVolumeCheckBox = new BCheckBox("volumeCheckBox",
                B_TRANSLATE("Show volume control on Deskbar"),
                new BMessage(MEDIA_SHOW_HIDE_VOLUME_CONTROL));

        if (BDeskbar().HasItem("MediaReplicant"))
                fVolumeCheckBox->SetValue(B_CONTROL_ON);

        return fVolumeCheckBox;
}


void
AudioSettingsView::_FillChannelMenu(const dormant_node_info* nodeInfo)
{
        _EmptyMenu(fChannelMenu);

        BMediaRoster* roster = BMediaRoster::Roster();
        media_node node;
        media_node_id node_id;

        status_t err = roster->GetInstancesFor(nodeInfo->addon,
                nodeInfo->flavor_id, &node_id);
        if (err != B_OK) {
                err = roster->InstantiateDormantNode(*nodeInfo, &node,
                        B_FLAVOR_IS_GLOBAL);
        } else {
                err = roster->GetNodeFor(node_id, &node);
        }

        if (err == B_OK) {
                int32 inputCount = 4;
                media_input* inputs = new media_input[inputCount];
                BPrivate::ArrayDeleter<media_input> inputDeleter(inputs);

                while (true) {
                        int32 realInputCount = 0;
                        err = roster->GetAllInputsFor(node, inputs,
                                inputCount, &realInputCount);
                        if (realInputCount > inputCount) {
                                inputCount *= 2;
                                inputs = new media_input[inputCount];
                                inputDeleter.SetTo(inputs);
                        } else {
                                inputCount = realInputCount;
                                break;
                        }
                }

                if (err == B_OK) {
                        BMessage message(ML_DEFAULT_CHANNEL_CHANGED);

                        for (int32 i = 0; i < inputCount; i++) {
                                media_input* input = new media_input();
                                *input = inputs[i];
                                ChannelMenuItem* channelItem = new ChannelMenuItem(input,
                                        new BMessage(message));
                                fChannelMenu->AddItem(channelItem);

                                if (channelItem->DestinationID() == 0)
                                        channelItem->SetMarked(true);
                        }
                }
        }

        if (Window())
                fChannelMenu->SetTargetForItems(BMessenger(this));
}


void
AudioSettingsView::_ShowDeskbarVolumeControl()
{
        BDeskbar deskbar;
        BEntry entry("/bin/desklink", true);
        int32 id;
        entry_ref ref;
        status_t status = entry.GetRef(&ref);
        if (status == B_OK)
                status = deskbar.AddItem(&ref, &id);

        if (status != B_OK) {
                fprintf(stderr, B_TRANSLATE(
                        "Couldn't add volume control in Deskbar: %s\n"),
                        strerror(status));
        }
}


void
AudioSettingsView::_HideDeskbarVolumeControl()
{
        BDeskbar deskbar;
        status_t status = deskbar.RemoveItem("MediaReplicant");
        if (status != B_OK) {
                fprintf(stderr, B_TRANSLATE(
                        "Couldn't remove volume control in Deskbar: %s\n"),
                        strerror(status));
        }
}


ChannelMenuItem*
AudioSettingsView::_ChannelMenuItemAt(int32 index)
{
        return static_cast<ChannelMenuItem*>(fChannelMenu->ItemAt(index));
}


VideoSettingsView::VideoSettingsView()
{
        BBox* defaultsBox = new BBox("defaults");
        defaultsBox->SetLabel(B_TRANSLATE("Defaults"));
        BGridView* defaultsGridView = new BGridView();

        BMenuField* inputMenuField = new BMenuField("inputMenuField",
                B_TRANSLATE("Video input:"), InputMenu());

        BMenuField* outputMenuField = new BMenuField("outputMenuField",
                B_TRANSLATE("Video output:"), OutputMenu());

        BLayoutBuilder::Grid<>(defaultsGridView)
                .SetInsets(B_USE_DEFAULT_SPACING, 0, B_USE_DEFAULT_SPACING,
                        B_USE_DEFAULT_SPACING)
                .AddMenuField(inputMenuField, 0, 0)
                .AddMenuField(outputMenuField, 0, 1);

        defaultsBox->AddChild(defaultsGridView);

        BLayoutBuilder::Group<>(this)
                .SetInsets(0, 0, 0, 0)
                .Add(defaultsBox)
                .AddGroup(B_HORIZONTAL)
                        .AddGlue()
                        .Add(MakeRestartButton())
                        .End()
                .AddGlue();
}


void
VideoSettingsView::SetDefaultInput(const dormant_node_info* info)
{
        SettingsView::SetDefaultInput(info);
        _MediaWindow()->UpdateInputListItem(MediaListItem::VIDEO_TYPE, info);
        BMediaRoster::Roster()->SetVideoInput(*info);
}


void
VideoSettingsView::SetDefaultOutput(const dormant_node_info* info)
{
        SettingsView::SetDefaultOutput(info);
        _MediaWindow()->UpdateOutputListItem(MediaListItem::VIDEO_TYPE, info);
        BMediaRoster::Roster()->SetVideoOutput(*info);
}