root/src/apps/mediaconverter/MediaConverterWindow.cpp
// Copyright 1999, Be Incorporated. All Rights Reserved.
// Copyright 2000-2004, Jun Suzuki. All Rights Reserved.
// Copyright 2007, 2010 Stephan Aßmus. All Rights Reserved.
// Copyright 2010-2013, Haiku, Inc. All Rights Reserved.
// This file may be used under the terms of the Be Sample Code License.


#include "MediaConverterWindow.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <Alert.h>
#include <Application.h>
#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <FilePanel.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <Menu.h>
#include <MenuBar.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <NumberFormat.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Roster.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <Slider.h>
#include <StringView.h>
#include <TextControl.h>

#include "MediaFileInfoView.h"
#include "MediaFileListView.h"
#include "MessageConstants.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "MediaConverter"
#define VERSION "1.3.0"


static const unsigned int kMinSourceWidth = 12;
static const unsigned int kQualitySliderWidth = 28;
static const unsigned int kDurationWidth = 10;


// #pragma mark - DirectoryFilter


class DirectoryFilter : public BRefFilter {
public:
        DirectoryFilter() {};
        virtual bool Filter(const entry_ref* ref,
                BNode* node, struct stat_beos* st, const char* filetype)
        {
                return node->IsDirectory();
        }
};


// #pragma mark - FileFormatMenuItem


class FileFormatMenuItem : public BMenuItem {
public:
        FileFormatMenuItem(media_file_format* format);
        virtual ~FileFormatMenuItem();

        media_file_format fFileFormat;
};


FileFormatMenuItem::FileFormatMenuItem(media_file_format* format)
        :
        BMenuItem(format->pretty_name, new BMessage(FORMAT_SELECT_MESSAGE))
{
        memcpy(&fFileFormat, format, sizeof(fFileFormat));
}


FileFormatMenuItem::~FileFormatMenuItem()
{
}


// #pragma mark - CodecMenuItem


class CodecMenuItem : public BMenuItem {
public:
        CodecMenuItem(media_codec_info* ci, uint32 message_type);
        virtual ~CodecMenuItem();

        media_codec_info fCodecInfo;
};


CodecMenuItem::CodecMenuItem(media_codec_info* ci, uint32 message_type)
        :
        BMenuItem(ci->pretty_name, new BMessage(message_type))
{
        memcpy(&fCodecInfo, ci, sizeof(fCodecInfo));
}


CodecMenuItem::~CodecMenuItem()
{
}


// #pragma mark - OutputBox


class OutputBox : public BBox {
public:
        OutputBox(border_style border, BView* child);
        virtual void FrameResized(float width, float height)
        {
                MediaConverterWindow* window
                        = dynamic_cast<MediaConverterWindow*>(Window());
                if (window != NULL)
                        window->TruncateOutputFolderPath();
                BBox::FrameResized(width, height);
        }
};


OutputBox::OutputBox(border_style border, BView* child)
        :
        BBox(border, child)
{
}


// #pragma mark - MediaConverterWindow


MediaConverterWindow::MediaConverterWindow(BRect frame)
        :
        BWindow(frame, B_TRANSLATE_SYSTEM_NAME("MediaConverter"),
                B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, B_NOT_ZOOMABLE
                | B_NOT_V_RESIZABLE | B_ASYNCHRONOUS_CONTROLS
                | B_AUTO_UPDATE_SIZE_LIMITS),
        fVideoQuality(75),
        fAudioQuality(75),
        fSaveFilePanel(NULL),
        fOpenFilePanel(NULL),
        fOutputDirSpecified(false),
        fEnabled(true),
        fConverting(false),
        fCancelling(false)
{
        BPath outputDir;
        if (find_directory(B_USER_DIRECTORY, &outputDir) != B_OK)
                outputDir.SetTo("/boot/home");
        fOutputDir.SetTo(outputDir.Path());

        fMenuBar = new BMenuBar("menubar");
        _CreateMenu();

        float padding = be_control_look->DefaultItemSpacing();

        fListView = new MediaFileListView();
        fListView->SetExplicitMinSize(BSize(padding * kMinSourceWidth, B_SIZE_UNSET));
        BScrollView* scroller = new BScrollView(NULL, fListView, 0, false, true);

        // file list view box
        fSourcesBox = new BBox(B_FANCY_BORDER, scroller);
        fSourcesBox->SetLayout(new BGroupLayout(B_HORIZONTAL, 0));
                // fSourcesBox's layout adjusted in _UpdateLabels

        // info box
        fInfoView = new MediaFileInfoView();
        fInfoView->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
                B_ALIGN_VERTICAL_UNSET));
        fInfoBox = new BBox(B_FANCY_BORDER, fInfoView);

        // output menu fields
        fFormatMenu = new BMenuField(NULL, B_TRANSLATE("File format:"),
                new BPopUpMenu(""));
        fAudioMenu = new BMenuField(NULL, B_TRANSLATE("Audio encoding:"),
                new BPopUpMenu(""));
        fVideoMenu = new BMenuField(NULL, B_TRANSLATE("Video encoding:"),
                new BPopUpMenu(""));

        // output folder
        fDestButton = new BButton(B_TRANSLATE("Output folder"),
                new BMessage(OUTPUT_FOLDER_MESSAGE));
        BAlignment labelAlignment(be_control_look->DefaultLabelAlignment());
        fOutputFolder = new BStringView(NULL, outputDir.Path());
        fOutputFolder->SetExplicitAlignment(labelAlignment);

        // start/end duration
        fStartDurationTC = new BTextControl(NULL, "0", NULL);
        BLayoutItem* startDuration = fStartDurationTC->CreateTextViewLayoutItem();
        startDuration->SetExplicitSize(BSize(padding * kDurationWidth, B_SIZE_UNSET));
        startDuration->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
                B_ALIGN_VERTICAL_CENTER));
        fEndDurationTC = new BTextControl(NULL, "0", NULL);
        BLayoutItem* endDuration = fEndDurationTC->CreateTextViewLayoutItem();
        endDuration->SetExplicitSize(BSize(padding * kDurationWidth, B_SIZE_UNSET));
        endDuration->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
                B_ALIGN_VERTICAL_CENTER));

        // video quality
        fVideoQualitySlider = new BSlider("VSlider", "" ,
                new BMessage(VIDEO_QUALITY_CHANGED_MESSAGE), 1, 100, B_HORIZONTAL);
        fVideoQualitySlider->SetModificationMessage(
                new BMessage(VIDEO_QUALITY_CHANGED_MESSAGE));
        fVideoQualitySlider->SetValue(fVideoQuality);
        fVideoQualitySlider->SetEnabled(false);
        fVideoQualitySlider->SetExplicitSize(BSize(padding * kQualitySliderWidth,
                B_SIZE_UNSET));

        // audio quality
        fAudioQualitySlider = new BSlider("ASlider", "" ,
                new BMessage(AUDIO_QUALITY_CHANGED_MESSAGE), 1, 100, B_HORIZONTAL);
        fAudioQualitySlider->SetModificationMessage(
                new BMessage(AUDIO_QUALITY_CHANGED_MESSAGE));
        fAudioQualitySlider->SetValue(fAudioQuality);
        fAudioQualitySlider->SetEnabled(false);
        fAudioQualitySlider->SetExplicitSize(BSize(padding * kQualitySliderWidth,
                B_SIZE_UNSET));

        // output format box
        BView* outputGrid = BLayoutBuilder::Grid<>()
                .Add(fFormatMenu->CreateLabelLayoutItem(), 0, 0)
                .Add(fFormatMenu->CreateMenuBarLayoutItem(), 1, 0)
                .Add(fAudioMenu->CreateLabelLayoutItem(), 0, 1)
                .Add(fAudioMenu->CreateMenuBarLayoutItem(), 1, 1)
                .Add(fVideoMenu->CreateLabelLayoutItem(), 0, 2)
                .Add(fVideoMenu->CreateMenuBarLayoutItem(), 1, 2)
                .Add(fDestButton, 0, 3)
                .Add(fOutputFolder, 1, 3)
                .Add(fStartDurationTC->CreateLabelLayoutItem(), 0, 4)
                .Add(startDuration, 1, 4)
                .Add(fEndDurationTC->CreateLabelLayoutItem(), 0, 5)
                .Add(endDuration, 1, 5)
                .Add(fVideoQualitySlider, 0, 6, 2, 1)
                .Add(fAudioQualitySlider, 0, 7, 2, 1)
                .View();
        outputGrid->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
                B_ALIGN_USE_FULL_HEIGHT));
        fOutputBox = new OutputBox(B_FANCY_BORDER, outputGrid);
        fOutputBox->SetLayout(new BGroupLayout(B_HORIZONTAL, 0));
                // fOutputBox's layout adjusted in _UpdateLabels

        // buttons
        fPreviewButton = new BButton(B_TRANSLATE("Preview"),
                new BMessage(PREVIEW_MESSAGE));
        fPreviewButton->SetEnabled(false);

        fConvertButton = new BButton(B_TRANSLATE("Convert"),
                new BMessage(CONVERT_BUTTON_MESSAGE));

        // Status views
        fStatus = new BStringView(NULL, NULL);
        fStatus->SetExplicitAlignment(labelAlignment);
        fFileStatus = new BStringView(NULL, NULL);
        fFileStatus->SetExplicitAlignment(labelAlignment);

        SetStatusMessage("");
        _UpdateLabels();

        BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
                .SetInsets(0, 0, 0, 0)
                .Add(fMenuBar)
                .AddSplit(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
                        .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
                                B_USE_WINDOW_SPACING, 0)
                        .Add(fSourcesBox)
                        .AddGroup(B_VERTICAL, B_USE_ITEM_SPACING)
                                .Add(fInfoBox)
                                .Add(fOutputBox)
                        .End()
                .End()
                .AddGrid(B_USE_ITEM_SPACING)
                        .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING,
                                B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING)
                        .Add(fStatus, 0, 0)
                        .Add(fFileStatus, 0, 1)
                        .Add(BSpaceLayoutItem::CreateGlue(), 1, 0)
                        .Add(fPreviewButton, 2, 0)
                        .Add(fConvertButton, 3, 0)
                .End();
}


MediaConverterWindow::~MediaConverterWindow()
{
        delete fSaveFilePanel;
        delete fOpenFilePanel;
}


// #pragma mark -


void
MediaConverterWindow::MessageReceived(BMessage* message)
{
        entry_ref inRef;

        BEntry inEntry;
        BNumberFormat fNumberFormat;
        BString buffer;

        switch (message->what) {
                #if B_BEOS_VERSION <= B_BEOS_VERSION_6
                case B_LANGUAGE_CHANGED:
                        LanguageChanged();
                        break;
                #endif

                case INIT_FORMAT_MENUS:
                        BuildFormatMenu();
                        if (CountSourceFiles() == 0)
                                SetEnabled(false, false);
                        break;

                case B_SIMPLE_DATA:
                        if (message->WasDropped()) {
                                DetachCurrentMessage();
                                message->what = B_REFS_RECEIVED;
                                BMessenger(be_app).SendMessage(message);
                                delete message;
                        }
                        break;

                case FORMAT_SELECT_MESSAGE:
                        BuildAudioVideoMenus();
                        break;
                case AUDIO_CODEC_SELECT_MESSAGE:
                        break;
                case VIDEO_CODEC_SELECT_MESSAGE:
                        break;

                case CONVERT_BUTTON_MESSAGE:
                        if (!fConverting) {
                                fConvertButton->SetLabel(B_TRANSLATE("Cancel"));
                                fConverting = true;
                                SetStatusMessage(B_TRANSLATE("Convert"));
                                SetEnabled(false, true);
                                BMessenger(be_app).SendMessage(START_CONVERSION_MESSAGE);
                        } else if (!fCancelling) {
                                fCancelling = true;
                                SetStatusMessage(B_TRANSLATE("Cancelling" B_UTF8_ELLIPSIS));
                                BMessenger(be_app).SendMessage(CANCEL_CONVERSION_MESSAGE);
                        }
                        break;

                case CONVERSION_DONE_MESSAGE:
                {
                        SetStatusMessage(fCancelling ? B_TRANSLATE("Conversion cancelled")
                                : B_TRANSLATE("Conversion completed"));
                        fConverting = false;
                        fCancelling = false;
                        bool enable = CountSourceFiles() > 0;
                        SetEnabled(enable, enable);
                        fConvertButton->SetLabel(B_TRANSLATE("Convert"));
                        break;
                }

                case OUTPUT_FOLDER_MESSAGE:
                        // Execute Save Panel
                        if (fSaveFilePanel == NULL) {
                                BButton* selectThisDir;

                                BMessage folderSelect(FOLDER_SELECT_MESSAGE);
                                fSaveFilePanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL,
                                        B_DIRECTORY_NODE, true, &folderSelect, NULL, false, true);
                                fSaveFilePanel->SetButtonLabel(B_DEFAULT_BUTTON,
                                        B_TRANSLATE("Select"));
                                fSaveFilePanel->SetTarget(this);

                                fSaveFilePanel->Window()->Lock();
                                fSaveFilePanel->Window()->SetTitle(
                                        B_TRANSLATE("MediaConverter+:SaveDirectory"));
                                BRect buttonRect
                                        = fSaveFilePanel->Window()->ChildAt(0)->FindView(
                                                "cancel button")->Frame();
                                buttonRect.right  = buttonRect.left - 20;
                                buttonRect.left = buttonRect.right - 130;
                                selectThisDir = new BButton(buttonRect, NULL,
                                        B_TRANSLATE("Select this folder"),
                                        new BMessage(SELECT_THIS_DIR_MESSAGE),
                                        B_FOLLOW_BOTTOM | B_FOLLOW_RIGHT);
                                selectThisDir->SetTarget(this);
                                fSaveFilePanel->Window()->ChildAt(0)->AddChild(selectThisDir);
                                fSaveFilePanel->Window()->Unlock();

                                fSaveFilePanel->SetRefFilter(new DirectoryFilter);
                        }
                        fSaveFilePanel->Show();
                        break;

                case FOLDER_SELECT_MESSAGE:
                        // "SELECT" Button at Save Panel Pushed
                        fSaveFilePanel->GetNextSelectedRef(&inRef);
                        inEntry.SetTo(&inRef, true);
                        _SetOutputFolder(inEntry);
                        fOutputDirSpecified = true;
                        fSaveFilePanel->Rewind();
                        break;

                case SELECT_THIS_DIR_MESSAGE:
                        // "THIS DIR" Button at Save Panel Pushed
                        fSaveFilePanel->GetPanelDirectory(&inRef);
                        fSaveFilePanel->Hide();
                        inEntry.SetTo(&inRef, true);
                        _SetOutputFolder(inEntry);
                        fOutputDirSpecified = true;
                        break;

                case OPEN_FILE_MESSAGE:
                        // Execute Open Panel
                        if (!fOpenFilePanel) {
                                fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL,
                                        B_FILE_NODE, true, NULL, NULL, false, true);
                                fOpenFilePanel->SetTarget(this);
                        }
                        fOpenFilePanel->Show();
                        break;

                case B_REFS_RECEIVED:
                        // Media Files Seleced by Open Panel
                        DetachCurrentMessage();
                        message->what = B_REFS_RECEIVED;
                        BMessenger(be_app).SendMessage(message);
                        // fall through

                case B_CANCEL:
                        break;

                case QUIT_MESSAGE:
                        MediaConverterWindow::QuitRequested();
                        break;

                case PREVIEW_MESSAGE:
                {
                        // Build the command line to launch the preview application.
                        // TODO: Launch the default app instead of hardcoded MediaPlayer!
                        int32 srcIndex = fListView->CurrentSelection();
                        BMediaFile* inFile = NULL;
                        status_t status = GetSourceFileAt(srcIndex, &inFile, &inRef);

                        const char* argv[3];
                        BString startPosString;
                        BPath path;

                        if (status == B_OK) {
                                argv[0] = "-pos";
                                        // NOTE: -pos argument is currently not supported by Haiku
                                        // MediaPlayer.
                                startPosString << fStartDurationTC->Text();
                                startPosString << "000";
                                argv[1] = startPosString.String();

                                status = inEntry.SetTo(&inRef);
                        }

                        if (status == B_OK) {
                                status = inEntry.GetPath(&path);
                                if (status == B_OK)
                                        argv[2] = path.Path();
                        }

                        if (status == B_OK) {
                                status = be_roster->Launch(
                                        "application/x-vnd.Haiku-MediaPlayer",
                                        3, (char**)argv, NULL);
                        }

                        if (status != B_OK && status != B_ALREADY_RUNNING) {
                                BString errorString(B_TRANSLATE("Error launching: %strError%"));
                                errorString.ReplaceFirst("%strError%", strerror(status));
                                BAlert* alert = new BAlert(B_TRANSLATE("Error"),
                                        errorString.String(), B_TRANSLATE("OK"));
                                alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                                alert->Go();
                        }
                        break;
                }

                case VIDEO_QUALITY_CHANGED_MESSAGE:
                {
                        int32 value;
                        BString data;

                        message->FindInt32("be:value", &value);
                        double percentValue = value / 100.0;

                        if (fNumberFormat.FormatPercent(data, percentValue) != B_OK)
                                data.SetToFormat("%d%%", (int8)value);

                        buffer.SetToFormat(B_TRANSLATE("Video quality: %s"), data.String());
                        fVideoQualitySlider->SetLabel(buffer.String());
                        fVideoQuality = (int)percentValue;
                        break;
                }

                case AUDIO_QUALITY_CHANGED_MESSAGE:
                {
                        int32 value;
                        BString data;

                        message->FindInt32("be:value", &value);
                        double percentValue = value / 100.0;

                        if (fNumberFormat.FormatPercent(data, percentValue) != B_OK)
                                data.SetToFormat("%d%%", (int8)value);

                        buffer.SetToFormat(B_TRANSLATE("Audio quality: %s"), data.String());
                        fAudioQualitySlider->SetLabel(buffer.String());
                        fAudioQuality = (int)percentValue;
                        break;
                }

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


bool
MediaConverterWindow::QuitRequested()
{
        if (!fConverting) {
                BMessenger(be_app).SendMessage(B_QUIT_REQUESTED);
                return true;
        } else if (!fCancelling) {
                fCancelling = true;
                SetStatusMessage(B_TRANSLATE("Cancelling"));
                BMessenger(be_app).SendMessage(CANCEL_CONVERSION_MESSAGE);
        }

        return false;
}


// #pragma mark -


void
MediaConverterWindow::LanguageChanged()
{
        _DestroyMenu();
        _CreateMenu();
        _UpdateLabels();
        BuildAudioVideoMenus();
        Lock();
        fInfoView->Invalidate();
        Unlock();
}


void
MediaConverterWindow::BuildAudioVideoMenus()
{
        BMenu* menu = fAudioMenu->Menu();
        BMenuItem* item;

        // clear out old audio codec menu items
        while ((item = menu->RemoveItem((int32)0)) != NULL)
                delete item;

        bool separator = true;

        // get selected file format
        FileFormatMenuItem* ffmi
                = (FileFormatMenuItem*)fFormatMenu->Menu()->FindMarked();
        media_file_format* mf_format = &(ffmi->fFileFormat);

        media_format format, outfmt;
        format.Clear();
        media_codec_info codec_info;
        int32 cookie = 0;
        CodecMenuItem* cmi;

        // add available audio encoders to menu
        format.type = B_MEDIA_RAW_AUDIO;
        format.u.raw_audio = media_raw_audio_format::wildcard;
        while (get_next_encoder(&cookie, mf_format, &format, &outfmt, &codec_info)
                        == B_OK) {
                if (separator) {
                        menu->AddItem(new BMenuItem(
                                B_TRANSLATE_CONTEXT("No audio", "Audio codecs list"),
                                new BMessage(AUDIO_CODEC_SELECT_MESSAGE)));
                        menu->AddSeparatorItem();
                        separator = false;
                }

                cmi = new CodecMenuItem(&codec_info, AUDIO_CODEC_SELECT_MESSAGE);
                menu->AddItem(cmi);
                // reset media format struct
/*
                format.type = B_MEDIA_RAW_AUDIO;
                format.u.raw_audio = media_raw_audio_format::wildcard;
*/
        }

        // mark first audio encoder
        item = menu->ItemAt(0);
        if (item != NULL) {
                fAudioMenu->SetEnabled(fEnabled);
                fAudioQualitySlider->SetEnabled(fEnabled);
                item->SetMarked(true);
                ((BInvoker*)item)->Invoke();
        } else {
                item = new BMenuItem(
                        B_TRANSLATE_CONTEXT("None available", "Audio codecs"), NULL);
                menu->AddItem(item);
                item->SetMarked(true);
                fAudioMenu->SetEnabled(false);
                fAudioQualitySlider->SetEnabled(false);
        }

        // clear out old video codec menu items
        menu = fVideoMenu->Menu();
        while ((item = menu->RemoveItem((int32)0)) != NULL)
                delete item;

        separator = true;

        // construct a generic video format.  Some of these parameters
        // seem silly, but are needed for R4.5.x, which is more picky
        // than subsequent BeOS releases will be.
        format.Clear();
        format.type = B_MEDIA_RAW_VIDEO;
        format.u.raw_video.last_active = (uint32)(240 - 1);
        format.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT;
        format.u.raw_video.display.format = B_RGB32;
        format.u.raw_video.display.line_width = (int32)320;
        format.u.raw_video.display.line_count = (int32)240;
        format.u.raw_video.display.bytes_per_row = 4 * 320;

        // add available video encoders to menu
        cookie = 0;
        while (get_next_encoder(&cookie, mf_format, &format, &outfmt, &codec_info)
                        == B_OK) {
                if (separator) {
                        menu->AddItem(new BMenuItem(
                                B_TRANSLATE_CONTEXT("No video", "Video codecs list"),
                                new BMessage(VIDEO_CODEC_SELECT_MESSAGE)));
                        menu->AddSeparatorItem();
                        separator = false;
                }

                cmi = new CodecMenuItem(&codec_info, VIDEO_CODEC_SELECT_MESSAGE);
                menu->AddItem(cmi);
        }

        // mark first video encoder
        item = menu->ItemAt(0);
        if (item != NULL) {
                fVideoMenu->SetEnabled(fEnabled);
                fVideoQualitySlider->SetEnabled(fEnabled);
                item->SetMarked(true);
                ((BInvoker*)item)->Invoke();
        } else {
                item = new BMenuItem(
                        B_TRANSLATE_CONTEXT("None available", "Video codecs"), NULL);
                menu->AddItem(item);
                item->SetMarked(true);
                fVideoMenu->SetEnabled(false);
                fVideoQualitySlider->SetEnabled(false);
        }
}

void
MediaConverterWindow::GetSelectedFormatInfo(media_file_format** format,
        media_codec_info** audio, media_codec_info** video)
{
        *audio = NULL;
        *video = NULL;
        *format = NULL;

        FileFormatMenuItem* formatItem =
                dynamic_cast<FileFormatMenuItem*>(fFormatMenu->Menu()->FindMarked());
        if (formatItem != NULL)
                *format = &(formatItem->fFileFormat);

        *audio = *video = NULL;
        CodecMenuItem* codecItem =
                dynamic_cast<CodecMenuItem*>(fAudioMenu->Menu()->FindMarked());
        if (codecItem != NULL)
                *audio =  &(codecItem->fCodecInfo);

        codecItem = dynamic_cast<CodecMenuItem*>(fVideoMenu->Menu()->FindMarked());
        if (codecItem != NULL)
                *video =  &(codecItem->fCodecInfo);
}


void
MediaConverterWindow::BuildFormatMenu()
{
        BMenu* menu = fFormatMenu->Menu();
        BMenuItem* item;

        // clear out old format menu items
        while ((item = menu->RemoveItem((int32)0)) != NULL)
                delete item;

        // add menu items for each file format
        media_file_format mfi;
        int32 cookie = 0;
        FileFormatMenuItem* ff_item;
        while (get_next_file_format(&cookie, &mfi) == B_OK) {
                if ((mfi.capabilities & media_file_format::B_WRITABLE) == 0)
                        continue;
                ff_item = new FileFormatMenuItem(&mfi);
                menu->AddItem(ff_item);
        }

        // mark first item
        item = menu->ItemAt(0);
        if (item != NULL) {
                item->SetMarked(true);
                ((BInvoker*)item)->Invoke();
        }
}


void
MediaConverterWindow::SetFileMessage(const char* message)
{
        fFileStatus->SetText(message);
}


void
MediaConverterWindow::SetStatusMessage(const char* message)
{
        fStatus->SetText(message);
}


// #pragma mark -


bool
MediaConverterWindow::AddSourceFile(BMediaFile* file, const entry_ref& ref)
{
        if (!fListView->AddMediaItem(file, ref))
                return false;

        if (!fOutputDirSpecified) {
                BEntry entry(&ref);
                entry.GetParent(&entry);
                _SetOutputFolder(entry);
        }

        return true;
}


void
MediaConverterWindow::RemoveSourceFile(int32 index)
{
        delete fListView->RemoveItem(index);
        fStartDurationTC->SetText("0");
        fEndDurationTC->SetText("0");
}


int32
MediaConverterWindow::CountSourceFiles()
{
        return fListView->CountItems();
}


status_t
MediaConverterWindow::GetSourceFileAt(int32 index, BMediaFile** _file,
        entry_ref* ref)
{
        MediaFileListItem* item = dynamic_cast<MediaFileListItem*>(
                fListView->ItemAt(index));
        if (item != NULL) {
                *_file = item->fMediaFile;
                *ref = item->fRef;
                return B_OK;
        } else
                return B_ERROR;
}


void
MediaConverterWindow::SourceFileSelectionChanged()
{
        int32 selected = fListView->CurrentSelection();
        BMediaFile* file = NULL;
        entry_ref ref;
        bool enabled = GetSourceFileAt(selected, &file, &ref) == B_OK;

        fPreviewButton->SetEnabled(enabled);
        fVideoQualitySlider->SetEnabled(enabled);
        fAudioQualitySlider->SetEnabled(enabled);
        fStartDurationTC->SetEnabled(enabled);
        fEndDurationTC->SetEnabled(enabled);

        BString duration;
        if (enabled) {
                fInfoView->Update(file, &ref);
                // HACK: get the fInfoView to update the duration "synchronously"
                UpdateIfNeeded();
                duration << fInfoView->Duration() / 1000;
        } else
                duration = "0";

        // update duration text controls
        fStartDurationTC->SetText("0");
        fEndDurationTC->SetText(duration.String());
}


// #pragma mark -


void
MediaConverterWindow::SetEnabled(bool enabled, bool convertEnabled)
{
        fConvertButton->SetEnabled(convertEnabled);
        if (enabled == fEnabled)
                return;

        fFormatMenu->SetEnabled(enabled);
        fAudioMenu->SetEnabled(enabled);
        fVideoMenu->SetEnabled(enabled);
        fListView->SetEnabled(enabled);
        fStartDurationTC->SetEnabled(enabled);
        fEndDurationTC->SetEnabled(enabled);

        fEnabled = enabled;
}


bool
MediaConverterWindow::IsEnabled()
{
        return fEnabled;
}


const char*
MediaConverterWindow::StartDuration() const
{
        return fStartDurationTC->Text();
}


const char*
MediaConverterWindow::EndDuration() const
{
        return fEndDurationTC->Text();
}


BDirectory
MediaConverterWindow::OutputDirectory() const
{
        return fOutputDir;
}


void
MediaConverterWindow::SetAudioQualityLabel(const char* label)
{
        fAudioQualitySlider->SetLabel(label);
}


void
MediaConverterWindow::SetVideoQualityLabel(const char* label)
{
        fVideoQualitySlider->SetLabel(label);
}


void
MediaConverterWindow::TruncateOutputFolderPath()
{
        BEntry entry;
        fOutputDir.GetEntry(&entry);
        BPath path;
        entry.GetPath(&path);
        BString pathString(path.Path());
        float maxWidth = fVideoMenu->MenuBar()->Frame().Width();

        fOutputFolder->TruncateString(&pathString, B_TRUNCATE_MIDDLE, maxWidth);
        fOutputFolder->SetText(pathString.String());
        if (fOutputFolder->StringWidth(path.Path()) > maxWidth)
                fOutputFolder->SetToolTip(path.Path());
        else
                fOutputFolder->SetToolTip((const char*)NULL);
}


// #pragma mark -


void
MediaConverterWindow::_UpdateLabels()
{
        BNumberFormat fNumberFormat;

        if (fSourcesBox != NULL) {
                fSourcesBox->SetLabel(B_TRANSLATE("Source files"));
                _UpdateBBoxLayoutInsets(fSourcesBox);
        }

        if (fInfoBox != NULL)
                fInfoBox->SetLabel(B_TRANSLATE("File details"));

        if (fOutputBox != NULL) {
                fOutputBox->SetLabel(B_TRANSLATE("Output format"));
                _UpdateBBoxLayoutInsets(fOutputBox);
        }

        if (fConvertButton != NULL)
                fConvertButton->SetLabel(B_TRANSLATE("Convert"));

        if (fPreviewButton != NULL)
                fPreviewButton->SetLabel(B_TRANSLATE("Preview"));

        if (fDestButton != NULL)
                fDestButton->SetLabel(B_TRANSLATE("Output folder"));

        if (fVideoQualitySlider != NULL) {
                BString buffer;
                BString data;
                double percentValue = fVideoQuality / 100.0;

                if (fNumberFormat.FormatPercent(data, percentValue) != B_OK)
                        data.SetToFormat("%d%%", (int8)fVideoQuality);

                buffer.SetToFormat(B_TRANSLATE("Video quality: %s"), data.String());
                fVideoQuality = (int)percentValue;
                fVideoQualitySlider->SetLabel(buffer.String());
                fVideoQualitySlider->SetLimitLabels(B_TRANSLATE("Low"), B_TRANSLATE("High"));
        }

        if (fAudioQualitySlider != NULL) {
                BString buffer;
                BString data;
                double percentValue = fAudioQuality / 100.0;

                if (fNumberFormat.FormatPercent(data, percentValue) != B_OK) {
                        data.SetToFormat("%d%%", (int8)fAudioQuality);
                }

                buffer.SetToFormat(B_TRANSLATE("Audio quality: %s"), data.String());
                fAudioQuality = (int)percentValue;
                fAudioQualitySlider->SetLabel(buffer.String());
                fAudioQualitySlider->SetLimitLabels(B_TRANSLATE("Low"), B_TRANSLATE("High"));
        }

        if (fStartDurationTC != NULL)
                fStartDurationTC->SetLabel(B_TRANSLATE("Start [ms]: "));

        if (fEndDurationTC != NULL)
                fEndDurationTC->SetLabel(B_TRANSLATE("End   [ms]: "));

        if (fFormatMenu != NULL)
                fFormatMenu->SetLabel(B_TRANSLATE("File format:"));

        if (fAudioMenu != NULL)
                fAudioMenu->SetLabel(B_TRANSLATE("Audio encoding:"));

        if (fVideoMenu != NULL)
                fVideoMenu->SetLabel(B_TRANSLATE("Video encoding:"));

        SetFileMessage(B_TRANSLATE("Drop media files onto this window"));
}


void
MediaConverterWindow::_UpdateBBoxLayoutInsets(BBox* box)
{
        BTwoDimensionalLayout* layout
                = dynamic_cast<BTwoDimensionalLayout*>(box->GetLayout());
        if (layout != NULL) {
                float padding = be_control_look->DefaultItemSpacing();
                layout->SetInsets(padding, box->TopBorderOffset() + padding, padding,
                        padding);
        }
}


void
MediaConverterWindow::_DestroyMenu()
{
        BMenu* menu;

        while ((menu = fMenuBar->SubmenuAt(0)) != NULL) {
                fMenuBar->RemoveItem(menu);
                delete menu;
        }
}


void
MediaConverterWindow::_CreateMenu()
{
        BMenu* menu;
        BMenuItem* item;

        menu = new BMenu(B_TRANSLATE_CONTEXT("File", "Menu"));
        item = new BMenuItem(B_TRANSLATE_CONTEXT("Open" B_UTF8_ELLIPSIS, "Menu"),
                new BMessage(OPEN_FILE_MESSAGE), 'O');
        menu->AddItem(item);
        menu->AddSeparatorItem();
        item = new BMenuItem(B_TRANSLATE_CONTEXT("Quit", "Menu"),
                new BMessage(QUIT_MESSAGE), 'Q');
        menu->AddItem(item);

        fMenuBar->AddItem(menu);
}


void
MediaConverterWindow::_SetOutputFolder(BEntry entry)
{
        BPath path;
        entry.GetPath(&path);
        if (access(path.Path(), W_OK) != -1) {
                fOutputDir.SetTo(&entry);
        } else {
                BString errorString(B_TRANSLATE("Error writing to location: %strPath%."
                        " Defaulting to location: /boot/home"));
                errorString.ReplaceFirst("%strPath%", path.Path());
                BAlert* alert = new BAlert(B_TRANSLATE("Error"),
                        errorString.String(), B_TRANSLATE("OK"));
                alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                alert->Go();
                fOutputDir.SetTo("/boot/home");
        }
        TruncateOutputFolderPath();
}