/*
 * This file is part of vimix - video live mixer
 *
 * **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
**/

#include <glib.h>
#include <iostream>
#include <iomanip>
#include <thread>
#include <algorithm>

#include <gst/gst.h>

// ImGui
#include "IconsFontAwesome5.h"
#include "Toolkit/ImGuiToolkit.h"
#include "Timeline.h"
#include "gst/gstclock.h"
#include "imgui.h"
#include "imgui_internal.h"

#include "defines.h"
#include "Log.h"
#include "Settings.h"
#include "Toolkit/SystemToolkit.h"
#include "Toolkit/DialogToolkit.h"
#include "Toolkit/GstToolkit.h"
#include "Resource.h"

#include "Mixer.h"
#include "Source/MediaSource.h"
#include "Source/StreamSource.h"
#include "MediaPlayer.h"
#include "ActionManager.h"
#include "Source/SourceCallback.h"
#include "UserInterfaceManager.h"

#include "SourceControlWindow.h"

#define CHECKER_RESOLUTION 5000
class Stream *checker_background_ = new Stream;

typedef struct payload
{
    enum Action {
        FADE_IN,
        FADE_OUT,
        FADE_IN_OUT,
        FADE_OUT_IN,
        CUT,
        CUT_MERGE,
        CUT_ERASE, 
        FLAG_ADD,
        FLAG_REMOVE
    };
    Action  action;
    guint64 drop_time;
    guint64 timing;
    int argument;

    payload()
    {
        action = FADE_IN;
        drop_time = GST_CLOCK_TIME_NONE;
        timing = GST_CLOCK_TIME_NONE;
        argument = 0;
    }

    payload(payload const &pl)
        : action(pl.action)
        , drop_time(pl.drop_time)
        , timing(pl.timing)
        , argument(pl.argument){
          }

    payload(Action a, guint64 t, int arg)
        : action(a), timing(t), argument(arg)
    {
        drop_time = GST_CLOCK_TIME_NONE;
    }

} TimelinePayload;

SourceControlWindow::SourceControlWindow() : WorkspaceWindow("SourceController"),
    min_width_(0.f), h_space_(0.f), v_space_(0.f), scrollbar_(0.f),
    timeline_height_(0.f),  mediaplayer_height_(0.f), buttons_width_(0.f), buttons_height_(0.f),
    play_toggle_request_(false), replay_request_(false), pending_(false),
    active_label_(LABEL_PLAYER_SELECTION), active_selection_(-1),
    selection_context_menu_(false), selection_mediaplayer_(nullptr), selection_target_slower_(0), selection_target_faster_(0),
    mediaplayer_active_(nullptr), mediaplayer_edit_fading_(false), mediaplayer_set_duration_(0),
    mediaplayer_edit_pipeline_(false), mediaplayer_mode_(false), mediaplayer_slider_pressed_(false), mediaplayer_timeline_zoom_(1.f),
    mediaplayer_edit_panel_(false), magnifying_glass(false)
{
    info_.setExtendedStringMode();

    captureFolderDialog = new DialogToolkit::OpenFolderDialog("Capture frame Location");

    // initialize checkerboard background texture
    checker_background_->open("videotestsrc name=bgchecker pattern=checkers-8 ! "
                              "videobalance saturation=0 contrast=1",
                              CHECKER_RESOLUTION, CHECKER_RESOLUTION);
    checker_background_->play(false);
    while (checker_background_->texture() == Resource::getTextureBlack()){
        std::this_thread::sleep_for (std::chrono::milliseconds(30));
        checker_background_->update();
    }
    checker_background_->close();
}


void SourceControlWindow::resetActiveSelection()
{
    info_.reset();
    active_selection_ = -1;
    active_label_ = LABEL_PLAYER_SELECTION;
    play_toggle_request_ = false;
    replay_request_ = false;
    capture_request_ = false;
}

void SourceControlWindow::setVisible(bool on)
{
    magnifying_glass = false;

    // restore workspace to show the window
    if (WorkspaceWindow::clear_workspace_enabled) {
        WorkspaceWindow::restoreWorkspace(on);
        // do not change status if ask to hide (consider user asked to toggle because the window was not visible)
        if (!on)  return;
    }

    if (Settings::application.widget.media_player_view > 0 && Settings::application.widget.media_player_view != Settings::application.current_view) {
        Settings::application.widget.media_player_view = -1;
        on = true;
    }

    // if no selection in the player and in the source selection, show all sources
    if (on && selection_.empty() && Mixer::selection().empty() )
        selection_ = valid_only( Mixer::manager().session()->getDepthSortedList() );

    Settings::application.widget.media_player = on;
}

bool SourceControlWindow::Visible() const
{
    return ( Settings::application.widget.media_player
             && (Settings::application.widget.media_player_view < 0 || Settings::application.widget.media_player_view == Settings::application.current_view )
             );
}

void SourceControlWindow::Update()
{
    WorkspaceWindow::Update();

    SourceList selectedsources;

    if (Settings::application.widget.media_player == false)
        selection_.clear();

    // get new selection or validate previous list if selection was not updated
    selectedsources = Mixer::manager().validate(selection_);
    if (selectedsources.empty() && !Mixer::selection().empty())
        selectedsources = valid_only(Mixer::selection().getCopy());

    // compute number of source selected and playable
    size_t n_source = selectedsources.size();
    size_t n_play = 0;
    for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source){
        if ( (*source)->active() && (*source)->playing() )
            n_play++;
    }

    //
    // Play button or keyboard [Space] was pressed
    //
    if ( play_toggle_request_ ) {

        for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source)
            (*source)->play( n_play < n_source );
        Action::manager().store( n_play < n_source ? "Sources Play" : "Sources Pause" );

        play_toggle_request_ = false;
    }

    //
    // Replay / rewind button or keyboard [CTRL+Space] was pressed
    //
    if ( replay_request_ ) {

        for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source)
            (*source)->replay();

        replay_request_ = false;
    }

    //
    // return from thread for selecting capture folder
    //
    if (captureFolderDialog->closed() && !captureFolderDialog->path().empty())
        // get the folder from this file dialog
        Settings::application.source.capture_path = captureFolderDialog->path();

    //
    // Capture frame on current selection
    //
    Source *s = nullptr;
    if ( selection_.size() == 1 )
        s = selection_.front();
    if ( s != nullptr) {
        // back from capture of FBO: can save file
        if ( capture.isFull() ){
            std::string filename;
            // if sequencial naming of file is selected
            if (Settings::application.source.capture_naming == 0 )
                filename = SystemToolkit::filename_sequential(Settings::application.source.capture_path, s->name(), "png");
            else
                filename = SystemToolkit::filename_dateprefix(Settings::application.source.capture_path, s->name(), "png");
            // save capture and inform user
            capture.save( filename );
            Log::Notify("Frame saved in %s", filename.c_str() );
        }
        // request capture : initiate capture of FBO
        if ( capture_request_ ) {
            capture.captureFramebuffer( s->frame() );
            capture_request_ = false;
        }
    }

    // reset on session change
    static Session *__session = nullptr;
    if ( Mixer::manager().session() != __session ) {
        __session = Mixer::manager().session();
        resetActiveSelection();
    }
}


FrameBuffer *SourceControlWindow::renderedFramebuffer() const
{
    if (selection_.size() == 1)
        return selection_.front()->frame();
    return nullptr;
}

void SourceControlWindow::Render()
{
    // estimate window size
    const ImGuiContext& g = *GImGui;
    h_space_ = g.Style.ItemInnerSpacing.x;
    v_space_ = g.Style.FramePadding.y;
    buttons_height_  = g.FontSize + v_space_ * 4.0f ;
    buttons_width_  = g.FontSize * 8.0f ;
    min_width_ = 6.f * buttons_height_;
    timeline_height_ = (g.FontSize + v_space_) * 2.0f ; // double line for each timeline
    scrollbar_ = g.Style.ScrollbarSize;
    // all together: 1 title bar + spacing + 1 toolbar + spacing + 2 timelines + scrollbar
    mediaplayer_height_ = buttons_height_ + 2.f * timeline_height_ + 2.f * scrollbar_ +  v_space_;

    // constraint size
    ImGui::SetNextWindowSizeConstraints(ImVec2(min_width_, 2.f * mediaplayer_height_), ImVec2(FLT_MAX, FLT_MAX));
    ImGui::SetNextWindowPos(ImVec2(1180, 400), ImGuiCond_FirstUseEver);
    ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);

    // Window named "SourceController" at instanciation
    if ( !ImGui::Begin(name_, &Settings::application.widget.media_player,
                       ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar |  ImGuiWindowFlags_NoCollapse ))
    {
        ImGui::End();
        return;
    }

    // menu (no title bar)
    if (ImGui::BeginMenuBar())
    {
        if (ImGuiToolkit::IconButton(4,16)){
            Settings::application.widget.media_player = false;
        }
        if (ImGui::BeginMenu(IMGUI_TITLE_MEDIAPLAYER))
        {
            // Preview and output menu
            if (ImGuiToolkit::MenuItemIcon(ICON_PREVIEW, MENU_PREVIEW, SHORTCUT_PREVIEW_SRC, false, renderedFramebuffer()!=nullptr) )
                UserInterface::manager().show_preview = UserInterface::PREVIEW_SOURCE;
            //
            // Menu section for play control
            //
            if (ImGui::MenuItem( MENU_PLAY_BEGIN, NULL, nullptr, !selection_.empty()))
                replay_request_ = true;
            if (ImGui::MenuItem( MENU_PLAY_PAUSE, SHORTCUT_PLAY_PAUSE, nullptr, !selection_.empty()))
                play_toggle_request_ = true;

            ImGui::Separator();
            //
            // Menu section for display
            //
            if (ImGui::BeginMenu( ICON_FA_IMAGE "  Displayed image"))
            {
                if (ImGuiToolkit::MenuItemIcon(8, 9, " Render"))
                    Settings::application.widget.media_player_slider = 0.0;
                if (ImGuiToolkit::MenuItemIcon(6, 9, " Split"))
                    Settings::application.widget.media_player_slider = 0.5;
                if (ImGuiToolkit::MenuItemIcon(7, 9, " Input"))
                    Settings::application.widget.media_player_slider = 1.0;
                ImGui::EndMenu();
            }
            if (ImGui::MenuItem( ICON_FA_TH "  List all")) {
                selection_.clear();
                resetActiveSelection();
                Mixer::manager().unsetCurrentSource();
                Mixer::selection().clear();
                selection_ = valid_only(Mixer::manager().session()->getDepthSortedList());
            }
            if (ImGui::MenuItem( ICON_FA_MINUS "  Clear")) {
                selection_.clear();
                resetActiveSelection();
                Mixer::manager().unsetCurrentSource();
                Mixer::selection().clear();
            }
            //
            // Menu section for window management
            //
            ImGui::Separator();
            bool pinned = Settings::application.widget.media_player_view == Settings::application.current_view;
            std::string menutext = std::string( ICON_FA_MAP_PIN "    Stick to ") + Settings::application.views[Settings::application.current_view].name + " view";
            if ( ImGui::MenuItem( menutext.c_str(), nullptr, &pinned) ){
                if (pinned)
                    Settings::application.widget.media_player_view = Settings::application.current_view;
                else
                    Settings::application.widget.media_player_view = -1;
            }
            if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_PLAYER) ) {
                Settings::application.widget.media_player = false;
                selection_.clear();
            }

            ImGui::EndMenu();
        }

        if (ImGui::BeginMenu(active_label_.c_str()))
        {
            // info on selection status
            size_t N = Mixer::manager().session()->numBatch();
            bool enabled = !selection_.empty() && active_selection_ < 0;

            // Menu : Dynamic selection
            if (ImGui::MenuItem(LABEL_PLAYER_SELECTION, NULL, active_selection_ < 0))
                resetActiveSelection();
            // Menu : list of selections
            if (N>0) {
                for (size_t i = 0 ; i < N; ++i)
                {
                    std::string label = std::string(LABEL_PLAYER_BATCH) + std::to_string(i);
                    if (ImGui::MenuItem( label.c_str(), NULL, active_selection_ == (int) i ))
                    {
                        active_selection_ = i;
                        active_label_ = label;
                        info_.reset();
                    }
                }
            }
            // Menu : store selection
            ImGui::Separator();
            if (ImGui::MenuItem(ICON_FA_PLUS_CIRCLE LABEL_PLAYER_BATCH_ADD, NULL, false, enabled))
            {
                active_selection_ = N;
                active_label_ = std::string(LABEL_PLAYER_BATCH) + std::to_string(active_selection_);
                Mixer::manager().session()->addBatch( ids(selection_) );
                info_.reset();
            }

            ImGui::EndMenu();
        }

        //
        // Menu for capture frame
        //
        if ( ImGui::BeginMenu(ICON_FA_ARROW_ALT_CIRCLE_DOWN "  Capture", selection_.size() == 1 ) )
        {
            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_CAPTURE, 0.8f));
            if (ImGui::MenuItem( MENU_CAPTUREFRAME, "F10" ))
                capture_request_ = true;
            ImGui::PopStyleColor(1);

            // separator and hack for extending menu width
            ImGui::Separator();
            ImGui::MenuItem("Settings ", nullptr, false, false);
            float combo_width = ImGui::GetTextLineHeightWithSpacing() * 7.f;

            // path menu selection
            static char* name_path[4] = { nullptr };
            if ( name_path[0] == nullptr ) {
                for (int i = 0; i < 4; ++i)
                    name_path[i] = (char *) malloc( 1024 * sizeof(char));
                snprintf( name_path[1], 1024, "%s", ICON_FA_HOME " Home");
                snprintf( name_path[2], 1024, "%s", ICON_FA_FOLDER " File location");
                snprintf( name_path[3], 1024, "%s", ICON_FA_FOLDER_PLUS " Select");
            }
            if (Settings::application.source.capture_path.empty())
                Settings::application.source.capture_path = SystemToolkit::home_path();
            snprintf( name_path[0], 1024, "%s", Settings::application.source.capture_path.c_str());
            int selected_path = 0;
            ImGui::SetNextItemWidth(combo_width);
            ImGui::Combo("##Path", &selected_path, name_path, 4);
            if (selected_path > 2)
                captureFolderDialog->open();
            else if (selected_path > 1) {
                // file location of media player
                if (mediaplayer_active_)
                    Settings::application.source.capture_path = SystemToolkit::path_filename( mediaplayer_active_->filename() );
                // else file location of session
                else
                    Settings::application.source.capture_path = SystemToolkit::path_filename( Mixer::manager().session()->filename() );
            }
            else if (selected_path > 0)
                Settings::application.source.capture_path = SystemToolkit::home_path();
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGuiToolkit::TextButton("Path"))
                Settings::application.source.capture_path = SystemToolkit::home_path();

            // offer to open folder location
            ImVec2 draw_pos = ImGui::GetCursorPos();
            ImGui::SetCursorPos(draw_pos + ImVec2(combo_width + 3.f * ImGui::GetTextLineHeight() , -ImGui::GetFrameHeight()) );
            if (ImGuiToolkit::IconButton( 3, 5, Settings::application.source.capture_path.c_str()))
                SystemToolkit::open(Settings::application.source.capture_path);
            ImGui::SetCursorPos(draw_pos);

            // Naming menu selection
            static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN "  Sequential", ICON_FA_CALENDAR "  Date prefix" };
            ImGui::SetNextItemWidth(combo_width);
            ImGui::Combo("##Filename", &Settings::application.source.capture_naming, naming_style, IM_ARRAYSIZE(naming_style));
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGuiToolkit::TextButton("Filename"))
                Settings::application.source.capture_naming = 0;

            ImGui::EndMenu();
        }

        //
        // Menu for video Mediaplayer control
        //
        if (ImGui::BeginMenu(ICON_FA_FILM " Timeline", mediaplayer_active_) )
        {
            if ( mediaplayer_active_->isImage()) {
                if ( ImGuiToolkit::MenuItemIcon(1, 14, "Remove")){
                    // set empty timeline
                    Timeline tl;
                    mediaplayer_active_->setTimeline(tl);
                    mediaplayer_active_->play(false);
                    // re-open the image with NO timeline
                    mediaplayer_active_->reopen();
                }

                if ( ImGui::MenuItem(ICON_FA_HOURGLASS_HALF "  Duration")){
                    mediaplayer_set_duration_ = 1;
                }
            }

            if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE "  Reset")){
                mediaplayer_timeline_zoom_ = 1.f;
                mediaplayer_active_->timeline()->clearFading();
                mediaplayer_active_->timeline()->clearGaps();
                mediaplayer_active_->timeline()->clearFlags();
                if (!mediaplayer_active_->videoEffect().empty() && mediaplayer_active_->videoEffectAvailable())
                    mediaplayer_active_->setVideoEffect("");
                std::ostringstream oss;
                oss << SystemToolkit::base_filename( mediaplayer_active_->filename() );
                oss << ": Reset timeline";
                Action::manager().store(oss.str());
            }

            bool _alpha_fading = mediaplayer_active_->timelineFadingMode()
                                 == MediaPlayer::FADING_ALPHA;
            if (ImGui::MenuItem(ICON_FA_FONT "  Alpha fading", NULL, &_alpha_fading)) {
                mediaplayer_active_->setTimelineFadingMode(
                    _alpha_fading ? MediaPlayer::FADING_ALPHA : MediaPlayer::FADING_COLOR);
            }

            if (ImGuiToolkit::BeginMenuIcon(4, 13, "Metronome"))
            {
                Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome();
                bool active = sync == Metronome::SYNC_NONE;
                if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", NULL, active ))
                    mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE);
                active = sync == Metronome::SYNC_BEAT;
                if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active ))
                    mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT);
                active = sync == Metronome::SYNC_PHASE;
                if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active ))
                    mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE);
                ImGui::EndMenu();
            }

            ImGui::Separator();
            if (ImGuiToolkit::MenuItemIcon(16, 16, "Gstreamer effect", nullptr,
                                           !mediaplayer_active_->videoEffect().empty(),
                                           mediaplayer_active_->videoEffectAvailable()) )
                mediaplayer_edit_pipeline_ = true;

            ImGui::EndMenu();
        }

        // button to activate the magnifying glass at top right corner
        ImVec2 p = g.CurrentWindow->Pos;
        p.x += g.CurrentWindow->Size.x - 2.1f * g.FontSize;
        if (g.CurrentWindow->DC.CursorPos.x < p.x)
        {
            bool enabled = (active_selection_ < 0 && selection_.size() == 1);
            ImGui::PushStyleColor(ImGuiCol_Text, enabled ? ImGui::GetColorU32(ImGuiCol_Text) : ImGui::GetColorU32(ImGuiCol_TextDisabled));
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f));
            ImGui::SetCursorScreenPos(p);
            ImGuiToolkit::ButtonToggle( ICON_FA_SEARCH, &magnifying_glass);
            // FORCE DISABLE IF NOT ENABLED 
            if (!enabled) 
                magnifying_glass = false;
            ImGui::PopStyleColor(2);
        }

        ImGui::EndMenuBar();
    }

    // disable magnifying glass if window is deactivated
    if ( g.NavWindow != g.CurrentWindow )
        magnifying_glass = false;

    // reset mediaplayer ptr
    mediaplayer_active_ = nullptr;

    // render with appropriate method
    if (active_selection_ > -1)
        RenderSelection(active_selection_);
    else
        RenderSelectedSources();

    ImGui::End();

}

void DrawTimeScale(const char* label, guint64 duration, double width_ratio)
{
    // get window
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems)
        return;

    // get style & id
    const ImGuiContext& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const ImGuiID id = window->GetID(label);

    const ImVec2 timeline_size( static_cast<float>( static_cast<double>(duration) * width_ratio ), 2.f * g.FontSize);

    const ImVec2 pos = window->DC.CursorPos;
    const ImVec2 frame_size( timeline_size.x + 2.f * style.FramePadding.x, timeline_size.y + style.FramePadding.y);
    const ImRect bbox(pos, pos + frame_size);
    ImGui::ItemSize(frame_size, style.FramePadding.y);
    if (!ImGui::ItemAdd(bbox, id))
        return;

    const ImVec2 timescale_pos = pos + ImVec2(style.FramePadding.x, 0.f);

    ImGuiToolkit::RenderTimeline(timescale_pos, timescale_pos + timeline_size, 0, duration, 1000, true);

}

bool EditTimeline(const char *label,
                  Timeline *tl,
                  int edit_mode,
                  bool *released,
                  const ImVec2 size)
{
    const float values_min = 0.f;
    const float values_max = 1.f;
    const guint64 begin = tl->begin();
    const guint64 end = tl->end();

    bool cursor_dot = edit_mode == 1;
    int  cursor_flag = -1;
    Timeline _tl;
    bool array_changed = false;
    float *lines_array = tl->fadingArray();
    float *gaps_array = tl->gapsArray();
    float *flags_array = tl->flagsArray();

    // get window
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems)
        return false;

    // capture coordinates before any draw or action
    const ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
    ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y);

    // get id
    const ImGuiID id = window->GetID(label);

    // add item
    ImVec2 pos = window->DC.CursorPos;
    ImRect bbox(pos, pos + size);
    ImGui::ItemSize(size);
    if (!ImGui::ItemAdd(bbox, id))
        return false;

    *released = false;

    // read user input and activate widget
    const bool mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Left);
    bool hovered = ImGui::ItemHoverable(bbox, id);
    bool temp_input_is_active = ImGui::TempInputIsActive(id);
    if (!temp_input_is_active)
    {
        const bool focus_requested = ImGui::FocusableItemRegister(window, id);
        if (focus_requested || (hovered && mouse_press) )
        {
            ImGui::SetActiveID(id, window);
            ImGui::SetFocusID(id, window);
            ImGui::FocusWindow(window);
        }
    }
    else
        return false;

    const ImGuiContext& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const float _h_space = style.WindowPadding.x;
    ImVec4 bg_color = hovered ? style.Colors[ImGuiCol_FrameBgHovered] : style.Colors[ImGuiCol_FrameBg];

    // prepare index
    double x = (mouse_pos_in_canvas.x - _h_space) / (size.x - 2.f * _h_space);
    size_t index = CLAMP( (int) floor(static_cast<double>(MAX_TIMELINE_ARRAY) * x), 0, MAX_TIMELINE_ARRAY);
    char cursor_text[64];
    guint64 time = begin + (index * end) / static_cast<guint64>(MAX_TIMELINE_ARRAY);
    static guint64 removed_flag_time = 0;
    static int removed_flag_type = -1;

    // enter edit if widget is active
    if (ImGui::GetActiveID() == id) {

        bg_color = style.Colors[ImGuiCol_FrameBgActive];

        // keep active area while mouse is pressed
        static bool active = false;
        static size_t previous_index = UINT32_MAX;
        if (mouse_press)
        {
            float val = mouse_pos_in_canvas.y / bbox.GetHeight();
            val = CLAMP( (val * (values_max-values_min)) + values_min, values_min, values_max);

            if (previous_index == UINT32_MAX)
                previous_index = index;

            const size_t left = MIN(previous_index, index);
            const size_t right = MAX(previous_index, index);

            if (edit_mode == 0) {
                static float target_value = values_min;

                // toggle value histo
                if (!active) {
                    target_value = gaps_array[index] > 0.f ? 0.f : 1.f;
                    active = true;
                }

                for (size_t i = left; i < right; ++i)
                    gaps_array[i] = target_value;
            }
            else if (edit_mode == 1) {
                const float target_value =  values_max - val;

                for (size_t i = left; i < right; ++i)
                    lines_array[i] = target_value;

            }
            else if (edit_mode == 2) {
                if (!active) {
                    // remove flag on mouse press   
                    if ( tl->isFlagged(time) ) {
                        removed_flag_time = time; 
                        removed_flag_type = tl->flagTypeAt(time);
                        tl->removeFlagAt(time);
                    }
                    // add flag on mouse release   
                    else
                        active = true;  
                }
                else if ( !tl->isFlagged(time) ) {
                    cursor_flag = tl->flagTypeAt(time);; 
                }
            }

            previous_index = index;
            array_changed = true;
        }
        // release active widget on mouse release
        else {
            // add flag on mouse release   
            if (edit_mode == 2 && active) {
                // exception: if flag was removed at same time, do not add it back
                if ( removed_flag_time != time ) {
                    if (removed_flag_type >= 0)
                        tl->addFlagAt(time, removed_flag_type);
                    else
                        tl->addFlagAt(time, Settings::application.widget.media_player_timeline_flag);
                }
                removed_flag_time = 0;
                removed_flag_type = -1;
            }
            
            active = false;
            ImGui::ClearActiveID();
            previous_index = UINT32_MAX;
            *released = true;
        }

    }

    // accept drops on timeline plot-histogram
    else if (ImGui::BeginDragDropTarget()) {
        const ImGuiPayload *tmp = ImGui::GetDragDropPayload();
        if (tmp && tmp->IsDataType("DND_TIMELINE") && tmp->Data != nullptr) {

            // get payload data (tested above)
            TimelinePayload *pl = (TimelinePayload *) tmp->Data;

            // fake mouse hovered
            hovered = true;
            cursor_dot = false;

            // dragged onto the timeline : apply changes on local copy
            switch (pl->action) {
            case TimelinePayload::CUT:
                _tl = *tl;
                _tl.cut(time, (bool) pl->argument);
                gaps_array = _tl.gapsArray();
                break;
            case TimelinePayload::CUT_MERGE:
                _tl = *tl;
                _tl.mergeGapstAt(time);
                gaps_array = _tl.gapsArray();
                break;
            case TimelinePayload::CUT_ERASE:
                _tl = *tl;
                _tl.removeGaptAt(time);
                gaps_array = _tl.gapsArray();
                break;
            case TimelinePayload::FADE_IN:
                _tl = *tl;
                _tl.fadeIn(time, pl->timing, (Timeline::FadingCurve) pl->argument);
                lines_array = _tl.fadingArray();
                break;
            case TimelinePayload::FADE_OUT:
                _tl = *tl;
                _tl.fadeOut(time, pl->timing, (Timeline::FadingCurve) pl->argument);
                lines_array = _tl.fadingArray();
                break;
            case TimelinePayload::FADE_IN_OUT:
                _tl = *tl;
                _tl.fadeInOutRange(time, pl->timing, true, (Timeline::FadingCurve) pl->argument);
                lines_array = _tl.fadingArray();
                break;
            case TimelinePayload::FADE_OUT_IN:
                _tl = *tl;
                _tl.fadeInOutRange(time, pl->timing, false, (Timeline::FadingCurve) pl->argument);
                lines_array = _tl.fadingArray();
                break;
            case TimelinePayload::FLAG_ADD:
                _tl = *tl;
                _tl.addFlagAt(time, pl->argument);
                flags_array = _tl.flagsArray();
                break;
            case TimelinePayload::FLAG_REMOVE:
                _tl = *tl;
                _tl.removeFlagAt(time);
                flags_array = _tl.flagsArray();
                break;
            default:
                break;
            }
        }

        // dropped into the timeline : confirm change to timeline
        if (ImGui::AcceptDragDropPayload("DND_TIMELINE", ImGuiDragDropFlags_AcceptNoDrawDefaultRect)) {
            // copy temporary timeline into given timeline
            *tl = _tl;
            // like a mouse release
            *released = true;
        }
        ImGui::EndDragDropTarget();
    }
    else {
        if ( edit_mode == 2 && tl->isFlagged(time) ) {
            cursor_flag = tl->flagTypeAt(time);; 
        }
    }

    // back to draw
    ImGui::SetCursorScreenPos(canvas_pos);

    // plot gaps (with frame)
    ImGui::PushStyleColor(ImGuiCol_FrameBg, bg_color);
    ImGui::PushStyleColor(ImGuiCol_PlotHistogram, style.Colors[ImGuiCol_ModalWindowDimBg]); // a dark color
    char buf[128];
    snprintf(buf, 128, "##Histo%s", label);
    ImGui::PlotHistogram(buf, gaps_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size);
    ImGui::PopStyleColor(2);

    // back to draw
    ImGui::SetCursorScreenPos(canvas_pos);

    // plot lines (transparent background)
    ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));
    snprintf(buf, 128, "##Lines%s", label);
    ImGui::PlotLines(buf, lines_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size);
    ImGui::PopStyleColor(1);

    // back to draw
    ImGui::SetCursorScreenPos(canvas_pos);

    // plot flags (transparent background)
    ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));
    ImGui::PushStyleColor(ImGuiCol_PlotHistogram, style.Colors[ImGuiCol_TabActive]); // cursor color
    snprintf(buf, 128, "##Flags%s", label);    
    ImGui::PlotHistogram(buf, flags_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size);
    ImGui::PopStyleColor(2);

    // draw the cursor
    if (hovered) {

        ImFormatString(cursor_text, IM_ARRAYSIZE(cursor_text), "%s",
                       GstToolkit::time_to_string(time).c_str());

        // prepare color and text
        const ImU32 cur_color = ImGui::GetColorU32(ImGuiCol_CheckMark);
        ImGui::PushStyleColor(ImGuiCol_Text, cur_color);
        ImVec2 label_size = ImGui::CalcTextSize(cursor_text, NULL);

        // render cursor depending on action
        mouse_pos_in_canvas.x = CLAMP(mouse_pos_in_canvas.x, _h_space, size.x - _h_space);
        ImVec2 cursor_pos = canvas_pos;
        if (cursor_dot) {
            cursor_pos = cursor_pos + mouse_pos_in_canvas;
            window->DrawList->AddCircleFilled( cursor_pos, 3.f, cur_color, 8);
        }
        else if (cursor_flag >= 0) {
            cursor_pos = cursor_pos + ImVec2(mouse_pos_in_canvas.x, 12.f);
            window->DrawList->AddLine( cursor_pos, cursor_pos + ImVec2(0.f, size.y - 8.f), cur_color);
            _drawIcon(cursor_pos - ImVec2(2.f, 1.f), 11 + cursor_flag, 6, true, window);
        } 
        else {
            cursor_pos = cursor_pos + ImVec2(mouse_pos_in_canvas.x, 4.f);
            window->DrawList->AddLine( cursor_pos, cursor_pos + ImVec2(0.f, size.y - 8.f), cur_color);
        }

        // draw text
        cursor_pos.y = canvas_pos.y + size.y - label_size.y - 1.f;
        if (mouse_pos_in_canvas.x > label_size.x * 1.5f + 2.f * _h_space)
            cursor_pos.x -= label_size.x + _h_space;
        else
            cursor_pos.x += _h_space + style.WindowPadding.x;
        ImGui::RenderTextClipped(cursor_pos, cursor_pos + label_size, cursor_text, NULL, &label_size);

        ImGui::PopStyleColor(1);
    }

    return array_changed;
}

bool TimelineSlider (const char* label, guint64 *time, TimeInterval *flag, Timeline *tl, const float width)
{
    // get window
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems)
        return false;

    // get style & id
    const ImGuiContext& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const float fontsize = g.FontSize;
    const ImGuiID id = window->GetID(label);

    //
    // PREPARE data structures
    //

    // widget bounding box
    const float height = 2.f * (fontsize + style.FramePadding.y);
    ImVec2 pos = window->DC.CursorPos;
    ImVec2 size = ImVec2(width, height);
    ImRect bbox(pos, pos + size);
    ImGui::ItemSize(size, style.FramePadding.y);
    if (!ImGui::ItemAdd(bbox, id))
        return false;

    // cursor size
    const float cursor_width = 0.5f * fontsize;

    // TIMELINE is inside the bbox, in a slightly smaller bounding box
    ImRect timeline_bbox(bbox);
    timeline_bbox.Expand( ImVec2() - style.FramePadding );

    // SLIDER is inside the timeline
    ImRect slider_bbox( timeline_bbox.GetTL() + ImVec2(-cursor_width + 2.f, cursor_width + 4.f ), timeline_bbox.GetBR() + ImVec2( cursor_width - 2.f, 0.f ) );

    // units conversion: from time to float (calculation made with higher precision first)
    float time_ = static_cast<float> ( static_cast<double>(*time - tl->begin()) / static_cast<double>(tl->duration()) );

    //
    // GET INPUT 
    //

    // read user input from system
    bool left_mouse_press = false;
    bool hovered = ImGui::ItemHoverable(bbox, id);
    bool temp_input_is_active = ImGui::TempInputIsActive(id);

    if (!temp_input_is_active)
    {
        const bool focus_requested = ImGui::FocusableItemRegister(window, id);
        left_mouse_press = hovered && ImGui::IsMouseDown(ImGuiMouseButton_Left);
        if (focus_requested || left_mouse_press || g.NavActivateId == id || g.NavInputId == id)
        {
            ImGui::SetActiveID(id, window);
            ImGui::SetFocusID(id, window);
            ImGui::FocusWindow(window);
        }
    }

    //
    // BACKGROUND
    //

    // Render the bounding box
    const ImU32 frame_col = ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
    ImGui::RenderFrame(bbox.Min, bbox.Max, frame_col, true, style.FrameRounding);

    // render the timeline
    ImGuiToolkit::RenderTimeline(timeline_bbox.Min, timeline_bbox.Max, tl->begin(), tl->end(), tl->step());

    //
    // FLAGS 
    //
    int index = 0;
    bool flag_pressed = false;
    const TimeIntervalSet flags = tl->flags();
    for (const auto &flag_Interval : flags) {

        // set position in screen corresponding to flag time
        GstClockTime flag_time = flag_Interval.midpoint();
        float flag_pos_ = static_cast<float> ( static_cast<double>(flag_time - tl->begin()) / static_cast<double>(tl->duration()) );
        ImVec2 draw_pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), flag_pos_);
        draw_pos -= ImVec2(2.f, -3.f); 

        // simulate Button behavior : if mouse hovering flag and mouse pressed
        ImRect bb(draw_pos, draw_pos + ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing()));
        bool hovered = ImGui::ItemHoverable(bb, id);
        if (hovered && left_mouse_press) {
            flag_pressed = true;
            *flag = flag_Interval;
            ImGui::MarkItemEdited(id);
        }

        // icon depends on flag type & color on hovered
        ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_NavHighlight) );
        _drawIcon(draw_pos, 11 + flag_Interval.type, 6, hovered, window);
        ImGui::PopStyleColor();

        // show time when hovering
        if (hovered) 
            ImGui::SetTooltip(ICON_FA_FLAG " %d (%s)", index, GstToolkit::time_to_string(flag_time).c_str());

        ++index;
    }   

    //
    // CURSOR 
    //
    bool cursor_pressed = false;
    ImRect grab_slider_bb;
    ImU32 grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrab);

    if (!flag_pressed) 
    {
        float time_slider = time_ * 10.f; // x 10 precision on grab
        float time_zero = 0.f;
        float time_end = 10.f;
        cursor_pressed = ImGui::SliderBehavior(slider_bbox, id, ImGuiDataType_Float, &time_slider, &time_zero,
                                                &time_end, "%.2f", 1.f, ImGuiSliderFlags_None, &grab_slider_bb);

        if (cursor_pressed){

            *time = static_cast<guint64> ( 0.1 * static_cast<double>(time_slider) * static_cast<double>(tl->duration()) );
            if (tl->first() != GST_CLOCK_TIME_NONE)
                *time -= tl->first();
            grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrabActive);

            ImGui::MarkItemEdited(id);
        }
        
    }

    // draw slider grab handle
    if (grab_slider_bb.Max.x > grab_slider_bb.Min.x) {
        window->DrawList->AddRectFilled(grab_slider_bb.Min, grab_slider_bb.Max, grab_slider_color, style.GrabRounding);
    }

    // draw the cursor
    pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), time_) - ImVec2(cursor_width, 2.f);
    ImGui::RenderArrow(window->DrawList, pos, ImGui::GetColorU32(ImGuiCol_SliderGrab), ImGuiDir_Up);

    return cursor_pressed;
}


std::list< std::pair<float, guint64> > DrawTimeline(const char* label, Timeline *timeline, guint64 time,
                                                    double width_ratio, float height)
{
    std::list< std::pair<float, guint64> > ret;

    // get window
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems)
        return ret;

    // get style & id
    const ImGuiContext& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const float fontsize = g.FontSize;
    const ImGuiID id = window->GetID(label);

    //
    // FIRST PREPARE ALL data structures
    //

    // fixed elements of timeline
    float *lines_array = timeline->fadingArray();
    float *flags_array = timeline->flagsArray();
    const guint64 duration = timeline->sectionsDuration();
    TimeIntervalSet se = timeline->sections();
    const ImVec2 timeline_size( static_cast<float>( static_cast<double>(duration) * width_ratio ), 2.f * fontsize);

    // widget bounding box
    const ImVec2 frame_pos = window->DC.CursorPos;
    const ImVec2 frame_size( timeline_size.x + 2.f * style.FramePadding.x, height);
    const ImRect bbox(frame_pos, frame_pos + frame_size);
    ImGui::ItemSize(frame_size, style.FramePadding.y);
    if (!ImGui::ItemAdd(bbox, id))
        return ret;

    // capture hover to avoid tooltip on plotlines
    ImGui::ItemHoverable(bbox, id);

    // cursor size
    const float cursor_width = 0.5f * fontsize;

    // TIMELINE is inside the bbox, at the bottom
    const ImVec2 timeline_pos = frame_pos + ImVec2(style.FramePadding.x, frame_size.y - timeline_size.y -style.FramePadding.y);
    const ImRect timeline_bbox( timeline_pos, timeline_pos + timeline_size);

    // PLOT of opacity is inside the bbox, at the top
    const ImVec2 plot_pos = frame_pos + style.FramePadding;
    const ImRect plot_bbox( plot_pos, plot_pos + ImVec2(timeline_size.x, frame_size.y - 4.f * style.FramePadding.y - timeline_size.y -1));

    //
    // THIRD RENDER
    //

    // Render the bounding box frame
    ImGui::RenderFrame(bbox.Min, bbox.Max, ImGui::GetColorU32(ImGuiCol_FrameBgActive), true, style.FrameRounding);

    // loop over sections of sources' timelines
    guint64 d = 0;
    guint64 e = 0;
    ImVec2 section_bbox_min = timeline_bbox.Min;
    for (auto section = se.begin(); section != se.end(); ++section) {

        // increment duration to adjust horizontal position
        d += section->duration();
        e = section->end;
        const float percent = static_cast<float>(d) / static_cast<float>(duration) ;
        ImVec2 section_bbox_max = ImLerp(timeline_bbox.GetBL(), timeline_bbox.GetBR(), percent);

        // adjust bbox of section and render a timeline
        ImRect section_bbox(section_bbox_min, section_bbox_max);
        // render the timeline
        ImGuiToolkit::RenderTimeline(section_bbox_min, section_bbox_max, 
            section->begin, section->end, timeline->step());        

        //
        // FLAGS 
        //
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f));
        ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_Button) );
        int index = 0;
        bool flag_pressed = false;
        const TimeIntervalSet flags = timeline->flags();
        for (const auto &flag_Interval : flags) {

            // set position in screen corresponding to flag time
            GstClockTime flag_time = flag_Interval.midpoint();
            float flag_pos_ = static_cast<float> ( static_cast<double>(flag_time - timeline->begin()) / static_cast<double>(timeline->duration()) );
            ImVec2 draw_pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), flag_pos_);
            draw_pos -= ImVec2(2.f, -3.f); 

            // icon depends on flag type & color on hovered
            _drawIcon(draw_pos, 11 + flag_Interval.type, 6, true, window);

            ++index;
        }   
        ImGui::PopStyleColor();
        ImGui::PopStyleVar(1);

        // draw the cursor
        float time_ = static_cast<float> ( static_cast<double>(time - section->begin) / static_cast<double>(section->duration()) );
        if ( time_ > -FLT_EPSILON && time_ < 1.f ) {
            ImVec2 pos = ImLerp(section_bbox.GetTL(), section_bbox.GetTR(), time_) - ImVec2(cursor_width, 2.f);
            ImGui::RenderArrow(window->DrawList, pos, ImGui::GetColorU32(ImGuiCol_SliderGrab), ImGuiDir_Up);
        }

        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));

        // draw plot of lines
        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));
        ImGui::SetCursorScreenPos(ImVec2(section_bbox_min.x, plot_bbox.Min.y));
        // find the index in timeline array of the section start time
        size_t i = timeline->fadingIndexAt(section->begin);
        // number of values is the index after end time of section (+1), minus the start index
        size_t values_count = 1 + timeline->fadingIndexAt(section->end) - i;
        ImGui::PlotLines("##linessection", lines_array + i, values_count, 
            0, NULL, 0.f, 1.f, 
            ImVec2(section_bbox.GetWidth(), plot_bbox.GetHeight()));
        ImGui::PopStyleColor(1);

        // back to draw
        ImGui::SetCursorScreenPos(ImVec2(section_bbox_min.x, plot_bbox.Min.y));

        // plot flags (transparent background)
        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));
        ImGui::PushStyleColor(ImGuiCol_PlotHistogram, style.Colors[ImGuiCol_TabActive]); // cursor color
        ImGui::PlotHistogram("##flagssection", flags_array, MAX_TIMELINE_ARRAY, 
            0, NULL, 0.f, 1.f, 
            ImVec2(section_bbox.GetWidth(), plot_bbox.GetHeight()));
        ImGui::PopStyleColor(2);

        ImGui::PopStyleVar(1);

        // detect if there was a gap before
        if (i > 0)
            window->DrawList->AddRectFilled(ImVec2(section_bbox_min.x -2.f, plot_bbox.Min.y), ImVec2(section_bbox_min.x + 2.f, plot_bbox.Max.y), ImGui::GetColorU32(ImGuiCol_TitleBg));

        ret.push_back( std::pair<float, guint64>(section_bbox_min.x,section->begin ) );
        ret.push_back( std::pair<float, guint64>(section_bbox_max.x,section->end ) );

        // iterate: next bbox of section starts at end of current
        section_bbox_min.x = section_bbox_max.x;
    }

    // detect if there is a gap after
    if (e < timeline->duration())
        window->DrawList->AddRectFilled(ImVec2(section_bbox_min.x -2.f, plot_bbox.Min.y), ImVec2(section_bbox_min.x + 2.f, plot_bbox.Max.y), ImGui::GetColorU32(ImGuiCol_TitleBg));

    return ret;
}

void SourceControlWindow::RenderSelection(size_t i)
{
    ImVec2 top = ImGui::GetCursorScreenPos();
    ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_);
    ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_);

    selection_ = Mixer::manager().session()->getBatch(i);
    int numsources = selection_.size();

    // no source selected
    if (numsources < 1)
    {
        ///
        /// Centered text
        ///
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f));
        ImVec2 center = rendersize * ImVec2(0.5f, 0.5f);
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC);
        center.x -= ImGui::GetTextLineHeight() * 2.f;
        ImGui::SetCursorScreenPos(top + center);
        ImGui::Text("Empty selection");
        ImGui::PopFont();
        ImGui::PopStyleColor(1);
    }
    else {
        ///
        /// Sources LIST
        ///
        ///

        // get max duration and max frame width
        GstClockTime maxduration = 0;
        std::list<guint64> durations;
        float maxframewidth = 0.f;
        for (auto source = selection_.begin(); source != selection_.end(); ++source) {
            // collect durations of all media sources
            MediaSource *ms = dynamic_cast<MediaSource *>(*source);
            if (ms != nullptr && !ms->mediaplayer()->singleFrame())
                durations.push_back(static_cast<guint64>(static_cast<double>(ms->mediaplayer()->timeline()->sectionsDuration()) / fabs(ms->mediaplayer()->playSpeed())));
            // compute the displayed width of frames of this source, and keep the max to align afterwards
            float w = 1.5f * timeline_height_ * (*source)->frame()->aspectRatio();
            if ( w > maxframewidth)
                maxframewidth = w;
        }
        if (durations.size()>0) {
            durations.sort();
            durations.unique();
            maxduration = durations.back();
        }

        // compute the ratio for timeline rendering : width (pixel) per time unit (ms)
        const float w = rendersize.x -maxframewidth - 3.f * h_space_ - scrollbar_;
        const double width_ratio = static_cast<double>(w) / static_cast<double>(maxduration);

        // draw list in a scroll area
        ImGui::BeginChild("##v_scroll2", rendersize, false);
        {
            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, v_space_));

            // draw play time scale if a duration is set
            if (maxduration > 0) {
                ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2( maxframewidth + h_space_, 0));
                DrawTimeScale("##timescale", maxduration, width_ratio);
            }

            // loop over all sources
            SourceList selection_sources = selection_;
            ///
            /// First pass: loop over media sources only (with timeline)
            ///
            for (auto source = selection_sources.begin(); source != selection_sources.end();  ) {

                // get media source
                MediaSource *ms = dynamic_cast<MediaSource *>(*source);
                if (ms == nullptr || ms->mediaplayer()->singleFrame()) {
                    // leave the source in the list and continue
                    ++source;
                    continue;
                }

                // ok, get the media player of the media source
                MediaPlayer *mp = ms->mediaplayer();

                ///
                /// Source Image Button
                ///
                ImVec2 image_top = ImGui::GetCursorPos();
                const ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_);
                int action = SourceButton(*source, framesize);
                if (action > 1) {
                    (*source)->play( ! (*source)->playing() );
                    Action::manager().store((*source)->playing() ? "Source Play" : "Source Pause" );
                }
                else if (action > 0)
                    UserInterface::manager().showSourceEditor(*source);

                // icon and text below thumbnail
                ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
                ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y);
                if ((*source)->playable()) {
                    ImGui::SameLine(ImGui::GetTextLineHeight(), 0);
                    ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() );
                    ImGui::SameLine(framesize.x - ImGui::GetTextLineHeightWithSpacing());
                    if ( mp->syncToMetronome() > Metronome::SYNC_NONE )
                        ImGuiToolkit::Icon( mp->syncToMetronome() > Metronome::SYNC_BEAT ? 7 : 6, 13);

                }
                ImGui::PopFont();

                // start to draw timeline aligned at maximum frame width + horizontal space
                ImVec2 pos = image_top + ImVec2( maxframewidth + h_space_, 0);
                ImGui::SetCursorPos(pos);

                // draw the mediaplayer's timeline, with the indication of cursor position
                // NB: use the same width/time ratio for all to ensure timing vertical correspondance

                // TODO : if (mp->syncToMetronome() > Metronome::SYNC_NONE)
                DrawTimeline("##timeline_mediaplayer", mp->timeline(), mp->position(),
                             width_ratio / fabs(mp->playSpeed()), framesize.y);

                if ( w > maxframewidth ) {

                    // next icon buttons are small
                    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 3.f));

                    // next buttons sub id
                    ImGui::PushID( static_cast<int>(mp->id()));

                    // display play speed
                    ImGui::SetCursorPos(pos + ImVec2( 0.f, framesize.y + v_space_));
                    ImGui::Text(UNICODE_MULTIPLY " %.2f", mp->playSpeed());
                    // if not 1x speed, offer to reset
                    if ( fabs( fabs(mp->playSpeed()) - 1.0 ) > EPSILON ) {
                        ImGui::SameLine(0,h_space_);
                        if (ImGuiToolkit::ButtonIcon(19, 15, "Reset speed"))
                            mp->setPlaySpeed( 1.0 );
                    }

                    // if more than one duration of media players, add buttons to adjust
                    if (durations.size() > 1)
                    {
                        for (auto d = durations.crbegin(); d != durations.crend(); ++d) {

                            // next buttons sub id
                            ImGui::PushID( static_cast<int>(*d));

                            // calculate position of icons
                            double x = static_cast<double>(*d) * width_ratio;
                            ImGui::SetCursorPos(pos + ImVec2( static_cast<float>(x) - 2.f, framesize.y + v_space_) );
                            // depending on position relative to media play duration, offer corresponding action
                            double secdur = static_cast<double>(mp->timeline()->sectionsDuration());
                            guint64 playdur = static_cast<guint64>( secdur / fabs(mp->playSpeed()) );
                            // last icon in the timeline
                            if ( playdur == (*d) ) {
                                // not the minimum duration :
                                if (playdur > durations.front() ) {
                                    // offer to speed up or slow down [<>]
                                    if (playdur < durations.back() ) {
                                        if ( ImGuiToolkit::ButtonIcon(0, 12, "Adjust duration") ) {
                                            auto prev = d;
                                            prev--;
                                            selection_target_slower_ = SIGN(mp->playSpeed()) * secdur / static_cast<double>(*prev);
                                            auto next = d;
                                            next++;
                                            selection_target_faster_ = SIGN(mp->playSpeed()) * secdur / static_cast<double>(*next);
                                            selection_mediaplayer_ = mp;
                                            selection_context_menu_ = true;
                                        }
                                    }
                                    // offer to speed up [< ]
                                    else if ( ImGuiToolkit::ButtonIcon(8, 12, "Adjust duration") ) {
                                        auto next = d;
                                        next++;
                                        selection_target_faster_ = SIGN(mp->playSpeed()) * secdur / static_cast<double>(*next);
                                        selection_target_slower_ = 0.0;
                                        selection_mediaplayer_ = mp;
                                        selection_context_menu_ = true;
                                    }
                                }
                                // minimum duration : offer to slow down [ >]
                                else if ( ImGuiToolkit::ButtonIcon(9, 12, "Adjust duration") ) {
                                    selection_target_faster_ = 0.0;
                                    auto prev = d;
                                    prev--;
                                    selection_target_slower_ = SIGN(mp->playSpeed()) * secdur / static_cast<double>(*prev);
                                    selection_mediaplayer_ = mp;
                                    selection_context_menu_ = true;
                                }
                            }
                            // middle buttons : offer to cut at this position
                            else if ( playdur > (*d) ) {
                                char text_buf[256];
                                GstClockTime cutposition = mp->timeline()->sectionsTimeAt( (*d) * fabs(mp->playSpeed()) );
                                ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "Cut at %s",
                                               GstToolkit::time_to_string(cutposition, GstToolkit::TIME_STRING_MINIMAL).c_str());

                                if ( ImGuiToolkit::ButtonIcon(11, 3, text_buf) ) {
                                    if ( mp->timeline()->cut(cutposition, false, true) ) {
                                        std::ostringstream info;
                                        info << SystemToolkit::base_filename( mp->filename() ) << ": Timeline " <<text_buf;
                                        Action::manager().store(info.str());
                                    }
                                }
                            }

                            ImGui::PopID();
                        }
                    }
                    // special case when all media players are (cut to) the same size
                    else if ( durations.size() > 0) {

                        // calculate position of icon
                        double x = static_cast<double>(durations.front()) * width_ratio;
                        ImGui::SetCursorPos(pos + ImVec2( static_cast<float>(x) - 2.f, framesize.y + v_space_) );

                        // offer only to adjust size by removing ending gap
                        if ( mp->timeline()->gapAt( mp->timeline()->end() ) ) {
                            if ( ImGuiToolkit::ButtonIcon(7, 0, "Remove end gap" )){
                                if ( mp->timeline()->removeGaptAt(mp->timeline()->end()) ) {
                                    std::ostringstream info;
                                    info << SystemToolkit::base_filename( mp->filename() ) << ": Timeline Remove end gap";
                                    Action::manager().store(info.str());
                                }
                            }
                        }
                    }

                    ImGui::PopStyleVar();
                    ImGui::PopID();
                }

                // next line position
                ImGui::SetCursorPos(image_top + ImVec2(0, 2.0f * timeline_height_ + 2.f * v_space_));

                // next source
                source = selection_sources.erase(source);
            }

            ImGui::Spacing();

            ///
            /// Second pass: loop over remaining sources (no timeline)
            ///
            ImGui::Columns( MAX( 1, MIN( int(ceil(w / 250.f)), (int)selection_sources.size()) ), "##selectioncolumns", false);
            for (auto source = selection_sources.begin(); source != selection_sources.end(); ++source) {
                ///
                /// Source Image Button
                ///
                const ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_);
                int action = SourceButton(*source, framesize);
                if (action > 1) {
                    (*source)->play( ! (*source)->playing() );                    
                    Action::manager().store((*source)->playing() ? "Source Play" : "Source Pause" );
                }
                else if (action > 0)
                    UserInterface::manager().showSourceEditor(*source);

                // icon and text below thumbnail
                ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
                ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y);
                if ((*source)->playable()) {
                    ImGui::SameLine(ImGui::GetTextLineHeight(), 0);
                    ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() );
                }
                ImGui::PopFont();

                // next line position
                ImGui::Spacing();
                ImGui::NextColumn();
            }
            ImGui::Columns(1);

            ImGui::PopStyleVar();
        }
        ImGui::EndChild();

    }

    ///
    /// context menu from actions above
    ///
    RenderSelectionContextMenu();

    ///
    /// Play button bar
    ///
    DrawButtonBar(bottom, rendersize.x);

    ///
    /// Selection of sources
    ///
    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
    ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
    ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.7f));

    float width_combo = ImGui::GetContentRegionAvail().x - buttons_height_;
    if (width_combo > buttons_width_)
    {
        ImGui::SameLine(0, width_combo -buttons_width_ );
        ImGui::SetNextItemWidth(buttons_width_);
        std::string label = std::string(ICON_FA_CHECK_CIRCLE "  ") + std::to_string(numsources) + ( numsources > 1 ? " sources" : " source");
        if (ImGui::BeginCombo("##SelectionImport", label.c_str()))
        {
            // select all playable sources
            for (auto s = Mixer::manager().session()->begin(); s != Mixer::manager().session()->end(); ++s) {
                if ( !(*s)->failed() ) {
                    std::string label = std::string((*s)->initials()) + " - " + (*s)->name();
                    if (std::find(selection_.begin(),selection_.end(),*s) == selection_.end()) {
                        if (ImGui::MenuItem( label.c_str() , 0, false ))
                            Mixer::manager().session()->addSourceToBatch(*s, i);
                    }
                    else {
                        if (ImGui::MenuItem( label.c_str(), 0, true ))
                            Mixer::manager().session()->removeSourceFromBatch(*s, i);
                    }
                }
            }
            ImGui::EndCombo();
        }
    }

    ImGui::SameLine();
    ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.3f);
    if (ImGui::Button(ICON_FA_TIMES_CIRCLE)) {
        resetActiveSelection();
        Mixer::manager().session()->deleteBatch(i);
    }
    if (ImGui::IsItemHovered())
        ImGuiToolkit::ToolTip("Delete batch");

    ImGui::PopStyleColor(4);
}

void SourceControlWindow::RenderSelectionContextMenu()
{
    if (selection_mediaplayer_ == nullptr)
        return;

    if (selection_context_menu_) {
        ImGui::OpenPopup("source_controller_selection_context_menu");
        selection_context_menu_ = false;
    }
    if (ImGui::BeginPopup("source_controller_selection_context_menu"))
    {
        std::ostringstream info;
        info << SystemToolkit::base_filename( selection_mediaplayer_->filename() );

        if ( ImGuiToolkit::MenuItemIcon(14, 16,  ICON_FA_CARET_LEFT " Accelerate", NULL, false, fabs(selection_target_faster_) > 0 )){
            selection_mediaplayer_->setPlaySpeed( selection_target_faster_ );
            info << ": Speed x" << std::setprecision(3) << selection_target_faster_;
            Action::manager().store(info.str());
        }
        if ( ImGuiToolkit::MenuItemIcon(15, 16,  "Slow down " ICON_FA_CARET_RIGHT, NULL, false, fabs(selection_target_slower_) > 0 )){
            selection_mediaplayer_->setPlaySpeed( selection_target_slower_ );
            info << ": Speed x" << std::setprecision(3) << selection_target_slower_;
            Action::manager().store(info.str());
        }
        if ( selection_mediaplayer_->timeline()->gapAt( selection_mediaplayer_->timeline()->end()) ) {

            if ( ImGuiToolkit::MenuItemIcon(7, 0, "Restore ending" )){
                info << ": Restore ending";
                if ( selection_mediaplayer_->timeline()->removeGaptAt(selection_mediaplayer_->timeline()->end()) )
                    Action::manager().store(info.str());
            }

        }
        ImGui::EndPopup();
    }

}

void DrawInspector(uint texture, ImVec2 texturesize, ImVec2 cropsize, ImVec2 origin)
{
    if (Settings::application.source.inspector_zoom > 0 && ImGui::IsWindowFocused())
    {
        // region size is computed with zoom factor from settings
        float region_sz = texturesize.x / Settings::application.source.inspector_zoom;

        // get coordinates of area to zoom at mouse position
        const ImGuiIO& io = ImGui::GetIO();
        float region_x = io.MousePos.x - origin.x - region_sz * 0.5f;
        if (region_x < 0.f)
            region_x = 0.f;
        else if (region_x > texturesize.x - region_sz)
            region_x = texturesize.x - region_sz;

        float region_y = io.MousePos.y - origin.y - region_sz * 0.5f;
        if (region_y < 0.f)
            region_y = 0.f;
        else if (region_y > texturesize.y - region_sz)
            region_y = texturesize.y - region_sz;

        // Tooltip without border and 100% opaque
        ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f) );
        ImGui::BeginTooltip();

        // compute UV and display image in tooltip
        ImVec2 uv0 = ImVec2((region_x) / cropsize.x, (region_y) / cropsize.y);
        ImVec2 uv1 = ImVec2((region_x + region_sz) / cropsize.x, (region_y + region_sz) / cropsize.y);
        ImVec2 uv2 = ImClamp( uv1, ImVec2(0.f, 0.f), ImVec2(1.f, 1.f));
        uv0 += (uv2-uv1);
        ImGui::Image((void*)(intptr_t)texture, ImVec2(texturesize.x / 3.f, texturesize.x / 3.f), uv0, uv2, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImVec4(1.0f, 1.0f, 1.0f, 0.5f));

        ImGui::EndTooltip();
        ImGui::PopStyleVar(3);
    }
}

void SourceControlWindow::DrawSource(Source *s, ImVec2 framesize, ImVec2 top_image, bool withslider, bool withinspector)
{
    if (!s)
        return;

    // 100% opacity for the image (ensure true colors)
    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f);

    // pre-draw background with checkerboard pattern
    ImGui::Image((void*)(uintptr_t) checker_background_->texture(), framesize,
                 ImVec2(0,0), ImVec2(framesize.x/CHECKER_RESOLUTION, framesize.y/CHECKER_RESOLUTION));
    // get back to top image corner after draw
    ImGui::SetCursorScreenPos(top_image);

    // pre-calculate cropping areas of source and cropped rendering area
    const glm::vec4 _crop = s->frame()->projectionArea();
    const ImVec2 _cropsize = framesize * ImVec2(0.5f * (_crop[1] - _crop[0]),
                                          0.5f * (_crop[2] - _crop[3]));
    const ImVec2 _croptop = framesize * ImVec2(0.5f * (1.f + _crop[0]),
                                         0.5f * (1.f - _crop[2]) );

    // pre-calculate slider coordinates in rendering area
    ImVec2 slider = framesize * ImVec2(Settings::application.widget.media_player_slider,1.f);

    // draw pre and post-processed parts if necessary
    if ( s->texturePostProcessed() )
    {
        //
        // LEFT of slider  : original texture
        //
        ImGui::Image((void*)(uintptr_t) s->texture(), slider, ImVec2(0.f,0.f), ImVec2(Settings::application.widget.media_player_slider,1.f));

        //
        // RIGHT of slider : post-processed image (after crop and color correction)
        //
        // no overlap of slider with cropped area
        if (slider.x < _croptop.x) {
            // draw cropped area
            ImGui::SetCursorScreenPos( top_image + _croptop );
            ImGui::Image((void*)(uintptr_t) s->frame()->texture(), _cropsize, ImVec2(0.f, 0.f), ImVec2(1.f,1.f));
        }
        // overlap of slider with cropped area (horizontally)
        else if (slider.x < _croptop.x + _cropsize.x ) {
            // compute slider ratio of cropped area
            float cropped_slider = (slider.x - _croptop.x) / _cropsize.x;
            // top x moves with slider
            ImGui::SetCursorScreenPos( top_image + ImVec2(slider.x, _croptop.y) );
            // size is reduced by slider
            ImGui::Image((void*)(uintptr_t) s->frame()->texture(), _cropsize * ImVec2(1.f -cropped_slider, 1.f), ImVec2(cropped_slider, 0.f), ImVec2(1.f,1.f));
        }
        // else : no render of cropped area

        //
        // SLIDER
        //
        ImDrawList* draw_list = ImGui::GetWindowDrawList();
        ImU32 slider_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight);
        if (withslider && !withinspector)
        {
            // user input : move slider horizontally
            ImGui::SetCursorScreenPos(top_image + ImVec2(- 20.f, 0.5f * framesize.y - 20.0f));
            ImGuiToolkit::InvisibleSliderFloat("#media_player_slider2", &Settings::application.widget.media_player_slider, 0.f, 1.f, ImVec2(framesize.x + 40.f, 40.0f) );
            // affordance: cursor change to horizontal arrows
            if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) {
                ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
                slider_color = ImGui::GetColorU32(ImGuiCol_Text);
            }
            // graphical indication of slider
            draw_list->AddCircleFilled(top_image + slider * ImVec2(1.f, 0.5f), 20.f, slider_color, 26);
        }
        // graphical indication of separator (vertical line)
        draw_list->AddLine(top_image + slider * ImVec2(1.f,0.0f), top_image + slider, slider_color, 1);
    }
    // no post-processed to show: draw simple texture
    else {
        ImGui::Image((void *) (uintptr_t) s->frame()->texture(), framesize);
        slider = framesize;
    }

    //
    // Handle mouse clic and hovering on image
    //
    const ImRect bb(top_image, top_image + framesize);
    const ImGuiID id = ImGui::GetCurrentWindow()->GetID("##source-texture");
    bool hovered, held;
    bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_PressedOnClick);
    // toggle preview on double clic
    if (pressed) {
        // trigger to show preview (will be ignored if not double clic)
        UserInterface::manager().show_preview = UserInterface::PREVIEW_SOURCE;
        // cancel the button behavior to let window move on drag
        ImGui::SetActiveID(0, ImGui::GetCurrentWindow());
        ImGui::SetHoveredID(0);
    }
    // show magnifying glass if hovering and inspector is active
    else if (hovered && withinspector) {
        // different texture to show depending on mouse position of slider
        if (ImGui::IsMouseHoveringRect(top_image, top_image + slider))
            // inspector over left side of slider
            DrawInspector(s->texture(), framesize, framesize, top_image);
        else
            // inspector over right side of slider
            DrawInspector(s->frame()->texture(), framesize, _cropsize, top_image + _croptop);
    }

    // pop ImGuiStyleVar_Alpha
    ImGui::PopStyleVar();
}

ImRect SourceControlWindow::DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize, bool with_inspector)
{
    ///
    /// Centered frame
    ///
    FrameBuffer *frame = s->frame();
    ImVec2 framesize = rendersize;
    ImVec2 corner(0.f, 0.f);
    ImVec2 tmp = ImVec2(framesize.y * frame->aspectRatio(), framesize.x / frame->aspectRatio());
    if (tmp.x > framesize.x) {
        //vertically centered, modulo height of font for display of info under frame
        corner.y = MAX( (framesize.y - tmp.y) / 2.f - ImGui::GetStyle().IndentSpacing, 0.f);
        framesize.y = tmp.y;
    }
    else {
        // horizontally centered
        //        corner.x = (framesize.x - tmp.x) - MAX( (framesize.x - tmp.x) / 2.f - ImGui::GetStyle().IndentSpacing, 0.f);
        corner.x = (framesize.x - tmp.x) / 2.f;
        framesize.x = tmp.x;
    }

    ///
    /// Image
    ///
    const ImVec2 top_image = top + corner;
    ImGui::SetCursorScreenPos(top_image);

    // pre-draw background with checkerboard pattern
    ImGui::Image((void*)(uintptr_t) checker_background_->texture(), framesize,
                 ImVec2(0,0), ImVec2(framesize.x/CHECKER_RESOLUTION, framesize.y/CHECKER_RESOLUTION));

    // draw source
    if (s->ready()) {
        ImGui::SetCursorScreenPos(top_image);
        DrawSource(s, framesize, top_image, true, with_inspector);
    }

    return ImRect( top_image, top_image + framesize);
}


int SourceControlWindow::SourceButton(Source *s, ImVec2 framesize)
{
    // returns > 0 if clicked, >1 if clicked on center play/pause button
    int ret = 0;

    // all subsequent buttons are identified under a unique source id
    ImGui::PushID(s->id());

    // Adjust size of font to frame size
    ImGuiToolkit::PushFont(framesize.x > 350.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO);

    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    const float H = ImGui::GetTextLineHeight();
    const ImVec2 frame_top = ImGui::GetCursorScreenPos();
    const ImVec2 frame_center = frame_top +  ImVec2((framesize.x  - H) / 2.f, (framesize.y  - H) / 2.f);
    ImU32 frame_color = ImGui::GetColorU32(ImGuiCol_Text);
    ImU32 icon_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight);

    // 1. draw texture of source
    if (s->ready())
        DrawSource(s, framesize, frame_top);

    // 2. interactive centered button Play / Pause
    if (s->active() && s->playable()) {
        //draw_list->AddText(frame_center, ImGui::GetColorU32(ImGuiCol_Text), SourcePlayIcon(s));
        ImGui::SetCursorScreenPos(frame_center - ImVec2(H * 0.2f, H * 0.2f));
        ImGui::InvisibleButton("##sourcebutton_icon", ImVec2(H * 1.2f, H * 1.2f));
        if (ImGui::IsItemHovered() || ImGui::IsItemActive()){
            frame_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight);
            icon_color  = ImGui::GetColorU32(ImGuiCol_Text);
        }
        if (ImGui::IsItemClicked()) {
            ret = 2;
        }
    }

    // back to draw overlay
    ImGui::SetCursorScreenPos(frame_top + ImVec2(1.f, 0.f) );

    // 3. draw initials in up-left corner
    draw_list->AddText(frame_top + ImVec2(H * 0.2f, H * 0.1f), ImGui::GetColorU32(ImGuiCol_Text), s->initials());

    // 4. interactive surface on whole texture with frame overlay on mouse over
    ImGui::InvisibleButton("##sourcebutton", framesize);
    if (ImGui::IsItemHovered() || ImGui::IsItemClicked())
    {
        // border
        draw_list->AddRect(frame_top, frame_top + framesize - ImVec2(1.f, 0.f), frame_color, 0, 0, 3.f);
        // centered icon in front of dark background
        if (s->active() && s->playable()) {
            draw_list->AddRectFilled(frame_center - ImVec2(H * 0.3f, H * 0.2f),
                                     frame_center + ImVec2(H * 1.1f, H * 1.15f), ImGui::GetColorU32(ImGuiCol_TitleBgCollapsed), 6.f);
            draw_list->AddText(frame_center, icon_color, s->playing() ? ICON_FA_PAUSE : ICON_FA_PLAY);
        }
    }
    if (ImGui::IsItemClicked()) {
        ret = 1;
    }

    // pops
    ImGui::PopFont();
    ImGui::PopID();

    return ret;
}

void SourceControlWindow::RenderSelectedSources()
{
    ImVec2 top = ImGui::GetCursorScreenPos();
    ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_);
    ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_);

    // get new selection or validate previous list if selection was not updated
    if (Mixer::selection().empty())
        selection_ = valid_only(Mixer::manager().validate(selection_));
    else
        selection_ = valid_only(Mixer::selection().getCopy());
    int numsources = selection_.size();

    // no source selected
    if (numsources < 1)
    {
        ///
        /// Centered text
        ///
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f));
        ImVec2 center = rendersize * ImVec2(0.5f, 0.5f);
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC);
        center.x -= ImGui::GetTextLineHeight() * 2.f;
        ImGui::SetCursorScreenPos(top + center);
        ImGui::Text("Nothing to play");
        ImGui::PopFont();
        ImGui::PopStyleColor(1);

        ///
        /// Play button bar (automatically disabled)
        ///
        DrawButtonBar(bottom, rendersize.x);

    }
    // single source selected
    else if (numsources < 2)
    {
        ///
        /// Single Source display
        ///
        RenderSingleSource( selection_.front() );
    }
    // Several sources selected
    else {
        ///
        /// Sources grid
        ///
        ImGui::BeginChild("##v_scroll", rendersize, false);
        {
            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 2.f * v_space_));

            // area horizontal
            rendersize.y -= buttons_height_ + 2.f * v_space_;
            int numcolumns = CLAMP( int(ceil(rendersize.x / rendersize.y)), 1, numsources );
            ImGui::Columns( numcolumns, "##selectiongrid", false);
            float widthcolumn = rendersize.x / static_cast<float>(numcolumns);
            widthcolumn -= scrollbar_;

            // loop over sources in grid
            for (auto source = selection_.begin(); source != selection_.end(); ++source) {
                ///
                /// Source Image Button
                ///
                ImVec2 image_top = ImGui::GetCursorPos();
                ImVec2 framesize(widthcolumn, widthcolumn / (*source)->frame()->aspectRatio());
                int action = SourceButton(*source, framesize);
                if (action > 1) {
                    (*source)->play( ! (*source)->playing() );                    
                    Action::manager().store((*source)->playing() ? "Source Play" : "Source Pause" );
                }
                else if (action > 0)
                    UserInterface::manager().showSourceEditor(*source);

                // source icon lower left corner
                ImGuiToolkit::PushFont(framesize.x > 350.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO);
                float h = ImGui::GetTextLineHeightWithSpacing();
                ImGui::SetCursorPos(image_top + ImVec2( h_space_, framesize.y -v_space_ - h ));
                ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y);
                if ((*source)->playable()) {
                    ImGui::SameLine();
                    ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() );
                }
                ImGui::PopFont();

                ImGui::Spacing();
                ImGui::NextColumn();
            }

            ImGui::Columns(1);
            ImGui::PopStyleVar();
        }
        ImGui::EndChild();

        ///
        /// Play button bar
        ///
        DrawButtonBar(bottom, rendersize.x);

        ///
        /// Menu to store Selection from current sources
        ///
        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));

        float space = ImGui::GetContentRegionAvail().x;
        float width = buttons_height_;
        std::string label(ICON_FA_PLUS_CIRCLE);
        if (space > buttons_width_) { // enough space to show full button with label text
            label += LABEL_PLAYER_BATCH_ADD;
            width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing();
        }
        ImGui::SameLine(0, space -width);
        ImGui::SetNextItemWidth(width);
        if (ImGui::Button( label.c_str() )) {
            active_selection_ = Mixer::manager().session()->numBatch();
            active_label_ = std::string("Batch #") + std::to_string(active_selection_);
            Mixer::manager().session()->addBatch( ids(selection_) );
        }
        if (space < buttons_width_ && ImGui::IsItemHovered())
            ImGuiToolkit::ToolTip(LABEL_PLAYER_BATCH_ADD);

        ImGui::PopStyleColor(2);
    }

}

void SourceControlWindow::RenderSingleSource(Source *s)
{
    static bool show_overlay_info = false;

    if ( s == nullptr)
        return;

    // in case of a MediaSource
    MediaSource *ms = dynamic_cast<MediaSource *>(s);
    if ( ms != nullptr && ms->playable() ) {
        RenderMediaPlayer( ms );
    }
    else
    {
        ///
        /// Draw centered Image
        ///
        ImVec2 top = ImGui::GetCursorScreenPos();
        ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_);
        ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_);

        ImRect imgarea = DrawSourceWithSlider(s, top, rendersize, magnifying_glass);

        ///
        /// Info overlays
        ///
        if (!show_overlay_info){
            ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);

            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
            ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_-1.f, v_space_-1.f));
            ImGui::Text("%s", s->initials());
            ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_+1.f, v_space_+1.f));
            ImGui::Text("%s", s->initials());
            ImGui::PopStyleColor(1);

            ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_));
            ImGui::Text("%s", s->initials());
            ImGui::PopFont();
        }
        if (!magnifying_glass) {

            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
            ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_));
            ImGui::Text(ICON_FA_CIRCLE);
            ImGui::PopStyleColor(1);

            ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_));
            ImGui::Text(ICON_FA_INFO_CIRCLE);
            show_overlay_info = ImGui::IsItemHovered();
            if (show_overlay_info){
                // fill info string
                s->accept(info_);
                // draw overlay frame and text
                float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing();
                ImGui::GetWindowDrawList()->AddRectFilled(imgarea.GetTL(), imgarea.GetTL() + ImVec2(imgarea.GetWidth(), tooltip_height), IMGUI_COLOR_OVERLAY);
                ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_));
                ImGui::Text("%s", info_.str().c_str());
                // special case Streams: print framerate
                StreamSource *sts = dynamic_cast<StreamSource*>(s);
                if (sts && s->playing()) {
                    ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - 1.5f * buttons_height_, 0.5f * tooltip_height));
                    ImGui::Text("%.1f Hz", sts->stream()->updateFrameRate());
                }
            }
            else
                // make sure next ItemHovered refreshes the info_
                info_.reset();
        }

        ///
        /// icon & timing in lower left corner
        ///
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
        ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, -ImGui::GetTextLineHeightWithSpacing() - h_space_ ));
        ImGuiToolkit::Icon( s->icon().x, s->icon().y);
        ImGui::SameLine();
        ImGui::Text("%s", s->playable() ? GstToolkit::time_to_string(s->playtime()).c_str() : " " );
        ImGui::PopFont();

        ///
        /// Play button bar
        ///
        DrawButtonBar(bottom, rendersize.x);

        ///
        /// Special possibly : selected a media source that is not playable
        /// then offer to make it playable by adding a timeline
        ///
        if ( ms != nullptr )
        {
            if (ms->mediaplayer()->isImage()) {

                ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));

                float space = ImGui::GetContentRegionAvail().x;
                float width = buttons_height_ ;
                if (space > buttons_width_)
                    width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing();
                ImGui::SameLine(0, space -width);
                ImGui::SetNextItemWidth(width);
                if (ImGuiToolkit::ButtonIcon( 0, 14, LABEL_PLAYER_TIMELINE_ADD, true, space > buttons_width_ )) {

                    // activate mediaplayer
                    mediaplayer_active_ = ms->mediaplayer();

                    // set timeline to default 1 second
                    Timeline tl;
                    TimeInterval interval(0, GST_SECOND);
                    // set fixed framerate to 25 FPS
                    tl.setTiming( interval, 40 * GST_MSECOND);
                    mediaplayer_active_->setTimeline(tl);

                    // set to play
                    mediaplayer_active_->play(true);

                    // re-open the image with a timeline
                    mediaplayer_active_->reopen();

                    // open dialog to set duration
                    mediaplayer_set_duration_ = 2;
                }

                ImGui::PopStyleColor(2);
            }
        }
        ///
        ///  Not a media source, but playable source
        ///  Offer context menu if it is a Stream source
        ///
        else if ( s->active() && s->playable() ) {

            static uint counter_menu_timeout = 0;

            ImGui::SameLine();
            ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.4f);
            if (ImGuiToolkit::IconButton(5, 8) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
                counter_menu_timeout=0;
                ImGui::OpenPopup( "MenuStreamOptions" );
            }

            if (ImGui::BeginPopup( "MenuStreamOptions" ))
            {
                if (ImGui::MenuItem(ICON_FA_REDO_ALT "  Reload"))
                    s->reload();
                // NB: ss is playable (tested above), and thus ss->stream() is not null
                bool option = s->replayOnDeactivate();
                if (ImGui::MenuItem(ICON_FA_SNOWFLAKE "  Restart on reactivation", NULL, &option ))
                    s->setReplayOnDeactivate(option);
                if (ImGui::IsWindowHovered())
                    counter_menu_timeout=0;
                else if (++counter_menu_timeout > 10)
                    ImGui::CloseCurrentPopup();

                ImGui::EndPopup();
            }
        }
    }
}


void DragButtonIcon(int i, int j, const char *tooltip, TimelinePayload payload)
{
    ImGuiToolkit::ButtonIcon(i, j, tooltip);
    if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
        // _payload.action = TimelinePayload::FADE_OUT_IN;
        // _payload.timing = d * GST_MSECOND;
        // _payload.argument = Timeline::FADING_SMOOTH;
        ImGui::SetDragDropPayload("DND_TIMELINE", &payload, sizeof(TimelinePayload));
        ImGuiToolkit::Icon(i, j);
        ImGui::EndDragDropSource();
    }
    if (ImGui::IsItemHovered())
        ImGui::SetMouseCursor(7);   // ImGuiMouseCursor_Hand
}

void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
{
    static bool show_overlay_info = false;
    static std::vector< std::pair<int,int> > editmode_icon = { {8, 3}, {7, 4}, {12, 6} };
    editmode_icon[2] = { Settings::application.widget.media_player_timeline_flag + 11, 6 };  
    static std::vector< std::string > editmode_tooltip = { "Cutting tool", "Fading tool", "Flag tool" };

    mediaplayer_active_ = ms->mediaplayer();

    // for action manager
    std::ostringstream oss;
    oss << SystemToolkit::base_filename( mediaplayer_active_->filename() );

    // for draw
    const float slider_zoom_width = timeline_height_ / 2.f;
    ImDrawList* draw_list = ImGui::GetWindowDrawList();

    ///
    /// Draw centered Image
    ///
    const ImVec2 top = ImGui::GetCursorScreenPos();
    const ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, mediaplayer_height_);
    ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_);

    ImRect imgarea = DrawSourceWithSlider(ms, top, rendersize, magnifying_glass);

    ///
    /// Info overlays
    ///
    if (!show_overlay_info){
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);

        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
        ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_-1.f, v_space_-1.f));
        ImGui::Text("%s", ms->initials());
        ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_+1.f, v_space_+1.f));
        ImGui::Text("%s", ms->initials());
        ImGui::PopStyleColor(1);

        ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_));
        if ( Settings::application.accept_audio && ms->audioFlags() & Source::Audio_enabled )
            // Icon to inform audio decoding
            ImGui::Text("%s " ICON_FA_VOLUME_UP, ms->initials());
        else
            ImGui::Text("%s", ms->initials());
        ImGui::PopFont();
    }
    if (!magnifying_glass) {

        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
        ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_));
        ImGui::Text(ICON_FA_CIRCLE);
        ImGui::PopStyleColor(1);

        ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_));
        ImGui::Text(ICON_FA_INFO_CIRCLE);
        show_overlay_info = ImGui::IsItemHovered();
        if (show_overlay_info){
            // information visitor
            mediaplayer_active_->accept(info_);
            float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing();
            draw_list->AddRectFilled(imgarea.GetTL(), imgarea.GetTL() + ImVec2(imgarea.GetWidth(), tooltip_height), IMGUI_COLOR_OVERLAY);
            ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_));
            ImGui::Text("%s", info_.str().c_str());

            // Icon to inform audio decoding
            if ( ms->audioFlags() & Source::Audio_enabled ) {
                ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - 2.f * ImGui::GetTextLineHeightWithSpacing(), 0.35f * tooltip_height));
                ImGui::Text(ICON_FA_VOLUME_UP);
            }

            // Icon to inform hardware decoding
            if ( mediaplayer_active_->decoderName().compare("software") != 0) {
                ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), 0.35f * tooltip_height));
                ImGui::Text(ICON_FA_MICROCHIP);
            }

            // refresh frequency
            if ( mediaplayer_active_->isPlaying()) {
                ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - 1.5f * buttons_height_, 0.667f * tooltip_height));
                ImGui::Text("%.1f Hz", mediaplayer_active_->updateFrameRate());
            }
        }
    }

    ///
    /// icon & timing in lower left corner
    ///
    ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
    ImVec2 S (h_space_, -ImGui::GetTextLineHeightWithSpacing() - h_space_ );
    ImGui::SetCursorScreenPos(bottom + S);
    ImGuiToolkit::Icon( ms->icon().x, ms->icon().y);
    ImGui::SameLine();
    ImGui::Text( "%s", GstToolkit::time_to_string(mediaplayer_active_->position()).c_str() );

    ///
    /// Sync info lower right corner
    ///
    Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome();
    if ( sync > Metronome::SYNC_NONE) {
        static bool show = true;
        if (mediaplayer_active_->pending())
            show = !show;
        else
            show = true;
        if (show) {
            S.x = rendersize.x + S.y;
            ImGui::SetCursorScreenPos(bottom + S);
            ImGuiToolkit::Icon( sync > Metronome::SYNC_BEAT ? 7 : 6, 13);
        }
    }

    ImGui::PopFont();

    ///
    /// media player timelines
    ///
    static std::vector< std::pair<int, int> > icons_loop = { {0, 15}, {1, 15}, {19, 14}, {18, 14} };
    static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)", "Stop and blackout at end" };
    static std::vector< std::pair<int, int> > icons_flags = { {11, 6}, {12, 6}, {13, 6} };
    static std::vector< std::string > tooltips_flags = { " Bookmark", " Stop Flag", " Blackout Flag" };

    double current_play_speed = mediaplayer_active_->playSpeed();
    static uint counter_menu_timeout = 0;
    const ImVec2 scrollwindow = ImVec2(ImGui::GetContentRegionAvail().x - slider_zoom_width - 3.0,
                                 2.f * timeline_height_ + scrollbar_ );

    if ( mediaplayer_active_->isEnabled() ) {

        // ignore actual play status of mediaplayer when slider is pressed
        if (!mediaplayer_slider_pressed_)
            mediaplayer_mode_ = mediaplayer_active_->isPlaying();

        // seek position
        guint64 seek_t = mediaplayer_active_->position();
        TimeInterval seek_flag;

        // scrolling sub-window
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1.f, 1.f));
        ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.f);
        ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.f, 0.f, 0.f, 0.0f));

        ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f));
        ImGui::BeginChild("##scrolling", scrollwindow,  false, ImGuiWindowFlags_HorizontalScrollbar);
        {
            ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), timeline_height_ -1);
            size.x *= mediaplayer_timeline_zoom_;

            Timeline *tl = mediaplayer_active_->timeline();
            if (tl->is_valid())
            {
                bool released = false;
                if ( EditTimeline("##TimelineArray", tl,
                                       Settings::application.widget.media_player_timeline_editmode,
                                       &released, size) ) {
                    tl->update();
                }
                // end of mouse down to draw timeline
                else if (released)
                {
                    tl->refresh();
                    if (Settings::application.widget.media_player_timeline_editmode == 0)
                        oss << ": Timeline cut";
                    else if (Settings::application.widget.media_player_timeline_editmode == 1)
                        oss << ": Timeline fading";
                    else
                        oss << ": Timeline flags";
                    Action::manager().store(oss.str());
                }
                // custom timeline slider
                // TODO  : if (mediaplayer_active_->syncToMetronome() > Metronome::SYNC_NONE)
                mediaplayer_slider_pressed_ = TimelineSlider("##timeline", &seek_t, &seek_flag, tl, size.x);
            }
        }
        ImGui::EndChild();

        // action mode
        bottom += ImVec2(scrollwindow.x + 2.f, 0.f);
        draw_list->AddRectFilled(bottom, bottom + ImVec2(slider_zoom_width, timeline_height_ -1.f), ImGui::GetColorU32(ImGuiCol_FrameBg));
        ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f));
        if (!mediaplayer_edit_panel_ && ImGuiToolkit::IconButton(11, 0, "Open panel"))
            mediaplayer_edit_panel_ = true;
        if (mediaplayer_edit_panel_ && ImGuiToolkit::IconButton(10, 0, "Close panel"))
            mediaplayer_edit_panel_ = false;
        ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.5f * timeline_height_));
        // select timeline editmode; cut, fade, flag
        ImGuiToolkit::IconMultistate(editmode_icon, &Settings::application.widget.media_player_timeline_editmode, editmode_tooltip );

        // zoom slider
        ImGui::SetCursorScreenPos(bottom + ImVec2(0.f, timeline_height_));
        ImGui::VSliderFloat("##TimelineZoom", ImVec2(slider_zoom_width, timeline_height_), &mediaplayer_timeline_zoom_, 1.0, 5.f, "");

        ImGui::PopStyleVar(2);
        ImGui::PopStyleColor(1);

        ///
        /// media player buttons bar (custom)
        ///
        bottom.x = top.x;
        bottom.y += 2.f * timeline_height_ + scrollbar_;

        draw_list->AddRectFilled(bottom, bottom + ImVec2(rendersize.x, buttons_height_), ImGui::GetColorU32(ImGuiCol_FrameBg), h_space_);

        // buttons style
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f));
        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.24f, 0.24f, 0.24f, 0.2f));
        ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));

        ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, v_space_) );
        if (ImGui::Button(mediaplayer_active_->playSpeed() > 0 ? ICON_FA_FAST_BACKWARD :ICON_FA_FAST_FORWARD))
            mediaplayer_active_->rewind();

        // display buttons Play/Stop depending on current playing mode
        ImGui::SameLine(0, h_space_);
        if (mediaplayer_mode_) {
            if (ImGui::Button(ICON_FA_PAUSE)){
                mediaplayer_mode_ = false;
                oss << ": Pause";
                Action::manager().store(oss.str());
            }
            ImGui::SameLine(0, h_space_);

            ImGui::PushButtonRepeat(true);
            if (ImGui::Button( mediaplayer_active_->playSpeed() < 0 ? ICON_FA_BACKWARD :ICON_FA_FORWARD, 
                    ImVec2(ImGui::GetFrameHeightWithSpacing(), 0)) )
                mediaplayer_active_->jump ();
            ImGui::PopButtonRepeat();
        }
        else {
            if (ImGui::Button(ICON_FA_PLAY)) {
                mediaplayer_mode_ = true;
                oss << ": Play";
                Action::manager().store(oss.str());
            }
            ImGui::SameLine(0, h_space_);

            ImGui::PushButtonRepeat(true);
            if (ImGui::Button( mediaplayer_active_->playSpeed() < 0 ? ICON_FA_STEP_BACKWARD : ICON_FA_STEP_FORWARD,
                    ImVec2(ImGui::GetFrameHeightWithSpacing(), 0)))
                mediaplayer_active_->step();
            ImGui::PopButtonRepeat();
        }

        // flag buttons
        if (rendersize.x > buttons_height_ * 6.0f) {
            if (  mediaplayer_active_->timeline()->numFlags() > 0 ) {

                GstClockTime _paused_time = mediaplayer_active_->position();

                ImGui::SameLine(0, h_space_);
                if( mediaplayer_active_->playSpeed() < 0 ) {
                    // Go to previous flag when playing backward
                    TimeInterval target_flag = mediaplayer_active_->timeline()->getPreviousFlag( _paused_time );
                    bool has_prev = target_flag.is_valid() && 
                      !( mediaplayer_active_->currentFlag().is_valid() && mediaplayer_active_->timeline()->numFlags() == 1) &&
                      ( mediaplayer_active_->loop() == MediaPlayer::LOOP_REWIND || (target_flag.end < _paused_time) );
                    if( ImGuiToolkit::ButtonIcon(6, 0, "Go to previous flag", has_prev) )
                        seek_flag = target_flag;
                }
                else {
                    // go to next flag when playing forward
                    TimeInterval target_flag = mediaplayer_active_->timeline()->getNextFlag( _paused_time );
                    bool has_next = target_flag.is_valid() && 
                      !( mediaplayer_active_->currentFlag().is_valid() && mediaplayer_active_->timeline()->numFlags() == 1) &&
                      ( mediaplayer_active_->loop() == MediaPlayer::LOOP_REWIND || (target_flag.begin > _paused_time) );
                    if( ImGuiToolkit::ButtonIcon(5, 0, "Go to next flag", has_next) )
                        seek_flag = target_flag;
                }

                // if stopped at a flag, show flag menu
                if (!mediaplayer_mode_ && mediaplayer_active_->currentFlag().is_valid()) {
                    ImGui::SameLine(0, h_space_);
                    if (ImGuiToolkit::IconButton(3, 0) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
                        counter_menu_timeout=0;
                        ImGui::OpenPopup( "MenuMediaPlayerFlags" );
                    }
                }
            }
            else {
                ImGui::SameLine(0, h_space_);
                ImGuiToolkit::ButtonIcon(mediaplayer_active_->playSpeed() < 0 ? 6 : 5, 0, nullptr, false);
            }
        }

        // right aligned buttons (if enough space)
        if ( rendersize.x > buttons_height_ * 9.5f ) {

            ImGui::SameLine();
            ImGui::SetCursorPosX(rendersize.x - buttons_height_ * 4.f);

            // loop modes button
            static int current_loop = 0;
            current_loop = (int) mediaplayer_active_->loop();
            if ( ImGuiToolkit::IconMultistate(icons_loop, &current_loop, tooltips_loop) )
                mediaplayer_active_->setLoop( (MediaPlayer::LoopMode) current_loop );

            // speed slider
            ImGui::SameLine(0, h_space_);
            ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttons_height_ - h_space_);
            float s = fabs(static_cast<float>(current_play_speed));
            if (ImGui::DragFloat( "##Speed", &s, 0.01f, 0.1f, 10.f, UNICODE_MULTIPLY " %.2f"))
                mediaplayer_active_->setPlaySpeed( SIGN(current_play_speed) * static_cast<double>(s) );
            // store action on mouse release
            if (ImGui::IsItemDeactivatedAfterEdit()){
                oss << ": Speed x" << std::setprecision(3) << s;
                Action::manager().store(oss.str());
            }
            if (ImGui::IsItemHovered())
                ImGuiToolkit::ToolTip("Play speed");
        }

        ImGui::SameLine();
        ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.4f);
        if (ImGuiToolkit::IconButton(5, 8) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
            counter_menu_timeout=0;
            ImGui::OpenPopup( "MenuMediaPlayerOptions" );
        }

        // restore buttons style
        ImGui::PopStyleColor(5);

        if (mediaplayer_active_->pending())
        {
            draw_list->AddRectFilled(bottom, bottom + ImVec2(rendersize.x, buttons_height_), ImGui::GetColorU32(ImGuiCol_ScrollbarBg), h_space_);
        }

        ///
        /// media player timeline actions
        ///

        // request seek (ASYNC)
        if ( mediaplayer_slider_pressed_ && mediaplayer_active_->go_to(seek_t) )
            mediaplayer_slider_pressed_ = false;

        // play/stop command should be following the playing mode (buttons)
        // AND force to stop when the slider is pressed
        bool media_play = mediaplayer_mode_  & (!mediaplayer_slider_pressed_);

        // apply play action to media only if status should change
        if ( mediaplayer_active_->isPlaying() != media_play ) {
            mediaplayer_active_->play( media_play );
        }

        // Flag pressed in timeline
        if (seek_flag.is_valid()) {
            // go to the flag position
            mediaplayer_active_->go_to_flag(seek_flag);
        }
    }
    else {
        ///
        /// Disabled areas for timeline and button bar
        ///
        // disabled timeline
        ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f));
        const ImGuiContext& g = *GImGui;
        const double width_ratio = static_cast<double>(scrollwindow.x - slider_zoom_width + g.Style.FramePadding.x ) / static_cast<double>(mediaplayer_active_->timeline()->sectionsDuration());
        DrawTimeline("##timeline_mediaplayers", mediaplayer_active_->timeline(), mediaplayer_active_->position(), width_ratio, 2.f * timeline_height_);

        // disabled area for timeline actions and zoom
        bottom += ImVec2(scrollwindow.x + 2.f, 0.f);
        draw_list->AddRectFilled(bottom, bottom + ImVec2(slider_zoom_width, 2.f * timeline_height_ -1.f),
                                 ImGui::GetColorU32(ImGuiCol_FrameBgActive));
        // disabled button bar
        bottom.x = top.x;
        bottom.y += 2.f * timeline_height_ + scrollbar_;
        DrawButtonBar(bottom, rendersize.x);
    }

    if (ImGui::BeginPopup( "MenuMediaPlayerOptions" ))
    {
        if (ImGuiToolkit::MenuItemIcon(8,0, "Play forward", nullptr, current_play_speed>0)) {
            mediaplayer_active_->setPlaySpeed( ABS(mediaplayer_active_->playSpeed()) );
            oss << ": Play forward";
            Action::manager().store(oss.str());
        }
        if (ImGuiToolkit::MenuItemIcon(9,0, "Play backward", nullptr, current_play_speed<0)) {
            mediaplayer_active_->setPlaySpeed( - ABS(mediaplayer_active_->playSpeed()) );
            oss << ": Play backward";
            Action::manager().store(oss.str());
        }
        if (ImGuiToolkit::MenuItemIcon(19,15, "Reset speed")) {
            mediaplayer_active_->setPlaySpeed(1.0);
            oss << ": Speed x 1.0";
            Action::manager().store(oss.str());
        }
        ImGui::Separator();

        if (ImGui::MenuItem( ICON_FA_REDO_ALT "  Reload" ))
            mediaplayer_active_->reopen();

        bool option = ms->replayOnDeactivate();
        if (ImGui::MenuItem(ICON_FA_SNOWFLAKE "  Restart on reactivation", NULL, &option )) {
            ms->setReplayOnDeactivate(option);
        }

        if (ImGui::IsWindowHovered())
            counter_menu_timeout=0;
        else if (++counter_menu_timeout > 10)
            ImGui::CloseCurrentPopup();

        ImGui::EndPopup();
    }
    
    if (ImGui::BeginPopup( "MenuMediaPlayerFlags" ))
    {
        int num_flags = static_cast<int>(icons_flags.size());
        TimeInterval copy_flag = mediaplayer_active_->currentFlag();
        for (int i = 0; i < num_flags; ++i) {
            if (ImGuiToolkit::MenuItemIcon(icons_flags[i].first,icons_flags[i].second, 
                tooltips_flags[i].c_str(), nullptr, copy_flag.type == i )) {
                copy_flag.type = i;    
                mediaplayer_active_->timeline()->replaceFlag( copy_flag );
                mediaplayer_active_->setCurrentFlag( copy_flag );
                oss << ": Flag changed";
                Action::manager().store(oss.str());
            }
        }
        ImGui::Separator();
        if (ImGuiToolkit::MenuItemIcon(2,0, "Delete flag")) {
            mediaplayer_active_->timeline()->removeFlagAt(mediaplayer_active_->currentFlag().midpoint() );
            oss << ": Flag removed";
            Action::manager().store(oss.str());
        }

        if (ImGui::IsWindowHovered())
            counter_menu_timeout=0;
        else if (++counter_menu_timeout > 10)
            ImGui::CloseCurrentPopup();

        ImGui::EndPopup();
    }

    ///
    /// Window area to edit gaps or fading
    ///
    if (mediaplayer_edit_panel_ && mediaplayer_active_->isEnabled()) {

        const ImVec2 gap_dialog_size(buttons_width_ * 3.0f, buttons_height_ * 1.42);
        const ImVec2 gap_dialog_pos = rendersize + ImVec2(h_space_, buttons_height_) - gap_dialog_size;

        ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyle().Colors[ImGuiCol_PopupBg] );
        ImGui::SetCursorPos(gap_dialog_pos);
        ImGui::BeginChild("##EditGapsWindow",
                     gap_dialog_size, true,
                     ImGuiWindowFlags_NoScrollbar);

        // variables state of panel
        static int current_curve = 2;
        static uint d = UINT_MAX;
        static guint64 target_time = 1000000000;
        static bool target_time_valid = true;

        // timeline to edit
        Timeline *tl = mediaplayer_active_->timeline();

        ///
        /// PANEL FOR CUT TIMELINE MODE
        ///
        ImGui::Spacing();
        if (Settings::application.widget.media_player_timeline_editmode == 0) {

            // PANEL WITH LARGE FONTS
            ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);

            ///
            /// CUT LEFT OF CURSOR
            ///
            DragButtonIcon(9, 3, "Drop in timeline to\nCut left",
                           TimelinePayload(TimelinePayload::CUT, 0, 1) );
            ///
            /// CUT RIGHT OF CURSOR
            ///
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            DragButtonIcon(10, 3, "Drop in timeline to\nCut right",
                           TimelinePayload(TimelinePayload::CUT, 0, 0) );

            if (tl->numGaps() > 0) {
                ///
                /// MERGE CUTS AT CURSOR
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(19, 3, "Drop in timeline to\nMerge two gaps",
                               TimelinePayload(TimelinePayload::CUT_MERGE, 0, 0) );
                ///
                /// ERASE CUT AT CURSOR
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(0, 4, "Drop in timeline to\nErase a gap",
                               TimelinePayload(TimelinePayload::CUT_ERASE, 0, 0) );
            }
            else {
                ///
                /// DISABLED ICONS IF NO GAP
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                ImGuiToolkit::ButtonIcon(19, 3, "No gap to merge", false);
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                ImGuiToolkit::ButtonIcon(0, 4, "No gap to erase", false);
            }

            ///
            /// SECTION WITH BUTTONS
            ///
            ImGui::SameLine(0, 0);
            ImGui::Text("|");

            /// Enter time value for CUT
            ImGui::SameLine(0, 0);
            ImVec2 draw_pos = ImGui::GetCursorPos();
            float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing() ;
            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11));
            ImGui::SetNextItemWidth(w - draw_pos.x - IMGUI_SAME_LINE); // VARIABLE WIDTH
            ImGuiToolkit::InputTime("##TimeCut", &target_time, tl->duration(), &target_time_valid);
            ImGui::PopStyleVar();

            /// CUT LEFT TIME
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
            ImGui::SetCursorPos( ImVec2(w, draw_pos.y));
            if (ImGuiToolkit::ButtonIcon(17, 3, "Cut left at given time", target_time_valid)) {
                tl->cut(target_time, true);
                tl->refresh();
                oss << ": Timeline cut";
                Action::manager().store(oss.str());
            }
            /// CUT RIGHT TIME
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGuiToolkit::ButtonIcon(18, 3, "Cut right at given time", target_time_valid)){
                tl->cut(target_time, false);
                tl->refresh();
                oss << ": Timeline cut";
                Action::manager().store(oss.str());
            }
            /// CLEAR
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all gaps")) {
                tl->clearGaps();
                oss << ": Timeline clear gaps";
                Action::manager().store(oss.str());
            }

            ImGui::PopStyleColor();

            // end icons
            ImGui::PopFont();
        }
        ///
        /// PANEL FOR FADING
        ///
        else if (Settings::application.widget.media_player_timeline_editmode == 1) {
            // Icons for Drag & Drop
            ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);

            ///
            /// CURVE MODE
            ///
            const std::vector<std::string> tooltips_curve = {{"Fading Sharp"},
                                                             {"Fading Linear"},
                                                             {"Fading Smooth"}};
            const std::vector<std::pair<int, int> > options_curve
                = {{12, 12},
                   {13, 12},
                   {14, 12}};

            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
            ImGuiToolkit::IconMultistate(options_curve, &current_curve, tooltips_curve);
            ImGui::PopStyleColor();

            ///
            /// FADING SHARP
            ///
            if (current_curve == 0) {
                ///
                /// FADE IN
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(14, 10, "Drop in timeline to insert\nSharp fade-in",
                               TimelinePayload(TimelinePayload::FADE_IN, d*GST_MSECOND, Timeline::FADING_SHARP));
                ///
                /// FADE OUT
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(11, 10, "Drop in timeline to insert\nSharp fade-out",
                               TimelinePayload(TimelinePayload::FADE_OUT, d*GST_MSECOND, Timeline::FADING_SHARP));
                ///
                /// FADE IN & OUT
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(9, 10, "Drop in timeline to insert\nSharp fade in & out",
                               TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SHARP));
            }
            ///
            /// FADE LINEAR
            ///
            else if (current_curve == 1) {
                ///
                /// FADE IN
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(15, 10, "Drop in timeline to insert\nLinear fade-in",
                               TimelinePayload(TimelinePayload::FADE_IN, d*GST_MSECOND, Timeline::FADING_LINEAR));
                ///
                /// FADE OUT
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(12, 10, "Drop in timeline to insert\nLinear fade-out",
                               TimelinePayload(TimelinePayload::FADE_OUT, d*GST_MSECOND, Timeline::FADING_LINEAR));
                ///
                /// FADE IN & OUT
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(7, 10, "Drop in timeline to insert\nLinear fade in & out",
                               TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_LINEAR));
            }
            ///
            /// FADE SMOOTH
            ///
            else if (current_curve == 2) {
                ///
                /// FADE IN
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(16, 10, "Drop in timeline to insert\nSmooth fade-in",
                               TimelinePayload(TimelinePayload::FADE_IN, d*GST_MSECOND, Timeline::FADING_SMOOTH));
                ///
                /// FADE OUT
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(13, 10, "Drop in timeline to insert\nSmooth fade-out",
                               TimelinePayload(TimelinePayload::FADE_OUT, d*GST_MSECOND, Timeline::FADING_SMOOTH));
                ///
                /// FADE IN & OUT
                ///
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                DragButtonIcon(17, 10, "Drop in timeline to insert\nSmooth fade in & out",
                               TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SMOOTH));
            }
            else {

            }

            ///
            /// DURATION SLIDER
            ///
            float seconds = (float) d / 1000.f;
            float maxi = (float) GST_TIME_AS_MSECONDS(tl->duration()) / 1000.f;
            float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing();

            ImGui::SameLine(0, IMGUI_SAME_LINE);
            ImVec2 draw_pos = ImGui::GetCursorPos();
            ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11));
            ImGui::SetNextItemWidth(w - draw_pos.x);
            if (ImGui::SliderFloat("##DurationFading",
                                   &seconds,
                                   0.05f,
                                   maxi,
                                   d < UINT_MAX ? "%.2f s" : "Auto")) {
                if (seconds > maxi - 0.05f)
                    d = UINT_MAX;
                else
                    d = (uint) floor(seconds * 1000);
            }
            ImGui::PopStyleVar();
            ImGui::PopFont();

            ///
            /// SECTION WITH BUTTONS
            ///
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
            ImGui::SetCursorPos( ImVec2(w, draw_pos.y));
            ImGui::Text("|");

            /// SMOOTH Curve
            static int _actionsmooth = 0;
            ImGui::SameLine(0, 0);
            ImGui::PushButtonRepeat(true);
            if (ImGuiToolkit::ButtonIcon(2, 7, "Apply smoothing filter")){
                tl->smoothFading( 15 );
                ++_actionsmooth;
            }
            ImGui::PopButtonRepeat();
            if (_actionsmooth > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
                oss << ": Timeline smooth curve";
                Action::manager().store(oss.str());
                _actionsmooth = 0;
            }

            /// CLEANUP
            ImGui::SameLine(0, 0);
            if (ImGuiToolkit::ButtonIcon(3, 7, "Clean curve in gaps")) {
                tl->autoFadeInGaps();
                oss << ": Timeline cleanup curve";
                Action::manager().store(oss.str());
            }

            /// CLEAR
            ImGui::SameLine(0, 0);
            if (ImGuiToolkit::ButtonIcon(11, 14, "Clear fading curve")){
                tl->clearFading();
                oss << ": Timeline clear fading";
                Action::manager().store(oss.str());
            }

            ImGui::PopStyleColor();

            // end icons
            ImGui::PopFont();
        }
        ///
        /// PANEL FOR FLAGS
        ///
        else if (Settings::application.widget.media_player_timeline_editmode == 2) {

            // PANEL WITH LARGE FONTS
            ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);

            ///
            /// DROP ICONS
            ///
            DragButtonIcon(11, 6, "Drop in timeline to\nAdd a Bookmark",
                           TimelinePayload(TimelinePayload::FLAG_ADD, 0, 0) );

            ImGui::SameLine(0, IMGUI_SAME_LINE);
            DragButtonIcon(12, 6, "Drop in timeline to\nAdd a Stop Flag",
                           TimelinePayload(TimelinePayload::FLAG_ADD, 0, 1) );

            ImGui::SameLine(0, IMGUI_SAME_LINE);
            DragButtonIcon(13, 6, "Drop in timeline to\nAdd a Blackout Flag",
                           TimelinePayload(TimelinePayload::FLAG_ADD, 0, 2) );

            ImGui::SameLine(0, IMGUI_SAME_LINE);
            DragButtonIcon(2, 0, "Drop in timeline to\nErase a flag",
                           TimelinePayload(TimelinePayload::FLAG_REMOVE, 0, 0) );

            ///
            /// SECTION WITH BUTTONS
            ///
            ImGui::SameLine(0, 0);
            ImGui::Text("|");
            ImGui::SameLine(0, 0);

            ImGuiToolkit::IconMultistate(icons_flags, 
                &Settings::application.widget.media_player_timeline_flag, tooltips_flags);

            /// Enter time value for Flag
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            ImVec2 draw_pos = ImGui::GetCursorPos();
            float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing() ;
            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11));
            ImGui::SetNextItemWidth(w - draw_pos.x - IMGUI_SAME_LINE); // VARIABLE WIDTH
            ImGuiToolkit::InputTime("##TimeFlag", &target_time, tl->duration(), &target_time_valid);
            ImGui::PopStyleVar();

            /// FLAG AT TIME
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
            ImGui::SetCursorPos( ImVec2(w, draw_pos.y));
            if (ImGuiToolkit::ButtonIcon(1, 0, "Add flag at given time", target_time_valid)) {
                tl->removeFlagAt(target_time);
                tl->addFlagAt(target_time, Settings::application.widget.media_player_timeline_flag);
                tl->refresh();
                oss << ": Timeline flag add";
                Action::manager().store(oss.str());
            }

            /// Add
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGuiToolkit::ButtonIcon(0, 0, "Add flag at cursor position", !mediaplayer_active_->isPlaying()) ) {
                tl->removeFlagAt(mediaplayer_active_->position());
                tl->addFlagAt(mediaplayer_active_->position(), Settings::application.widget.media_player_timeline_flag);
                tl->refresh();
                oss << ": Timeline flag add";
                Action::manager().store(oss.str());
            }              

            /// CLEAR
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all flags")) {
                tl->clearFlags();
                oss << ": Timeline flag clear";
                Action::manager().store(oss.str());
            }                                                                                                                                                                                                                           

            ImGui::PopStyleColor();

            // end icons
            ImGui::PopFont();
        }

        ImGui::EndChild();
        ImGui::PopStyleColor();
    }

    
    ///
    /// Dialog to edit timeline fade in and out
    ///
    if (mediaplayer_edit_fading_) {
        ImGui::OpenPopup(LABEL_EDIT_FADING);
        mediaplayer_edit_fading_ = false;
    }
    const ImVec2 mp_dialog_size(buttons_width_ * 2.f, buttons_height_ * 6);
    ImGui::SetNextWindowSize(mp_dialog_size, ImGuiCond_Always);
    const ImVec2 mp_dialog_pos = top + rendersize * 0.5f  - mp_dialog_size * 0.5f;
    ImGui::SetNextWindowPos(mp_dialog_pos, ImGuiCond_Always);
    if (ImGui::BeginPopupModal(LABEL_EDIT_FADING, NULL, ImGuiWindowFlags_NoResize))
    {
        const ImVec2 pos = ImGui::GetCursorPos();
        const ImVec2 area = ImGui::GetContentRegionAvail();

        ImGui::Spacing();
        ImGui::Text("Set parameters and apply:");
        ImGui::Spacing();

        static int l = 0;
        static std::vector< std::tuple<int, int, std::string> > fading_options = {
            {19, 7, "Fade in"},
            {18, 7, "Fade out"},
            { 0, 8, "Auto fade in & out"}
        };
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        ImGuiToolkit::ComboIcon("Fading", &l, fading_options);

        static int c = 0;
        static std::vector< std::tuple<int, int, std::string> > curve_options = {
            {18, 3, "Linear"},
            {19, 3, "Progressive"},
            {17, 3, "Abrupt"}
        };
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        ImGuiToolkit::ComboIcon("Curve", &c, curve_options);

        static uint d = 1000;
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        ImGuiToolkit::SliderTiming ("Duration", &d, 200, 5050, 50, "Maximum");
        if (d > 5000)
            d = UINT_MAX;

        bool close = false;
        ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_));
        if (ImGui::Button(ICON_FA_TIMES "  Cancel", ImVec2(area.x * 0.3f, 0)))
            close = true;
        ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_));
        ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab));
        if (ImGui::Button(ICON_FA_CHECK "  Apply", ImVec2(area.x * 0.3f, 0))
                || ImGui::IsKeyPressedMap(ImGuiKey_Enter)  || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) {
            close = true;
            // timeline to edit
            Timeline *tl = mediaplayer_active_->timeline();
            switch (l) {
            case 0:
                tl->fadeIn( static_cast<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
                oss << ": Timeline Fade in " << d;
                break;
            case 1:
                tl->fadeOut( static_cast<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
                oss << ": Timeline Fade out " << d;
                break;
            case 2:
                tl->autoFading( static_cast<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
                oss << ": Timeline Fade in&out " << d;
                break;
            default:
                break;
            }
            tl->smoothFading( 2 );
            Action::manager().store(oss.str());
        }
        ImGui::PopStyleColor(1);

        if (close)
            ImGui::CloseCurrentPopup();
        ImGui::EndPopup();
    }

    ///
    /// Dialog to set gstreamer video effect
    ///
    static std::string _effect_description = "";
    static bool _effect_description_changed = false;
    if (mediaplayer_edit_pipeline_) {
        // open dialog
        ImGui::OpenPopup(DIALOG_GST_EFFECT);
        mediaplayer_edit_pipeline_ = false;
        // initialize dialog
        _effect_description = mediaplayer_active_->videoEffect();
        _effect_description_changed = true;
    }
    const ImVec2 mpp_dialog_size(buttons_width_ * 3.f, buttons_height_ * 6.2f);
    ImGui::SetNextWindowSize(mpp_dialog_size, ImGuiCond_Always);
    const ImVec2 mpp_dialog_pos = top + rendersize * 0.5f  - mpp_dialog_size * 0.5f;
    ImGui::SetNextWindowPos(mpp_dialog_pos, ImGuiCond_Always);
    if (ImGui::BeginPopupModal(DIALOG_GST_EFFECT, NULL, ImGuiWindowFlags_NoResize))
    {
        const ImVec2 pos = ImGui::GetCursorPos();
        const ImVec2 area = ImGui::GetContentRegionAvail();
        static uint _status = 0;  // 0:unknown, 1:ok, 2:error
        static std::string _status_message = "";
        static std::vector< std::pair< std::string, std::string> > _examples = { {"Primary color", "frei0r-filter-primaries" },
                                                                                 {"Histogram", "frei0r-filter-levels"},
                                                                                 {"Emboss", "frei0r-filter-emboss"},
                                                                                 {"Denoise", "frei0r-filter-hqdn3d spatial=0.05 temporal=0.1"},
                                                                                 {"Thermal", "coloreffects preset=heat"},
                                                                                 {"Afterimage", "streaktv"}
                                                                               };
        static ImVec2 fieldsize(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 70);
        static int numlines = 0;
        const ImGuiContext& g = *GImGui;
        fieldsize.y = MAX(2.5, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y;

        ImGui::Spacing();
        ImGui::Text("Enter a gstreamer video effect description and apply.\nLeave empty for no effect.");
        ImGui::SameLine();
        ImGuiToolkit::HelpToolTip("Video effects are directly integrated in the gstreamer pipeline "
                                  "and performed on CPU (might be slow). Vimix recommends using "
                                  "GPU accelerated filters by cloning the source.");
        ImGui::Spacing();

        // Editor
        if ( ImGuiToolkit::InputCodeMultiline("Effect", &_effect_description, fieldsize, &numlines) )
            _effect_description_changed = true;
        if ( ImGui::IsItemActive() )
            _status = 0;

        // Local menu for list of examples
        ImVec2 pos_bot = ImGui::GetCursorPos();
        ImGui::SetCursorPos( pos_bot + ImVec2(fieldsize.x + IMGUI_SAME_LINE, -ImGui::GetFrameHeightWithSpacing()));
        if (ImGui::BeginCombo("##ExamplesVideoEffect", "ExamplesVideoEffect", ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLarge))  {
            ImGui::TextDisabled("Examples");
            for (auto it = _examples.begin(); it != _examples.end(); ++it) {
                if (ImGui::Selectable( it->first.c_str() ) ) {
                    _effect_description = it->second;
                    _effect_description_changed = true;
                }
            }
            ImGui::Separator();
            ImGui::TextDisabled("Explore online");
            if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Frei0r" ) )
                SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/frei0r");
            if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Effectv" ) )
                SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/effectv");
            if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Gaudi" ) )
                SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/gaudieffects");
            if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Geometric" ) )
                SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/geometrictransform");
            ImGui::EndCombo();
        }
        // Local menu for clearing
        ImGui::SameLine();
        if ( ImGuiToolkit::ButtonIcon(11,13,"Clear") ) {
            _effect_description = "";
            _effect_description_changed = true;
        }

        // if desciption changed, start a timeout to test the pipeline
        if ( _effect_description_changed ){
            // reset to status 'unknown' and no message
            _status = 0;
            _status_message.clear();

            // No description, no effect
            if ( _effect_description.empty() ) {
                _status = 1;
                _status_message = "(no video effect)";
            }
            // has a description, we test it
            else {
                GError *error = NULL;
                GstElement *effect = gst_parse_launch( _effect_description.c_str(), &error);
                // on error
                if (effect == NULL || error != NULL) {
                    _status = 2;
                    if (error != NULL)
                        _status_message = error->message;
                    g_clear_error (&error);
                    if (effect)
                        gst_object_unref(effect);
                }
                // on success
                else
                    _status = 1;
            }
            // done
            _effect_description_changed = false;
        }

        // display message line
        if (_status > 1) {
            // On Error
            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0, 0.2, 0.2, 0.95f));
            ImGui::TextWrapped("Error - %s", _status_message.c_str());
            ImGui::PopStyleColor(1);
        }
        else if (_status > 0) {
            // All ok
            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2, 1.0, 0.2, 0.85f));
            ImGui::Text("Ok %s", _status_message.c_str());
            ImGui::PopStyleColor(1);
        }

        bool close = false;
        ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_));
        if (ImGui::Button(ICON_FA_TIMES "  Cancel", ImVec2(area.x * 0.3f, 0)))
            close = true;
        ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_));
        // on success status, offer to apply
        if (_status == 1) {
            ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab));
            if (ImGui::Button(ICON_FA_CHECK "  Apply", ImVec2(area.x * 0.3f, 0))
                    || ImGui::IsKeyPressedMap(ImGuiKey_Enter)  || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) {
                close = true;

                // apply to pipeline
                mediaplayer_active_->setVideoEffect(_effect_description);
                oss << ": gstreamer effect";
                Action::manager().store(oss.str());
            }
            ImGui::PopStyleColor(1);
        }
        else
            ImGuiToolkit::ButtonDisabled(ICON_FA_CHECK "  Apply", ImVec2(area.x * 0.3f, 0));

        if (close)
            ImGui::CloseCurrentPopup();
        ImGui::EndPopup();
    }

    ////
    ///  Dialog to set timeline duration
    ///
    static double timeline_duration_ = 0.0;
    static double timeline_duration_previous = 0.0;
    if (mediaplayer_set_duration_) {
        // get current duration of mediaplayer
        GstClockTime end = mediaplayer_active_->timeline()->end();
        timeline_duration_ = (double) (GST_TIME_AS_MSECONDS(end)) / 1000.f;
        // remember previous duration for Cancel
        // NB: trick with var 'mediaplayer_set_duration_' set to 2 when first time created
        timeline_duration_previous = mediaplayer_set_duration_ > 1 ? 0.0 : timeline_duration_;
        // open dialog to change duration
        ImGui::OpenPopup(DIALOG_TIMELINE_DURATION);
        // only once
        mediaplayer_set_duration_ = 0;
    }
    const ImVec2 tld_dialog_size(buttons_width_ * 2.f, buttons_height_ * 4);
    ImGui::SetNextWindowSize(tld_dialog_size, ImGuiCond_Always);
    const ImVec2 tld_dialog_pos = top + rendersize * 0.5f  - tld_dialog_size * 0.5f;
    ImGui::SetNextWindowPos(tld_dialog_pos, ImGuiCond_Always);
    if (ImGui::BeginPopupModal(DIALOG_TIMELINE_DURATION, NULL, ImGuiWindowFlags_NoResize))
    {
        const ImVec2 pos = ImGui::GetCursorPos();
        const ImVec2 area = ImGui::GetContentRegionAvail();

        ImGui::Spacing();
        ImGui::Text("Set the duration of the timeline");
        ImGui::Spacing();

        // get current timeline
        ImGui::InputDouble("second", &timeline_duration_, 1.0f, 10.0f, "%.2f");
        timeline_duration_ = ABS(timeline_duration_);

        bool close = false;
        ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_));
        if (ImGui::Button(ICON_FA_TIMES "  Cancel", ImVec2(area.x * 0.3f, 0))) {
            // restore previous timeline duration
            timeline_duration_ = timeline_duration_previous;
            // close dialog
            close = true;
        }
        ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_));
        ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab));
        if (ImGui::Button(ICON_FA_CHECK "  Apply", ImVec2(area.x * 0.3f, 0))
                || ImGui::IsKeyPressedMap(ImGuiKey_Enter)  || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) {
            // close dialog
            close = true;
        }
        ImGui::PopStyleColor(1);
        if (close) {
            // zero duration requested : delete timeline
            if (timeline_duration_ < 0.01) {
                // set empty timeline
                Timeline tl;
                mediaplayer_active_->setTimeline(tl);
                mediaplayer_active_->play(false);
                // re-open the image with NO timeline
                mediaplayer_active_->reopen();
            }
            // else normal change timeline end
            else
                mediaplayer_active_->timeline()->setEnd( GST_MSECOND * (GstClockTime) ( timeline_duration_ * 1000.f ) );
            // close popup window
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    }
}

void SourceControlWindow::DrawButtonBar(ImVec2 bottom, float width)
{
    // draw box
    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    draw_list->AddRectFilled(bottom, bottom + ImVec2(width, buttons_height_), ImGui::GetColorU32(ImGuiCol_FrameBg), h_space_);

    // prepare position to draw text
    ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, v_space_) );

    // play bar is enabled if only one source selected is enabled
    bool enabled = false;
    bool hasflags = false;
    size_t n_play = 0;
    for (auto source = selection_.begin(); source != selection_.end(); ++source){
        if ( (*source)->active() && (*source)->playable())
            enabled = true;
        if ( (*source)->playing() )
            n_play++;
        MediaSource *ms = dynamic_cast<MediaSource*>(*source);
        if ( ms && ms->mediaplayer() && ms->mediaplayer()->timeline() && ms->mediaplayer()->timeline()->numFlags() > 0 )
            hasflags = true;
    }

    // buttons style for disabled / enabled bar
    if (enabled) {
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f));
        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
    }
    else {
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f));
        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
    }

    // Always the rewind button
    if (ImGui::Button(ICON_FA_FAST_BACKWARD) && enabled) {
        for (auto source = selection_.begin(); source != selection_.end(); ++source)
            (*source)->replay();
    }
    ImGui::SameLine(0, h_space_);

    // unique play / pause button
    if (n_play < 1 || selection_.size() == n_play) {
        if (n_play) {
            if (ImGui::Button(ICON_FA_PAUSE) && enabled) {
                for (auto source = selection_.begin(); source != selection_.end(); ++source)
                    (*source)->play(false);
                Action::manager().store(std::string("Pause ") +
                                        std::to_string(selection_.size()) +
                                        " sources");
            }
        }
        else {
            if (ImGui::Button(ICON_FA_PLAY) && enabled){
                for (auto source = selection_.begin(); source != selection_.end(); ++source)
                    (*source)->play(true);
                Action::manager().store(std::string("Play ") +
                                        std::to_string(selection_.size()) +
                                        " sources");
            }
        }
    }
    // separate play & pause buttons for disagreeing sources
    else {
        if (ImGui::Button(ICON_FA_PLAY) && enabled) {
            for (auto source = selection_.begin(); source != selection_.end(); ++source)
                (*source)->play(true);
            Action::manager().store(std::string("Play " ) +
                                    std::to_string(selection_.size()) +
                                    " sources");
        }
        ImGui::SameLine(0, h_space_);
        if (ImGui::Button(ICON_FA_PAUSE) && enabled) {
            for (auto source = selection_.begin(); source != selection_.end(); ++source)
                (*source)->play(false);
            Action::manager().store(std::string("Pause ") +
                                    std::to_string(selection_.size()) +
                                    " sources");
        }
    }
    ImGui::SameLine(0, h_space_);
    if (hasflags) {
        if( ImGuiToolkit::ButtonIcon(5, 0, "Go to next flag", enabled) && enabled ) {
            for (auto source = selection_.begin(); source != selection_.end(); ++source) {
                (*source)->call( new Flag( ));
            }
        }
    }

    // restore style
    ImGui::PopStyleColor(3);

    ImGui::SameLine(0, h_space_);
}
