root/src/apps/mediaplayer/InfoWin.cpp
/*
 * InfoWin.cpp - Media Player for the Haiku Operating System
 *
 * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
 * Copyright 2015 Axel Dörfler <axeld@pinc-software.de>
 *
 * Released under the terms of the MIT license.
 */


#include "InfoWin.h"

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

#include <Bitmap.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Debug.h>
#include <LayoutBuilder.h>
#include <MediaDefs.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <Screen.h>
#include <String.h>
#include <StringFormat.h>
#include <StringForRate.h>
#include <StringView.h>
#include <TextView.h>

#include "Controller.h"
#include "ControllerObserver.h"
#include "LocationStringView.h"
#include "PlaylistItem.h"


#define MIN_WIDTH 500


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "MediaPlayer-InfoWin"


class IconView : public BView {
public:
                                                                IconView(const char* name, int32 iconSize);
        virtual                                         ~IconView();

                        status_t                        SetIcon(const PlaylistItem* item);
                        status_t                        SetIcon(const char* mimeType);
                        void                            SetGenericIcon();

        virtual void                            GetPreferredSize(float* _width, float* _height);
        virtual void                            AttachedToWindow();
        virtual void                            Draw(BRect updateRect);

private:
                        BBitmap*                        fIconBitmap;
};


IconView::IconView(const char* name, int32 iconSize)
        :
        BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
        fIconBitmap(NULL)
{
        fIconBitmap = new BBitmap(BRect(0, 0, iconSize - 1, iconSize - 1),
                B_RGBA32);
        SetExplicitMaxSize(PreferredSize());
}


IconView::~IconView()
{
        delete fIconBitmap;
}


status_t
IconView::SetIcon(const PlaylistItem* item)
{
        return item->GetIcon(fIconBitmap, B_LARGE_ICON);
}


status_t
IconView::SetIcon(const char* mimeTypeString)
{
        if (!mimeTypeString)
                return B_BAD_VALUE;

        // get type icon
        BMimeType mimeType(mimeTypeString);
        status_t status = mimeType.GetIcon(fIconBitmap, B_LARGE_ICON);

        // get supertype icon
        if (status != B_OK) {
                BMimeType superType;
                status = mimeType.GetSupertype(&superType);
                if (status == B_OK)
                        status = superType.GetIcon(fIconBitmap, B_LARGE_ICON);
        }

        return status;
}


void
IconView::SetGenericIcon()
{
        // get default icon
        BMimeType genericType(B_FILE_MIME_TYPE);
        if (genericType.GetIcon(fIconBitmap, B_LARGE_ICON) != B_OK) {
                // clear bitmap
                uint8 transparent = 0;
                if (fIconBitmap->ColorSpace() == B_CMAP8)
                        transparent = B_TRANSPARENT_MAGIC_CMAP8;

                memset(fIconBitmap->Bits(), transparent, fIconBitmap->BitsLength());
        }
}


void
IconView::GetPreferredSize(float* _width, float* _height)
{
        if (_width != NULL) {
                *_width = fIconBitmap->Bounds().Width()
                        + 2 * be_control_look->DefaultItemSpacing();
        }
        if (_height != NULL) {
                *_height = fIconBitmap->Bounds().Height()
                        + 2 * be_control_look->DefaultItemSpacing();
        }
}


void
IconView::AttachedToWindow()
{
        if (Parent() != NULL)
                SetViewColor(Parent()->ViewColor());
        else
                SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
}


void
IconView::Draw(BRect updateRect)
{
        BRect rect(Bounds());

        if (fIconBitmap != NULL) {
                // Draw bitmap centered within the view
                SetDrawingMode(B_OP_ALPHA);
                DrawBitmap(fIconBitmap, BPoint(rect.left
                                + (rect.Width() - fIconBitmap->Bounds().Width()) / 2,
                        rect.top + (rect.Height() - fIconBitmap->Bounds().Height()) / 2));
        }
}


// #pragma mark -


InfoWin::InfoWin(BPoint leftTop, Controller* controller)
        :
        BWindow(BRect(leftTop.x, leftTop.y, leftTop.x + MIN_WIDTH - 1,
                leftTop.y + 300), B_TRANSLATE("File info"), B_TITLED_WINDOW,
                B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE),
        fController(controller),
        fControllerObserver(new ControllerObserver(this,
                OBSERVE_FILE_CHANGES | OBSERVE_TRACK_CHANGES | OBSERVE_STAT_CHANGES))
{
        fIconView = new IconView("background", B_LARGE_ICON);

        fFilenameView = _CreateInfo("filename");
        BFont bigFont(be_plain_font);
        bigFont.SetSize(bigFont.Size() * 1.5f);
        fFilenameView->SetFont(&bigFont);

        // Create info views

        BStringView* containerLabel = _CreateLabel("containerLabel",
                B_TRANSLATE("Container"));
        containerLabel->SetHighUIColor(B_PANEL_TEXT_COLOR);
        fContainerInfo = _CreateInfo("container");

        fVideoSeparator = _CreateSeparator();
        fVideoLabel = _CreateLabel("videoLabel", B_TRANSLATE("Video"));
        fVideoFormatInfo = _CreateInfo("videoFormat");
        fVideoConfigInfo = _CreateInfo("videoConfig");
        fDisplayModeLabel = _CreateLabel("displayModeLabel",
                B_TRANSLATE("Display mode"));
        fDisplayModeInfo = _CreateInfo("displayMode");

        fAudioSeparator = _CreateSeparator();
        fAudioLabel = _CreateLabel("audioLabel", B_TRANSLATE("Audio"));
        fAudioLabel->SetHighUIColor(B_PANEL_TEXT_COLOR);
        fAudioFormatInfo = _CreateInfo("audioFormat");
        fAudioConfigInfo = _CreateInfo("audioConfig");

        BStringView* durationLabel = _CreateLabel("durationLabel",
                B_TRANSLATE("Duration"));
        durationLabel->SetHighUIColor(B_PANEL_TEXT_COLOR);
        fDurationInfo = _CreateInfo("duration");

        BStringView* locationLabel = _CreateLabel("locationLabel",
                B_TRANSLATE("Location"));
        locationLabel->SetHighUIColor(B_PANEL_TEXT_COLOR);
        fLocationInfo = _CreateInfoLocation("location");

        fCopyrightSeparator = _CreateSeparator();
        fCopyrightLabel = _CreateLabel("copyrightLabel", B_TRANSLATE("Copyright"));
        fCopyrightInfo = _CreateInfo("copyright");

        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .SetInsets(B_USE_DEFAULT_SPACING)
                .AddGroup(B_HORIZONTAL)
                        .Add(fIconView, 0)
                        .Add(fFilenameView, 1)
                        .End()
                .AddGrid(2, 13)
                        .Add(containerLabel, 0, 0)
                        .Add(fContainerInfo, 1, 0)
                        .Add(fVideoSeparator, 0, 1)
                        .Add(fVideoLabel, 0, 2)
                        .Add(fVideoFormatInfo, 1, 2)
                        .Add(fVideoConfigInfo, 1, 3)
                        .Add(fDisplayModeLabel, 0, 4)
                        .Add(fDisplayModeInfo, 1, 4)
                        .Add(fAudioSeparator, 0, 5)
                        .Add(fAudioLabel, 0, 6)
                        .Add(fAudioFormatInfo, 1, 6)
                        .Add(fAudioConfigInfo, 1, 7)
                        .Add(_CreateSeparator(), 0, 8)
                        .Add(durationLabel, 0, 9)
                        .Add(fDurationInfo, 1, 9)
                        .Add(_CreateSeparator(), 0, 10)
                        .Add(locationLabel, 0, 11)
                        .Add(fLocationInfo, 1, 11)
                        .Add(fCopyrightSeparator, 0, 12)
                        .Add(fCopyrightLabel, 0, 12)
                        .Add(fCopyrightInfo, 1, 12)
                        .SetColumnWeight(0, 0)
                        .SetColumnWeight(1, 1)
                        .SetSpacing(B_USE_DEFAULT_SPACING, 0)
                        .SetExplicitMinSize(BSize(MIN_WIDTH, B_SIZE_UNSET));

        fController->AddListener(fControllerObserver);
        Update();

        UpdateSizeLimits();

        // Move window on screen if needed
        BScreen screen(this);
        if (screen.Frame().bottom < Frame().bottom)
                MoveBy(0, screen.Frame().bottom - Frame().bottom);
        if (screen.Frame().right < Frame().right)
                MoveBy(0, screen.Frame().right - Frame().right);

        Show();
}


InfoWin::~InfoWin()
{
        fController->RemoveListener(fControllerObserver);
        delete fControllerObserver;
}


void
InfoWin::MessageReceived(BMessage* msg)
{
        switch (msg->what) {
                case MSG_CONTROLLER_FILE_FINISHED:
                        break;
                case MSG_CONTROLLER_FILE_CHANGED:
                        Update(INFO_ALL);
                        break;
                case MSG_CONTROLLER_VIDEO_TRACK_CHANGED:
                        Update(INFO_VIDEO | INFO_STATS);
                        break;
                case MSG_CONTROLLER_AUDIO_TRACK_CHANGED:
                        Update(INFO_AUDIO | INFO_STATS);
                        break;
                case MSG_CONTROLLER_VIDEO_STATS_CHANGED:
                case MSG_CONTROLLER_AUDIO_STATS_CHANGED:
                        Update(INFO_STATS);
                        break;
                default:
                        BWindow::MessageReceived(msg);
                        break;
        }
}


bool
InfoWin::QuitRequested()
{
        Hide();
        return false;
}


void
InfoWin::Pulse()
{
        if (IsHidden())
                return;
        Update(INFO_STATS);
}


// #pragma mark -


void
InfoWin::Update(uint32 which)
{
        if (!fController->Lock())
                return;

        if ((which & INFO_FILE) != 0)
                _UpdateFile();

        // video track format information
        if ((which & INFO_VIDEO) != 0)
                _UpdateVideo();

        // audio track format information
        if ((which & INFO_AUDIO) != 0)
                _UpdateAudio();

        // statistics
        if ((which & INFO_STATS) != 0) {
                _UpdateDuration();
                // TODO: demux/video/audio/... perfs (Kb/info)
        }

        if ((which & INFO_TRANSPORT) != 0) {
                // Transport protocol info (file, http, rtsp, ...)
        }

        if ((which & INFO_COPYRIGHT)!=0)
                _UpdateCopyright();

        fController->Unlock();
}


void
InfoWin::_UpdateFile()
{
        bool iconSet = false;
        if (fController->HasFile()) {
                const PlaylistItem* item = fController->Item();
                iconSet = fIconView->SetIcon(item) == B_OK;
                media_file_format fileFormat;
                status_t status = fController->GetFileFormatInfo(&fileFormat);
                if (status == B_OK) {
                        fContainerInfo->SetText(fileFormat.pretty_name);
                        if (!iconSet)
                                iconSet = fIconView->SetIcon(fileFormat.mime_type) == B_OK;
                } else
                        fContainerInfo->SetText(strerror(status));

                BString info;
                if (fController->GetLocation(&info) != B_OK)
                        info = B_TRANSLATE("<unknown>");
                fLocationInfo->SetText(info.String());
                fLocationInfo->SetToolTip(info.String());
                fLocationInfo->CheckAndSetStyleForLink();

                if (fController->GetName(&info) != B_OK || info.IsEmpty())
                        info = B_TRANSLATE("<unnamed media>");
                fFilenameView->SetText(info.String());
                fFilenameView->SetToolTip(info.String());
        } else {
                fFilenameView->SetText(B_TRANSLATE("<no media>"));
                fContainerInfo->SetText("-");
                fLocationInfo->SetText("-");
                fLocationInfo->CheckAndSetStyleForLink();
        }

        if (!iconSet)
                fIconView->SetGenericIcon();
}


void
InfoWin::_UpdateVideo()
{
        bool visible = fController->VideoTrackCount() > 0;
        if (visible) {
                BString info;
                media_format format;
                media_raw_video_format videoFormat = {};
                status_t status = fController->GetEncodedVideoFormat(&format);
                if (status != B_OK) {
                        info << "(" << strerror(status) << ")\n";
                } else if (format.type == B_MEDIA_ENCODED_VIDEO) {
                        videoFormat = format.u.encoded_video.output;
                        media_codec_info mci;
                        status = fController->GetVideoCodecInfo(&mci);
                        if (status != B_OK) {
                                if (format.user_data_type == B_CODEC_TYPE_INFO) {
                                        info << (char *)format.user_data << " "
                                                << B_TRANSLATE("(not supported)");
                                } else
                                        info = strerror(status);
                        } else
                                info << mci.pretty_name; //<< "(" << mci.short_name << ")";
                } else if (format.type == B_MEDIA_RAW_VIDEO) {
                        videoFormat = format.u.raw_video;
                        info << B_TRANSLATE("raw video");
                } else
                        info << B_TRANSLATE("unknown format");

                fVideoFormatInfo->SetText(info.String());

                info.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRIu32 " × %" B_PRIu32,
                        "The '×' is the Unicode multiplication sign U+00D7"),
                        format.Width(), format.Height());

                // encoded has output as 1st field...
                char fpsString[20];
                snprintf(fpsString, sizeof(fpsString), B_TRANSLATE("%.3f fps"),
                        videoFormat.field_rate);
                info << ", " << fpsString;

                fVideoConfigInfo->SetText(info.String());

                if (fController->IsOverlayActive())
                        fDisplayModeInfo->SetText(B_TRANSLATE("Overlay"));
                else
                        fDisplayModeInfo->SetText(B_TRANSLATE("DrawBitmap"));
        }

        fVideoSeparator->SetVisible(visible);
        _SetVisible(fVideoLabel, visible);
        _SetVisible(fVideoFormatInfo, visible);
        _SetVisible(fVideoConfigInfo, visible);
        _SetVisible(fDisplayModeLabel, visible);
        _SetVisible(fDisplayModeInfo, visible);
}


void
InfoWin::_UpdateAudio()
{
        bool visible = fController->AudioTrackCount() > 0;
        if (visible) {
                BString info;
                media_format format;
                media_raw_audio_format audioFormat = {};

                status_t status = fController->GetEncodedAudioFormat(&format);
                if (status != B_OK) {
                        info << "(" << strerror(status) << ")\n";
                } else if (format.type == B_MEDIA_ENCODED_AUDIO) {
                        audioFormat = format.u.encoded_audio.output;
                        media_codec_info mci;
                        status = fController->GetAudioCodecInfo(&mci);
                        if (status != B_OK) {
                                if (format.user_data_type == B_CODEC_TYPE_INFO) {
                                        info << (char *)format.user_data << " "
                                                << B_TRANSLATE("(not supported)");
                                } else
                                        info = strerror(status);
                        } else
                                info = mci.pretty_name;
                } else if (format.type == B_MEDIA_RAW_AUDIO) {
                        audioFormat = format.u.raw_audio;
                        info = B_TRANSLATE("raw audio");
                } else
                        info = B_TRANSLATE("unknown format");

                fAudioFormatInfo->SetText(info.String());

                int bitsPerSample = 8 * (audioFormat.format & media_raw_audio_format::B_AUDIO_SIZE_MASK);
                uint32 channelCount = audioFormat.channel_count;
                float sr = audioFormat.frame_rate;

                info.Truncate(0);

                if (bitsPerSample > 0) {
                        char bitString[20];
                        snprintf(bitString, sizeof(bitString), B_TRANSLATE("%d Bit"),
                                bitsPerSample);
                        info << bitString << " ";
                }

                static BStringFormat channelFormat(B_TRANSLATE(
                        "{0, plural, =1{Mono} =2{Stereo} other{# Channels}}"));
                channelFormat.Format(info, channelCount);

                info << ", ";
                if (sr > 0.0) {
                        char rateString[20];
                        snprintf(rateString, sizeof(rateString),
                                B_TRANSLATE("%.3f kHz"), sr / 1000);
                        info << rateString;
                } else {
                        BString rateString = B_TRANSLATE("%d kHz");
                        rateString.ReplaceFirst("%d", "??");
                        info << rateString;
                }
                if (format.type == B_MEDIA_ENCODED_AUDIO) {
                        float br = format.u.encoded_audio.bit_rate;
                        char string[20] = "";
                        if (br > 0.0)
                                info << ", " << string_for_rate(br, string, sizeof(string));
                }

                fAudioConfigInfo->SetText(info.String());
        }

        fAudioSeparator->SetVisible(visible);
        _SetVisible(fAudioLabel, visible);
        _SetVisible(fAudioFormatInfo, visible);
        _SetVisible(fAudioConfigInfo, visible);
}


void
InfoWin::_UpdateDuration()
{
        if (!fController->HasFile()) {
                fDurationInfo->SetText("-");
                return;
        }

        BString info;

        bigtime_t d = fController->TimeDuration() / 1000;
        bigtime_t v = d / (3600 * 1000);
        d = d % (3600 * 1000);
        bool hours = v > 0;
        if (hours)
                info << v << ":";
        v = d / (60 * 1000);
        d = d % (60 * 1000);
        info << v << ":";
        v = d / 1000;
        if (v < 10)
                info << '0';
        info << v;
        if (hours)
                info << " " << B_TRANSLATE_COMMENT("h", "Hours");
        else
                info << " " << B_TRANSLATE_COMMENT("min", "Minutes");

        fDurationInfo->SetText(info.String());
}


void
InfoWin::_UpdateCopyright()
{
        BString info;

        bool visible = fController->HasFile()
                && fController->GetCopyright(&info) == B_OK && !info.IsEmpty();
        if (visible)
                fCopyrightInfo->SetText(info.String());

        fCopyrightSeparator->SetVisible(visible);
        _SetVisible(fCopyrightLabel, visible);
        _SetVisible(fCopyrightInfo, visible);
}


// #pragma mark -


BStringView*
InfoWin::_CreateLabel(const char* name, const char* label)
{
        static const rgb_color kLabelColor = tint_color(
                ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_3_TINT);

        BStringView* view = new BStringView(name, label);
        view->SetAlignment(B_ALIGN_RIGHT);
        view->SetHighColor(kLabelColor);

        return view;
}


BStringView*
InfoWin::_CreateInfo(const char* name)
{
        BStringView* view = new BStringView(name, "");
        view->SetExplicitMinSize(BSize(200, B_SIZE_UNSET));
        view->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
        view->SetTruncation(B_TRUNCATE_SMART);

        return view;
}


LocationStringView*
InfoWin::_CreateInfoLocation(const char* name)
{
        LocationStringView* view = new LocationStringView(name, "");
        view->SetExplicitMinSize(BSize(200, B_SIZE_UNSET));
        view->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
        view->SetTruncation(B_TRUNCATE_SMART);

        return view;
}


BLayoutItem*
InfoWin::_CreateSeparator()
{
        return BSpaceLayoutItem::CreateVerticalStrut(
                be_control_look->ComposeSpacing(B_USE_HALF_ITEM_SPACING));
}


void
InfoWin::_SetVisible(BView* view, bool visible)
{
        bool hidden = view->IsHidden(view);
        if (hidden && visible)
                view->Show();
        else if (!hidden && !visible)
                view->Hide();
}