/*
 * 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 <cstddef>
#include <iterator>
#include <string>
#include <vector>
#include <iomanip>

#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_access.hpp>

#include <tinyxml2.h>

#include "IconsFontAwesome5.h"
#include "imgui.h"
#include "imgui_internal.h"

#include "Log.h"
#include "defines.h"
#include "Scene/Scene.h"
#include "Scene/Primitives.h"
//#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "MediaPlayer.h"
#include "Source/MediaSource.h"
#include "Source/CloneSource.h"
#include "Filter/FrameBufferFilter.h"
#include "Filter/DelayFilter.h"
#include "Filter/ImageFilter.h"
#include "Source/RenderSource.h"
#include "Source/SessionSource.h"
#include "Source/PatternSource.h"
#include "Source/DeviceSource.h"
#include "Source/ScreenCaptureSource.h"
#include "Source/NetworkSource.h"
#include "Source/SrtReceiverSource.h"
#include "Source/MultiFileSource.h"
#include "Source/TextSource.h"
#include "Source/ShaderSource.h"
#include "SessionCreator.h"
#include "SessionVisitor.h"
#include "Settings.h"
#include "Mixer.h"
#include "MixingGroup.h"
#include "ActionManager.h"
#include "Mixer.h"
#include "ControlManager.h"
#include "Transcoder.h"
#include "Canvas.h"

#include "imgui.h"
#include "Toolkit/ImGuiToolkit.h"
#include "Toolkit/BaseToolkit.h"
#include "UserInterfaceManager.h"
#include "Toolkit/SystemToolkit.h"

#include "ImGuiVisitor.h"

ImGuiVisitor::ImGuiVisitor()
{

}

void ImGuiVisitor::visit(Node &)
{

}

void ImGuiVisitor::visit(Group &n)
{
    // MODEL VIEW
    ImGui::PushID(std::to_string(n.id()).c_str());

    if (ImGuiToolkit::ButtonIcon(1, 16)) {
        n.translation_.x = 0.f;
        n.translation_.y = 0.f;
        n.rotation_.z = 0.f;
        n.scale_.x = 1.f;
        n.scale_.y = 1.f;
        Action::manager().store("Geometry Reset");
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    ImGui::Text("Geometry");

    if (ImGuiToolkit::ButtonIcon(6, 15)) {
        n.translation_.x = 0.f;
        n.translation_.y = 0.f;
        Action::manager().store("Position 0.0, 0.0");
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    float translation[2] = { n.translation_.x, n.translation_.y};
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    if ( ImGui::SliderFloat2("Position", translation, -5.0, 5.0) )
    {
        n.translation_.x = translation[0];
        n.translation_.y = translation[1];
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Position " << std::setprecision(3) << n.translation_.x << ", " << n.translation_.y;
        Action::manager().store(oss.str());
    }
    if (ImGuiToolkit::ButtonIcon(3, 15))  {
        n.scale_.x = 1.f;
        n.scale_.y = 1.f;
        Action::manager().store("Scale 1.0 x 1.0");
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    float scale[2] = { n.scale_.x, n.scale_.y} ;
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    if ( ImGui::SliderFloat2("Scale", scale, -MAX_SCALE, MAX_SCALE, "%.2f") )
    {
        n.scale_.x = CLAMP_SCALE(scale[0]);
        n.scale_.y = CLAMP_SCALE(scale[1]);
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Scale " << std::setprecision(3) << n.scale_.x << " x " << n.scale_.y;
        Action::manager().store(oss.str());
    }

    if (ImGuiToolkit::ButtonIcon(18, 9)){
        n.rotation_.z = 0.f;
        Action::manager().store("Angle 0.0");
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    ImGui::SliderAngle("Angle", &(n.rotation_.z), -180.f, 180.f) ;
    if (ImGui::IsItemDeactivatedAfterEdit()) {
        oss << "Angle " << std::setprecision(3) << n.rotation_.z * 180.f / M_PI;
        Action::manager().store(oss.str());
    }


    ImGui::PopID();

    // spacing
    ImGui::Spacing();
}

void ImGuiVisitor::visit(Switch &n)
{
    if (n.numChildren()>0)
        n.activeChild()->accept(*this);
}

void ImGuiVisitor::visit(Scene &n)
{
    ImGui::SetNextItemOpen(true, ImGuiCond_Once);
    if (ImGui::CollapsingHeader("Scene Property Tree"))
    {
        n.root()->accept(*this);
    }
}

void ImGuiVisitor::visit(Primitive &n)
{
    ImGui::PushID(std::to_string(n.id()).c_str());
    ImGui::Text("Primitive");

    n.shader()->accept(*this);

    ImGui::PopID();
}

void ImGuiVisitor::visit(FrameBufferSurface &)
{
    ImGui::Text("Framebuffer");
}

void ImGuiVisitor::visit(MediaPlayer &)
{
    ImGui::Text("Media Player");
}

void ImGuiVisitor::visit(Shader &n)
{
    ImGui::PushID(std::to_string(n.id()).c_str());

    // Base color
//    if (ImGuiToolkit::ButtonIcon(10, 2)) {
//        n.blending = Shader::BLEND_OPACITY;
//        n.color = glm::vec4(1.f, 1.f, 1.f, 1.f);
//    }
//    ImGui::SameLine(0, IMGUI_SAME_LINE);
//    ImGui::ColorEdit3("Color", glm::value_ptr(n.color), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel ) ;
//    ImGui::SameLine(0, 5);
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    int mode = n.blending;
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    if (ImGuiToolkit::ComboIcon("Blending", &mode, Shader::blendingFunction) )
    {
        n.blending = Shader::BlendMode(mode);
        oss << "Blending " << std::get<2>(Shader::blendingFunction[mode]).c_str();
        Action::manager().store(oss.str());
    }

    ImGui::PopID();
}

void ImGuiVisitor::visit(ImageProcessingShader &n)
{
    ImGuiIO& io = ImGui::GetIO();
    ImGui::PushID(std::to_string(n.id()).c_str());

    ///
    /// GAMMA
    ///
    ImGui::ColorEdit3("Gamma Color", glm::value_ptr(n.gamma), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ;
    if (ImGui::IsItemDeactivatedAfterEdit()) {
        char buf[64];
        ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp((int)ceil(255.f * n.gamma.x),0,255),
                       ImClamp((int)ceil(255.f * n.gamma.y),0,255), ImClamp((int)ceil(255.f * n.gamma.z),0,255));
        oss << "Gamma color " << buf;
        Action::manager().store(oss.str());
    }

    ImGui::SameLine(0, IMGUI_SAME_LINE);
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    float val = log10f(n.gamma.w);
    if ( ImGui::SliderFloat("##Gamma", &val, -1.f, 1.f, "%.3f", 2.f) )
        n.gamma.w = powf(10.f, val);
    if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
        val = CLAMP(val + 0.001f * io.MouseWheel, -1.f, 1.f);
        n.gamma.w = powf(10.f, val);
        oss << "Gamma " << std::setprecision(3) << val;
        Action::manager().store(oss.str());
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Gamma " << std::setprecision(3) << val;
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Gamma")) {
        n.gamma = glm::vec4(1.f, 1.f, 1.f, 1.f);
        oss << "Gamma 0.0, #FFFFFF";
        Action::manager().store(oss.str());
    }

    ///
    /// BRIGHTNESS
    ///
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    ImGui::SliderFloat("##Brightness", &n.brightness, -1.0, 1.0, "%.3f", 2.f);
    if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
        n.brightness = CLAMP(n.brightness + 0.001f * io.MouseWheel, -1.f, 1.f);
        oss << "Brightness " << std::setprecision(3) << n.brightness;
        Action::manager().store(oss.str());
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Brightness " << std::setprecision(3) << n.brightness;
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Brightness")) {
        n.brightness = 0.f;
        oss << "Brightness " << std::setprecision(2) << n.brightness;
        Action::manager().store(oss.str());
    }

    ///
    /// CONTRAST
    ///
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    ImGui::SliderFloat("##Contrast", &n.contrast, -1.0, 1.0, "%.3f", 2.f);
    if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
        n.contrast = CLAMP(n.contrast + 0.001f * io.MouseWheel, -1.f, 1.f);
        oss << "Contrast " << std::setprecision(3) << n.contrast;
        Action::manager().store(oss.str());
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Contrast " << std::setprecision(3) << n.contrast;
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Contrast")) {
        n.contrast = 0.f;
        oss << "Contrast " << std::setprecision(2) << n.contrast;
        Action::manager().store(oss.str());
    }

    ///
    /// SATURATION
    ///
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    ImGui::SliderFloat("##Saturation", &n.saturation, -1.0, 1.0, "%.3f", 2.f);
    if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
        n.saturation = CLAMP(n.saturation + 0.001f * io.MouseWheel, -1.f, 1.f);
        oss << "Saturation " << std::setprecision(3) << n.saturation;
        Action::manager().store(oss.str());
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Saturation " << std::setprecision(3) << n.saturation;
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Saturation")) {
        n.saturation = 0.f;
        oss << "Saturation " << std::setprecision(2) << n.saturation;
        Action::manager().store(oss.str());
    }

    ///
    /// HUE SHIFT
    ///
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    ImGui::SliderFloat("##Hue", &n.hueshift, 0.0, 1.0, "%.3f");
    if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
        n.hueshift = CLAMP(n.hueshift + 0.001f * io.MouseWheel, 0.f, 1.f);
        oss << "Hue shift " << std::setprecision(3) << n.hueshift;
        Action::manager().store(oss.str());
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Hue shift " << std::setprecision(3) << n.hueshift;
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Hue ")) {
        n.hueshift = 0.f;
        oss << "Hue shift  " << std::setprecision(2) << n.hueshift;
        Action::manager().store(oss.str());
    }

    ///
    /// POSTERIZATION
    ///
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    float posterized = n.nbColors < 1 ? 257.f : n.nbColors;
    if (ImGui::SliderFloat("##Posterize", &posterized, 257.f, 1.f, posterized > 256.f ? "Full range" : "%.0f colors", 0.5f)) {
        n.nbColors = posterized > 256 ? 0.f : posterized;
    }
    if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
        n.nbColors = CLAMP( n.nbColors + io.MouseWheel, 1.f, 257.f);
        if (n.nbColors == 0) oss << "Full range"; else oss << n.nbColors;
        Action::manager().store(oss.str());
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Posterize ";
        if (n.nbColors == 0) oss << "Full range"; else oss << n.nbColors;
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Posterize ")) {
        n.nbColors = 0.f;
        oss << "Posterize Full range";
        Action::manager().store(oss.str());
    }

    ///
    /// THRESHOLD
    ///
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    float threshold = n.threshold < 0.001f ? 0.f : n.threshold;
    if (ImGui::SliderFloat("##Threshold", &threshold, 0.f, 1.f, threshold < 0.001f ? "None" : "%.3f") ){
        n.threshold = threshold < 0.001f ? 0.f : threshold;
    }
    if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
        n.threshold = CLAMP(n.threshold + 0.001f * io.MouseWheel, 0.f, 1.f);
        if (n.threshold < 0.001f) oss << "None"; else oss << std::setprecision(3) << n.threshold;
        Action::manager().store(oss.str());
    }
    if (ImGui::IsItemDeactivatedAfterEdit()){
        oss << "Threshold ";
        if (n.threshold < 0.001f) oss << "None"; else oss << std::setprecision(3) << n.threshold;
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Threshold ")) {
        n.threshold = 0.f;
        oss << "Threshold None";
        Action::manager().store(oss.str());
    }

    ///
    /// INVERSION
    ///
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    if (ImGui::Combo("##Invert", &n.invert, "None\0Color RGB\0Luminance\0"))
        Action::manager().store("Invert " + std::string(n.invert<1 ? "None": (n.invert>1 ? "Luminance" : "Color")));
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Invert ")) {
        n.invert = 0;
        oss << "Invert None";
        Action::manager().store(oss.str());
    }

    ImGui::PopID();

    ImGui::Spacing();
}

void ImGuiVisitor::visit (Source& s)
{
    const float preview_width = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;
    const float preview_height = 4.5f * ImGui::GetFrameHeightWithSpacing();
    const float space = ImGui::GetStyle().ItemSpacing.y;

    ImGui::PushID(std::to_string(s.id()).c_str());

    oss = std::ostringstream();
    oss << s.name() << ": ";

    // blending selection
    Shader::BlendMode prevblend = s.blendingShader()->blending;
    s.blendingShader()->accept(*this);
    if (s.blendingShader()->blending != prevblend)
        s.touch();

    // Draw different info if failed or succeed
    if ( !s.failed() ) {

        // remember where we were...
        ImVec2 pos = ImGui::GetCursorPos();

        // preview
        float width = preview_width;
        float height = s.frame()->projectionSize().y * width / ( s.frame()->projectionSize().x * s.frame()->aspectRatio());
        if (height > preview_height - space) {
            height = preview_height - space;
            width = height * s.frame()->aspectRatio() * ( s.frame()->projectionSize().x / s.frame()->projectionSize().y);
        }
        // centered image
        if (s.ready()) {
            ImVec2 dpos = ImVec2(pos.x + 0.5f * (preview_width-width), pos.y + 0.5f * (preview_height-height-space));
            ImGui::SetCursorPos( dpos );
            // get mouse over before drawing
            ImVec2 up_left = ImGui::GetCursorScreenPos();
            ImVec2 down_right = ImVec2( up_left.x + width, up_left.y + height );
            // on mouse over show source original image
            if (ImGui::IsMouseHoveringRect( up_left, down_right )) {
                ImGui::Image((void*)(uintptr_t) s.texture(), ImVec2(width, height));
                ImGuiToolkit::ToolTip("Source original image");
            }
            // draw source processed frame by default
            else {
                ImGui::Image((void*)(uintptr_t) s.frame()->texture(), ImVec2(width, height));
            }
        } else {
            ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
            ImGui::SetCursorPos(
                ImVec2(pos.x + (preview_width - ImGui::GetFrameHeight()) * 0.5f,
                       pos.y + (preview_height - ImGui::GetFrameHeight()) * 0.5f));
            ImGui::Text(ICON_FA_HOURGLASS_HALF);
            ImGui::PopFont();
        }

        // inform on visibility status
        ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y ) );
        if (s.active()) {
            if (s.blendingShader()->color.a > 0.f) {
                std::string indication = "Visible\nAlpha ";
                indication += std::to_string((int)(s.blendingShader()->color.a * 100.f)) + "%";
                if (ImGuiToolkit::IconButton( ICON_FA_SUN,  indication.c_str()) ) {
                    UserInterface::manager().setView(View::MIXING);
                }
            } else
                ImGuiToolkit::Indication("not Visible", ICON_FA_MOON);
        }
        else
            ImGuiToolkit::Indication("Inactive", ICON_FA_SNOWFLAKE);

        // Inform on link
        ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + ImGui::GetFrameHeightWithSpacing()) );
        if (s.mixingGroup() != nullptr) {
            if (ImGuiToolkit::IconButton(ICON_FA_LINK, "Linked")){
                Mixer::selection().clear();
                Mixer::selection().add( s.mixingGroup()->getCopy() );
                UserInterface::manager().setView(View::MIXING);
            }
        }
        else
            ImGuiToolkit::Indication("not Linked", ICON_FA_UNLINK);

        // inform of input mapping
        ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 2.1f * ImGui::GetFrameHeightWithSpacing()) );
        std::list<uint> inputs = Mixer::manager().session()->inputsForSource( s.id() );
        if (!inputs.empty())    {
            if (ImGuiToolkit::IconButton( ICON_FA_HAND_PAPER ) ) {
                // open input mapping window
                Settings::application.widget.inputs = true;
            }
            if (ImGui::IsItemHovered()) {         
                std::string tooltip = "Mapped to input";  
                tooltip +=  inputs.size() > 1 ? "s:" : ":" ;
                for(const auto& input : inputs) {
                    tooltip += "\n - ";
                    tooltip += Control::inputLabel(input);
                }
                ImGuiToolkit::ToolTip(tooltip.c_str());
            }
        }
        else
            ImGuiToolkit::Indication("not Mapped to an input", ICON_FA_HAND_PAPER);

        // locking
        ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + preview_height - ImGui::GetFrameHeightWithSpacing()) );
        static const char *tooltip[2] = {"Unlocked", "Locked"};
        bool l = s.locked();
        if (ImGuiToolkit::IconToggle(15, 6, 17, 6, &l, tooltip ) ) {
            s.setLocked(l);
            if (l) {
                Mixer::selection().clear();
                oss << "Locked";
            }
            else {
                Mixer::selection().set(&s);
                oss << "Unlocked";
            }
            Action::manager().store(oss.str());
        }

        // Filter
        bool on = s.imageProcessingEnabled();
        ImGui::SetCursorPos( ImVec2( pos.x, pos.y + preview_height));
        if (on)
            ImGui::Text(ICON_FA_PALETTE "  Color correction");
        else
            ImGuiToolkit::Indication("Color correction filter is disabled", ICON_FA_PALETTE "  Color correction");
        pos = ImGui::GetCursorPos();

        // menu icon for image processing
        ImGui::SameLine(preview_width + 5, 2 * IMGUI_SAME_LINE);
        static uint counter_menu_timeout = 0;
        if (ImGuiToolkit::IconButton(5, 8) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
            counter_menu_timeout=0;
            ImGui::OpenPopup( "MenuImageProcessing" );
        }

        if (ImGui::BeginPopup( "MenuImageProcessing" ))
        {
            if (ImGui::MenuItem("Enable", NULL, &on)) {
                oss << ( on ? "Enable Color correction" : "Disable Color correction");
                Action::manager().store(oss.str());
                s.setImageProcessingEnabled(on);
            }

            if (s.processingshader_link_.connected()) {
                if (ImGui::MenuItem( "Unfollow", NULL, false, on)){
                    s.processingshader_link_.disconnect();
                }
            }
            else {
                if (ImGui::MenuItem("Reset", NULL, false, on )){
                    ImageProcessingShader defaultvalues;
                    s.processingShader()->copy(defaultvalues);
                    s.processingshader_link_.disconnect();
                    oss << "Reset Filter";
                    Action::manager().store(oss.str());
                }
                if (ImGui::MenuItem("Copy", NULL, false, on )){
                    std::string clipboard = SessionVisitor::getClipboard(s.processingShader());
                    if (!clipboard.empty())
                        ImGui::SetClipboardText(clipboard.c_str());
                }
                const char *clipboard = ImGui::GetClipboardText();
                const bool can_paste = (clipboard != nullptr && SessionLoader::isClipboard(clipboard));
                if (ImGui::MenuItem("Paste", NULL, false, can_paste)) {
                    SessionLoader::applyImageProcessing(s, clipboard);
                    oss << "Change Filter";
                    Action::manager().store(oss.str());
                }
                //            // NON-stable style follow mechanism
                //            ImGui::Separator();
                //            if (ImGui::BeginMenu("Follow", on))
                //            {
                //                for (auto mpit = Mixer::manager().session()->begin();
                //                     mpit != Mixer::manager().session()->end(); mpit++ )
                //                {
                //                    std::string label = (*mpit)->name();
                //                    if ( (*mpit)->id() != s.id() &&
                //                         (*mpit)->imageProcessingEnabled() &&
                //                         !(*mpit)->processingshader_link_.connected()) {
                //                        if (ImGui::MenuItem( label.c_str() )){
                //                            s.processingshader_link_.connect(*mpit);
                //                            s.touch();
                //                        }
                //                    }
                //                }
                //                ImGui::EndMenu();
                //            }
            }

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

            ImGui::EndPopup();
        }

        if (s.imageProcessingEnabled()) {

            // full panel for image processing
            ImGui::SetCursorPos( pos );
            ImGui::Spacing();

            if (s.processingshader_link_.connected()) {
                Source *target = s.processingshader_link_.source();
                ImGui::Text("Following");
                if ( target != nullptr && ImGui::Button(target->name().c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
                    Mixer::manager().setCurrentSource(target);
            }
            else
                s.processingShader()->accept(*this);
        }

    }
    else {

        // remember where we were...
        ImVec2 pos = ImGui::GetCursorScreenPos();

        // preview = black rectangle
        ImDrawList* draw_list = ImGui::GetWindowDrawList();
        draw_list->AddRectFilled(ImVec2(pos.x + space, pos.y + space), 
            ImVec2(pos.x + preview_width - space, pos.y + preview_height - space),
            ImColor(0, 0, 0, 255));

        // centered icon of failed (skull)
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
        ImGui::SetCursorScreenPos( ImVec2(pos.x + space + (preview_width  -ImGui::GetFrameHeight())* 0.5f ,
                                    pos.y + (preview_height -ImGui::GetFrameHeight()) * 0.5f) );
        ImGui::Text(ICON_FA_SKULL);
        ImGui::PopFont();

        // back
        ImGui::SetCursorScreenPos( ImVec2( pos.x, pos.y + preview_height));
    }

    ImGui::PopID();

    ImGui::Spacing();
    ImGuiToolkit::Icon(s.icon().x, s.icon().y);
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    ImGui::Text("%s", s.info().c_str());

}

void ImGuiVisitor::visit (MediaSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // Media info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    if (ImGui::IsItemHovered())
        ImGuiToolkit::ToolTip( s.path().c_str() );

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
            top.x += ImGui::GetFrameHeight();
        }
        ImGui::SetCursorPos(top);
        if (ImGuiToolkit::IconButton(3, 5, "Show in finder"))
            SystemToolkit::open(SystemToolkit::path_filename(s.path()));

        MediaPlayer *mp = s.mediaplayer();
        if (mp && !mp->isImage()) {

            // information on gstreamer effect filter
            if ( mp->videoEffectAvailable() &&  !mp->videoEffect().empty()) {
                top.x += ImGui::GetFrameHeight();
                ImGui::SetCursorPos(top);
                std::string desc = mp->videoEffect();
                desc = desc.substr(0, desc.find_first_of(' '));
                desc = "Has gstreamer effect '" + desc + "'";
                ImGuiToolkit::Indication(desc.c_str(),16,16);
            }

            ImGui::SetCursorPos(botom);

            // Selector for Hardware or software decoding, if available
            if ( Settings::application.render.gpu_decoding ) {

                // Build string information on decoder name with icon HW/SW
                bool hwdec = !mp->softwareDecodingForced();
                std::string decoder = mp->decoderName();
                if (hwdec) {
                    if ( decoder.compare("software") != 0)
                        decoder = ICON_FA_MICROCHIP " Hardware (" + decoder + ")";
                    else
                        decoder = ICON_FA_COGS " Software";
                }
                else
                    decoder = ICON_FA_COGS " Software only";
                // Combo selector shoing the current state and the option to choose
                // between auto select and forced software decoding
                ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
                if (ImGui::BeginCombo("Decoding", decoder.c_str() ) )
                {
                    if (ImGui::Selectable( ICON_FA_MICROCHIP " / " ICON_FA_COGS " Auto select", &hwdec ))
                        mp->setSoftwareDecodingForced(false);
                    if (ImGui::IsItemHovered() && !hwdec)
                        ImGuiToolkit::ToolTip( "Changing decoding will\nre-open the media" );
                    hwdec = mp->softwareDecodingForced();
                    if (ImGui::Selectable( ICON_FA_COGS " Software only", &hwdec ))
                        mp->setSoftwareDecodingForced(true);
                    if (ImGui::IsItemHovered() && !hwdec)
                        ImGuiToolkit::ToolTip( "Changing decoding will\nre-open the media" );
                    ImGui::EndCombo();
                }
            }
            else {
                // info of software only decoding if disabled
                ImGuiToolkit::Icon(14,2,false);
                ImGui::SameLine();
                ImGui::TextDisabled("Hardware decoding disabled");
            }

            // transcoding panel
            static Transcoder *transcoder = nullptr;
            static guint64 transcode_id = 0;
            static bool _transcoding = false;
            bool show_content = _transcoding && (transcode_id == 0 || transcode_id == s.id());
            float w_height = (!show_content) ? 
                     ImGui::GetFrameHeight() : (6.1f * ImGui::GetFrameHeightWithSpacing());
            
            ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_PopupBg));
            ImGui::BeginChild("transcode_child", ImVec2(0, w_height), 
                                    true, ImGuiWindowFlags_MenuBar);
            if (ImGui::BeginMenuBar())
            {
                std::string title = "  Transcoding";
                if (show_content)
                    title.insert(0, ICON_FA_CHEVRON_DOWN " ");
                else {
                    title.insert(0, ICON_FA_CHEVRON_RIGHT " ");
                    if (transcoder != nullptr)
                        title += " (" + std::to_string((int)(100.0 * transcoder->progress())) + "%)";
                }

                if ( ImGui::Selectable( title.c_str(), false, 
                    (transcoder != nullptr ? ImGuiSelectableFlags_Disabled : 0)) )
                    _transcoding = !_transcoding;
                ImGui::EndMenuBar();
            }

            if (_transcoding ) {
                
                ImGuiToolkit::ButtonSwitch( "Backward playback", &Settings::application.transcode_options[0],
                "Optimize the video for backward playback by adding more keyframes.",transcoder == nullptr);
                ImGuiToolkit::ButtonSwitch( "Animation content", &Settings::application.transcode_options[1],
                "Optimize the video encoding for animation content (cartoons, "
                "drawings, computer graphics) to preserve more details." ,transcoder == nullptr   );   
                ImGuiToolkit::ButtonSwitch( "Constant Quality", &Settings::application.transcode_options[2],
                "Use Constant Quality encoding to preserve more visual details "
                "(produces larger files).",transcoder == nullptr);   
                ImGuiToolkit::ButtonSwitch( "Remove audio", &Settings::application.transcode_options[3],
                "Remove the audio track from the video during transcoding.",transcoder == nullptr);   

                if (transcoder == nullptr)
                {   
                    if (ImGui::Button(ICON_FA_COG " Re-encode", ImVec2(IMGUI_RIGHT_ALIGN,0))) {
                        transcode_id = s.id();
                        transcoder = new Transcoder(gst_uri_get_location(mp->uri().c_str()));
                        TranscoderOptions transcode_options(Settings::application.transcode_options[0], 
                            Settings::application.transcode_options[1] ?  PsyTuning::ANIMATION : PsyTuning::NONE, 
                            Settings::application.transcode_options[2] ? 19 : -1, 
                            Settings::application.transcode_options[3]);
                        if (!transcoder->start(transcode_options)) {
                            Log::Warning("Failed to start transcoding: %s", transcoder->error().c_str());
                            delete transcoder;
                            transcoder = nullptr;
                            transcode_id = 0;
                        }
                    }
                    ImGui::SameLine();
                    ImGuiToolkit::HelpToolTip("Re-encode the source video in MP4 "
                            "(H.264 video @ 30fps + AAC audio) using the specified options.\n\n "
                            ICON_FA_FILM "  The new file will replace the one in the source "
                            "once the transcoding is successfully completed.\n\n"
                            "The current file remains untouched.");
                }

                if (transcoder != nullptr) {
                    if (transcoder->finished()) {
                        if (transcoder->success()) {
                            Log::Notify("Transcoding successful : %s", transcoder->outputFilename().c_str());
                            // reload source with new file
                            Source * s = Mixer::manager().findSource(transcode_id);
                            if (s != nullptr) {
                                MediaSource * ms = static_cast<MediaSource*>(s);
                                ms->setPath( transcoder->outputFilename() );
                            }
                            info.reset();
                            _transcoding = false;
                        }
                        // all done in any case
                        delete transcoder;
                        transcoder = nullptr;
                        transcode_id = 0;
                    }
                    else {
                        float progress = transcoder->progress();
                        ImGui::ProgressBar(progress, ImVec2(IMGUI_RIGHT_ALIGN,0), progress < EPSILON ? "working..." : nullptr);
                        ImGui::SameLine();
                        if (ImGui::Button( ICON_FA_TIMES " Cancel", ImVec2(0,0)) ||
                            Mixer::manager().findSource(transcode_id) == nullptr ) {
                            // cancel transcoding by user or source removed
                            transcoder->stop();     
                        }
                    }
                }
            }
            ImGui::EndChild();
            ImGui::PopStyleColor();
        }
        else
            ImGui::SetCursorPos(botom);
    }
    else {
        // failed
        ImGui::SetCursorPos(top);
        if (ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy message"))
            ImGui::SetClipboardText(info.str().c_str());
        info.reset();

        ImGui::SetCursorPos(botom);

        // because sometimes the error comes from gpu decoding
        if ( Settings::application.render.gpu_decoding &&
            SystemToolkit::file_exists(s.path()) &&
            !s.mediaplayer()->softwareDecodingForced() )
        {
            // offer to reload the source without hardware decoding
            if ( ImGui::Button( ICON_FA_REDO_ALT " Try again ", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) {
                // replace current source with one created with a flag forcing software decoding
                Mixer::manager().replaceSource(Mixer::manager().currentSource(),
                                               Mixer::manager().createSourceFile(s.path(), true));
            }
        }
    }

}

void ImGuiVisitor::visit (SessionFileSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    if (ImGui::IsItemHovered())
        ImGuiToolkit::ToolTip( s.path().c_str() );

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() && s.session() != nullptr ) {

        if ( s.active() && s.session()->ready() ) {
            // versions
            SessionSnapshots *versions = s.session()->snapshots();
            if (versions->keys_.size()>0) {
                ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
                if (ImGui::BeginCombo("Version", ICON_FA_CODE_BRANCH " Select" ) )
                {
                    for (auto v = versions->keys_.crbegin() ; v != versions->keys_.crend(); ++v){
                        std::string label = std::to_string(*v);
                        const tinyxml2::XMLElement *snap = versions->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(*v).c_str() );
                        if (snap)
                            label = snap->Attribute("label");
                        if (ImGui::Selectable( label.c_str() )) {
                            s.session()->applySnapshot(*v);
                        }
                    }
                    ImGui::EndCombo();
                }
            }

            // fading
            int f = 100 - int(s.session()->fading() * 100.f);
            ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
            if (ImGui::SliderInt("##Fading", &f, 0, 100, f > 99 ? ICON_FA_ADJUST " None" : ICON_FA_ADJUST " %d %%") )
                s.session()->setFadingTarget( float(100 - f) * 0.01f );
            if (ImGui::IsItemDeactivatedAfterEdit()){
                oss << "Fading " << f << " %";
                Action::manager().store(oss.str());
            }
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGuiToolkit::TextButton("Fading")) {
                s.session()->setFadingTarget(0.f);
                oss << "Fading 0 %";
                Action::manager().store(oss.str());
            }

            // import
            if ( ImGui::Button( ICON_FA_FILE_EXPORT " Import all", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
                Mixer::manager().import( &s );
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            ImGui::Text("Sources");

            // file open
            if ( ImGui::Button( ICON_FA_FILE_UPLOAD " Open", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
                Mixer::manager().set( s.detach() );
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            ImGui::Text("Session");

            botom = ImGui::GetCursorPos();

            // icon (>) to open player
            if ( s.playable() ) {
                ImGui::SetCursorPos(top);
                std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
                if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                    UserInterface::manager().showSourceEditor(&s);
                top.x += ImGui::GetFrameHeight();
            }

        }
        else
        {
            // file open
            if ( ImGui::Button( ICON_FA_ARROW_CIRCLE_RIGHT " Transition", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
            {
                TransitionView *tv = static_cast<TransitionView*>(Mixer::manager().view(View::TRANSITION));
                tv->attach(&s);
                Mixer::manager().setView(View::TRANSITION);
                WorkspaceWindow::clearWorkspace();
            }
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            ImGui::Text("Session");

            botom = ImGui::GetCursorPos();
        }

        ImGui::SetCursorPos(top);
        if (ImGuiToolkit::IconButton(3, 5, "Show in finder"))
            SystemToolkit::open(SystemToolkit::path_filename(s.path()));

    }
    else
        info.reset();

    ImGui::SetCursorPos(botom);
}

void ImGuiVisitor::visit (SessionGroupSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    Session *session = s.session();
    if (session == nullptr)
        return;

    // info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    // Show list of sources in text bloc (multi line, dark background)
    ImGuiTextBuffer listofsources;
    listofsources.append( BaseToolkit::joinned( session->getNameList(), '\n').c_str() );
    ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    ImGui::InputTextMultiline("Sources", (char *)listofsources.c_str(), listofsources.size(),
                              ImVec2(IMGUI_RIGHT_ALIGN, CLAMP(session->size(), 2, 5) * ImGui::GetTextLineHeightWithSpacing()),
                              ImGuiInputTextFlags_ReadOnly);
    ImGui::PopStyleColor(1);

    if ( ImGui::Button( ICON_FA_TIMES "  Uncover", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
        Mixer::manager().import( &s );

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
        }
    }
    else
        info.reset();

    ImGui::SetCursorPos(botom);
}

void ImGuiVisitor::visit (RenderSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    // loopback provenance
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    int m = (int) s.renderingProvenance();
    if (ImGuiToolkit::ComboIcon("##SelectRender", &m, RenderSource::ProvenanceMethod)) {
        s.setRenderingProvenance((RenderSource::RenderSourceProvenance)m);
        oss << "Mode " << m;
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Mode ")) {
        s.setRenderingProvenance(RenderSource::RENDER_TEXTURE);
        oss << "Mode 0";
        Action::manager().store(oss.str());
    }

    if (s.renderingProvenance() == RenderSource::RENDER_CANVAS) {
        static int __canvas = -1;
        if (__canvas < 0)
            __canvas = s.canvasProvenance() + 1;

        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        ImGui::SliderInt("##Canvas", &__canvas, 1, Canvas::manager().size(), "%02d");
        if (ImGui::IsItemDeactivatedAfterEdit()){
            s.setCanvasProvenance((size_t)__canvas - 1);
            oss << "Canvas " << __canvas;
            Action::manager().store(oss.str());
            __canvas = -1;
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Canvas")) {
            s.setCanvasProvenance(0);
            oss << "Canvas 01";
            Action::manager().store(oss.str());
            __canvas = -1;
        }

    }

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
            top.x += ImGui::GetFrameHeight();
        }
    }
    else
        info.reset();

    ImGui::SetCursorPos(botom);
}

void ImGuiVisitor::visit (FrameBufferFilter&)
{

}

void ImGuiVisitor::visit (PassthroughFilter&)
{

}

void ImGuiVisitor::visit (DelayFilter& f)
{
    ImGuiIO& io = ImGui::GetIO();

//    if (ImGuiToolkit::IconButton(ICON_FILTER_DELAY)) {
//        f.setDelay(0.f);
//        Action::manager().store("Delay 0 s");
//    }
//    ImGui::SameLine(0, IMGUI_SAME_LINE);
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    float d = f.delay();
    if (ImGui::SliderFloat("##Delay", &d, 0.f, 2.f, "%.2f s"))
        f.setDelay(d);
    if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
        d = CLAMP( d + 0.01f * io.MouseWheel, 0.f, 2.f);
        f.setDelay(d);
        oss << "Delay " << std::setprecision(3) << d << " s";
        Action::manager().store(oss.str());
    }
    if (ImGui::IsItemDeactivatedAfterEdit()) {
        oss << "Delay " << std::setprecision(3) << d << " s";
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Delay ")) {
        f.setDelay(0.5f);
        oss << "Delay 0.5 s";
        Action::manager().store(oss.str());
    }
}

void ImGuiVisitor::visit (ResampleFilter& f)
{
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    int m = (int) f.factor();
    if (ImGui::Combo("##Factor", &m, ResampleFilter::factor_label, IM_ARRAYSIZE(ResampleFilter::factor_label) )) {
        f.setFactor( m );
        oss << "Resample " << ResampleFilter::factor_label[m];
        Action::manager().store(oss.str());
        info.reset();
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Factor")) {
        f.setFactor( 0 );
        oss << "Resample " << ResampleFilter::factor_label[0];
        Action::manager().store(oss.str());
        info.reset();
    }
}

void list_parameters_(ImageFilter &f, std::ostringstream &oss)
{
    ImGuiIO& io = ImGui::GetIO();

    std::map<std::string, float> filter_parameters = f.program().parameters();
    for (auto param = filter_parameters.rbegin(); param != filter_parameters.rend(); ++param)
    {
        ImGui::PushID( param->first.c_str() );
        float v = param->second;
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if (ImGui::SliderFloat( "##ImageFilterParameterEdit", &v, 0.f, 1.f, "%.2f")) {
            f.setProgramParameter(param->first, v);
        }
        if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
            v = CLAMP( v + 0.01f * io.MouseWheel, 0.f, 1.f);
            f.setProgramParameter(param->first, v);
            oss << " " << param->first << " " << std::setprecision(3) << v;
            Action::manager().store(oss.str());
        }
        if (ImGui::IsItemDeactivatedAfterEdit()) {
            oss << " " << param->first << " " << std::setprecision(3) <<param->second;
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton( param->first.c_str() )) {
            v = 0.5f;
            f.setProgramParameter(param->first, v);
            oss << " " << param->first << " " << std::setprecision(3) << v;
            Action::manager().store(oss.str());
        }
        ImGui::PopID();
    }
}

void ImGuiVisitor::visit (BlurFilter& f)
{
    oss << "Blur ";

    // Method selection
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    int m = (int) f.method();
    if (ImGui::Combo("##MethodBlur", &m, BlurFilter::method_label, IM_ARRAYSIZE(BlurFilter::method_label) )) {
        f.setMethod( m );
        oss << BlurFilter::method_label[m];
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Method")) {
        f.setMethod( 0 );
        oss << BlurFilter::method_label[0];
        Action::manager().store(oss.str());
    }

    // List of parameters
    list_parameters_(f, oss);
}

void ImGuiVisitor::visit (SharpenFilter& f)
{
    oss << "Sharpen ";

    // Method selection
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    int m = (int) f.method();
    if (ImGui::Combo("##MethodSharpen", &m, SharpenFilter::method_label, IM_ARRAYSIZE(SharpenFilter::method_label) )) {
        f.setMethod( m );
        oss << SharpenFilter::method_label[m];
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Method")) {
        f.setMethod( 0 );
        oss << SharpenFilter::method_label[0];
        Action::manager().store(oss.str());
    }

    // List of parameters
    list_parameters_(f, oss);
}

void ImGuiVisitor::visit (SmoothFilter& f)
{
    oss << "Smooth ";

    // Method selection
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    int m = (int) f.method();
    if (ImGui::Combo("##MethodSmooth", &m, SmoothFilter::method_label, IM_ARRAYSIZE(SmoothFilter::method_label) )) {
        f.setMethod( m );
        oss << SmoothFilter::method_label[m];
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Method")) {
        f.setMethod( 0 );
        oss << SmoothFilter::method_label[0];
        Action::manager().store(oss.str());
    }

    // List of parameters
    list_parameters_(f, oss);
}

void ImGuiVisitor::visit (EdgeFilter& f)
{
    oss << "Edge ";

    // Method selection
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    int m = (int) f.method();
    if (ImGui::Combo("##MethodEdge", &m, EdgeFilter::method_label, IM_ARRAYSIZE(EdgeFilter::method_label) )) {
        f.setMethod( m );
        oss << EdgeFilter::method_label[m];
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Method")) {
        f.setMethod( 0 );
        oss << EdgeFilter::method_label[0];
        Action::manager().store(oss.str());
    }

    // List of parameters
    list_parameters_(f, oss);
}

void ImGuiVisitor::visit (AlphaFilter& f)
{
    ImGuiIO& io = ImGui::GetIO();
    oss << "Alpha ";

    // Alpha operation selection
    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    int m = (int) f.operation();
    if (ImGui::Combo("##Operation", &m, AlphaFilter::operation_label, IM_ARRAYSIZE(AlphaFilter::operation_label) )) {
        f.setOperation( m );
        oss << AlphaFilter::operation_label[m];
        Action::manager().store(oss.str());
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    if (ImGuiToolkit::TextButton("Operation")) {
        f.setOperation( 0 );
        oss << AlphaFilter::operation_label[0];
        Action::manager().store(oss.str());
    }

    // List of parameters
    std::map<std::string, float> filter_parameters = f.program().parameters();

    if ( m == AlphaFilter::ALPHA_CHROMAKEY || m == AlphaFilter::ALPHA_LUMAKEY)
    {
        float v = filter_parameters["Tolerance"];
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if (ImGui::SliderFloat( "##Tolerance", &v, 0.f, 1.f, "%.2f")) {
            f.setProgramParameter("Tolerance", v);
        }
        if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
            v = CLAMP( v + 0.01f * io.MouseWheel, 0.f, 1.f);
            f.setProgramParameter("Tolerance", v);
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Tolerance" << " " << std::setprecision(3) << v;
            Action::manager().store(oss.str());
        }
        if (ImGui::IsItemDeactivatedAfterEdit()) {
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Tolerance" << " " << std::setprecision(3) << v;
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Tolerance")) {
            v = 0.f;
            f.setProgramParameter("Tolerance", v);
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Tolerance" << " " << std::setprecision(3) << v;
            Action::manager().store(oss.str());
        }
        float t = filter_parameters["Threshold"];
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if (ImGui::SliderFloat( "##Threshold", &t, 0.f, 1.f, "%.2f")) {
            f.setProgramParameter("Threshold", t);
        }
        if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
            t = CLAMP( t + 0.01f * io.MouseWheel, 0.f, 1.f);
            f.setProgramParameter("Threshold", t);
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Threshold" << " " << std::setprecision(3) << t;
            Action::manager().store(oss.str());
        }
        if (ImGui::IsItemDeactivatedAfterEdit()) {
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Threshold" << " " << std::setprecision(3) << t;
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Threshold")) {
            t = 0.f;
            f.setProgramParameter("Threshold", t);
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Threshold" << " " << std::setprecision(3) << t;
            Action::manager().store(oss.str());
        }
    }

    if ( m == AlphaFilter::ALPHA_CHROMAKEY || m == AlphaFilter::ALPHA_FILL)
    {        
        // read color from filter
        float color[3] = {filter_parameters["Red"], filter_parameters["Green"], filter_parameters["Blue"]};

        // show color
        if (ImGui::ColorEdit3("Chromakey Color", color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) )
        {
            f.setProgramParameter("Red",   color[0]);
            f.setProgramParameter("Green", color[1]);
            f.setProgramParameter("Blue",  color[2]);
        }
        if (ImGui::IsItemDeactivatedAfterEdit()) {
            char buf[64];
            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp((int)ceil(255.f * color[0]),0,255),
                           ImClamp((int)ceil(255.f * color[1]),0,255), ImClamp((int)ceil(255.f * color[2]),0,255));
            oss << " Color " << buf;
            Action::manager().store(oss.str());
        }

        // offer to pick color
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if ( ImGui::Button( ICON_FA_EYE_DROPPER " Open selector", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ){
            if ( DialogToolkit::ColorPickerDialog::instance().busy()) {
                Log::Warning("Close previously openned color picker.");
            }
            else {
                // prepare the color picker to start with filter's color
                DialogToolkit::ColorPickerDialog::instance().setRGB( std::make_tuple(color[0], color[1], color[2]) );

                // declare function to be called
                std::string msg = oss.str();
                auto applyColor = [&f, msg]( std::tuple<float, float, float> c)  {
                    f.setProgramParameter("Red",   std::get<0>(c));
                    f.setProgramParameter("Green", std::get<1>(c));
                    f.setProgramParameter("Blue",  std::get<2>(c));
                    char buf[1024];
                    ImFormatString(buf, IM_ARRAYSIZE(buf), "%scolor #%02X%02X%02X",
                                   msg.c_str(),
                                   ImClamp((int)ceil(255.f * std::get<0>(c)),0,255),
                                   ImClamp((int)ceil(255.f * std::get<1>(c)),0,255),
                                   ImClamp((int)ceil(255.f * std::get<2>(c)),0,255));
                    Action::manager().store(buf);
                };

                // open dialog (starts a thread that will callthe 'applyColor' function
                DialogToolkit::ColorPickerDialog::instance().open( applyColor );
            }
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Color")) {
            f.setProgramParameter("Red",   0.f);
            f.setProgramParameter("Green", 1.f);
            f.setProgramParameter("Blue",  0.f);
            oss << " Color #00FF00";
            Action::manager().store(oss.str());
        }

    }
    // Luminance extra parameter
    else {
        float v = filter_parameters["Luminance"];
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if (ImGui::SliderFloat( "##Luminance", &v, 0.f, 1.f, "%.2f")) {
            f.setProgramParameter("Luminance", v);
        }
        if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
            v = CLAMP( v + 0.01f * io.MouseWheel, 0.f, 1.f);
            f.setProgramParameter("Luminance", v);
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Luminance" << " " << std::setprecision(3) << v;
            Action::manager().store(oss.str());
        }
        if (ImGui::IsItemDeactivatedAfterEdit()) {
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Luminance" << " " << std::setprecision(3) << v;
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Luminance")) {
            v = 0.f;
            f.setProgramParameter("Luminance", v);
            oss << AlphaFilter::operation_label[ f.operation() ];
            oss << " " << "Luminance" << " " << std::setprecision(3) << v;
            Action::manager().store(oss.str());
        }
    }

}


void list_textures_(ImageFilter &f, std::ostringstream &oss)
{
    std::map<std::string, uint64_t > filter_textures = f.program().textures();

    for (auto tex = filter_textures.rbegin(); tex != filter_textures.rend(); ++tex)
    {
        ImGui::PushID( tex->first.c_str() );
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);

        Source *source = Mixer::manager().findSource( tex->second );
        std::string label = source == nullptr ? "Select source" : ""
            + std::string( source->initials() )
            + " - "
            + source->name();
        if (ImGui::BeginCombo(tex->first.c_str(), label.c_str()) )
        {
            SourceList::iterator iter;
            for (iter = Mixer::manager().session()->begin(); iter != Mixer::manager().session()->end(); ++iter)
            {
                label = std::string((*iter)->initials()) + " - " + (*iter)->name();
                if (ImGui::Selectable( label.c_str())) {
                    f.setProgramTexture(tex->first, (*iter)->id() );
                    oss << " Texture " << tex->first << " " << label;
                    Action::manager().store(oss.str());
                }
            }
            ImGui::EndCombo();
        }
        ImGui::PopID();
    }
}

void ImGuiVisitor::visit (ImageFilter& f)
{
    // Open Editor
    if ( ImGui::Button( ICON_FA_CODE "  Open editor", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
        UserInterface::manager().shadercontrol.setVisible(true);
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    ImGui::Text("Code");

    // List of parameters & textures
    oss << "Custom ";
    list_parameters_(f, oss);
    list_textures_(f, oss);

}

void ImGuiVisitor::visit (CloneSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    if ( !s.failed() ) {

        // link to origin source
        std::string label = std::string(s.origin()->initials()) + " - " + s.origin()->name();
        if (ImGui::Button(label.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) )) {
            Mixer::manager().setCurrentSource(s.origin());

            if (UserInterface::manager().navigator.pannelVisible())
                UserInterface::manager().navigator.showPannelSource( Mixer::manager().indexCurrentSource() );
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        ImGui::Text("Origin");

        // filter selection
        int type = (int) s.filter()->type();
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if (ImGuiToolkit::ComboIcon("##SelectFilter", &type, FrameBufferFilter::Types)) {
            s.setFilter( FrameBufferFilter::Type(type) );
            oss << "Filter " << std::get<2>(FrameBufferFilter::Types[type]);
            Action::manager().store(oss.str());
            info.reset();
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Filter")) {
            s.setFilter( FrameBufferFilter::FILTER_PASSTHROUGH );
            oss << "Filter None";
            Action::manager().store(oss.str());
            info.reset();
        }
        // offer option to convert to Custom shader to be able to edit code
        if (s.filter()->type() > FrameBufferFilter::FILTER_RESAMPLE
            && s.filter()->type() < FrameBufferFilter::FILTER_IMAGE) {
            ImageFilter *imf = static_cast<ImageFilter *>(s.filter());
            if (!imf->program().isTwoPass()) {
                ImGui::SameLine(0, IMGUI_SAME_LINE);
                if (ImGuiToolkit::TextButton(ICON_FA_CODE, "Convert to custom")) {
                    // get code and values
                    const std::pair<std::string, std::string> code = imf->program().code();
                    const std::map<std::string, float> param = imf->program().parameters();
                    FilteringProgram target;
                    target.setCode(code);
                    target.setParameters(param);
                    target.setName(imf->program().name());
                    // change filter and program
                    s.setFilter(FrameBufferFilter::FILTER_IMAGE);
                    imf = static_cast<ImageFilter *>(s.filter());
                    imf->setProgram(target);
                    info.reset();
                }
            }
        }

        // filter options
        s.filter()->accept(*this);

        ImVec2 botom = ImGui::GetCursorPos();

        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
        }

        ImGui::SetCursorPos(botom);
    }
    else {
        ImGuiToolkit::ButtonDisabled("No source", ImVec2(IMGUI_RIGHT_ALIGN, 0) );
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        ImGui::Text("Origin");
        info.reset();
    }
}

void ImGuiVisitor::visit (ShaderSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // stream info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    // filter options : open editor
    s.filter()->accept(*this);

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
            top.x += ImGui::GetFrameHeight();
        }
        ImGui::SetCursorPos(top);
        if (ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy shader code"))
            ImGui::SetClipboardText( s.filter()->program().code().first.c_str() );
    }
    else
        info.reset();

    ImGui::SetCursorPos(botom);
}

void ImGuiVisitor::visit (PatternSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // stream info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
    if (ImGui::BeginCombo("##Patterns", Pattern::get(s.pattern()->type()).label.c_str(), ImGuiComboFlags_HeightLarge) )
    {
        for (uint p = 0; p < Pattern::count(); ++p){
            pattern_descriptor pattern = Pattern::get(p);
            std::string label = pattern.label;
            if (pattern.available &&
                pattern.animated == s.playable() &&
                ImGui::Selectable( label.c_str(), p == s.pattern()->type() )) {
                s.setPattern(p, s.pattern()->resolution());
                info.reset();
                oss << "Pattern " << Pattern::get(p).label;
                Action::manager().store(oss.str());
                // ensure all sources are updated after the texture change of this one
                Mixer::manager().session()->execute([](Source *so) { so->touch(Source::SourceUpdate_Mask); });
            }
        }
        ImGui::EndCombo();
    }
    ImGui::SameLine(0, IMGUI_SAME_LINE);
    ImGui::Text("Generator");

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
            top.x += ImGui::GetFrameHeight();
        }
        ImGui::SetCursorPos(top);
        if (ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy gstreamer code"))
            ImGui::SetClipboardText(Pattern::get( s.pattern()->type() ).pipeline.c_str());
    }
    else
        info.reset();

    ImGui::SetCursorPos(botom);
}

void ImGuiVisitor::visit (DeviceSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    if ( !s.failed() ) {

        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if (ImGui::BeginCombo("Device", s.device().c_str()))
        {
            for (int d = 0; d < Device::manager().numDevices(); ++d){
                std::string namedev = Device::manager().name(d);
                if (ImGui::Selectable( namedev.c_str() )) {
                    if ( namedev.compare(s.device())==0 )
                        s.reconnect();
                    else {
                        s.setDevice(namedev);
                        info.reset();
                        oss << "Device " << namedev;
                        Action::manager().store(oss.str());
                    }
                    // ensure all sources are updated after the texture change of this one
                    Mixer::manager().session()->execute([](Source *so) { so->touch(Source::SourceUpdate_Mask); });
                }
            }
            ImGui::EndCombo();
        }
        ImVec2 botom = ImGui::GetCursorPos();

        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
            top.x += ImGui::GetFrameHeight();
        }

        // icon to show gstreamer properties
        ImGui::SetCursorPos(top);
        ImGuiToolkit::Icon(16, 16, false);
        if (ImGui::IsItemHovered()) {
            int index = Device::manager().index( s.device() );
            std::string prop = Device::manager().properties( index );
            ImGui::BeginTooltip();
            ImGui::PushTextWrapPos(ImGui::GetFontSize() * 14.0f);
            ImGui::TextUnformatted( prop.c_str());
            ImGui::PopTextWrapPos();
            ImGui::EndTooltip();
        }

        ImGui::SetCursorPos(botom);
    }
    else {

        info.reset();

    }

}


void ImGuiVisitor::visit (ScreenCaptureSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    if ( !s.failed() ) {

        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if (ImGui::BeginCombo("Window", s.window().c_str()))
        {
            for (int d = 0; d < ScreenCapture::manager().numWindow(); ++d){
                std::string namedev = ScreenCapture::manager().name(d);
                if (ImGui::Selectable( namedev.c_str() )) {
                    if ( namedev.compare(s.window())==0 )
                        s.reconnect();
                    else {
                        s.setWindow(namedev);
                        info.reset();
                        oss << "Window '" << namedev << "'";
                        Action::manager().store(oss.str());
                    }
                    // ensure all sources are updated after the texture change of this one
                    Mixer::manager().session()->execute([](Source *so) { so->touch(Source::SourceUpdate_Mask); });
                }
            }
            ImGui::EndCombo();
        }
        ImVec2 botom = ImGui::GetCursorPos();

        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
        }

        ImGui::SetCursorPos(botom);
    }
    else {

        info.reset();

    }

}


void ImGuiVisitor::visit (NetworkSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
    ImGui::Text("%s", s.connection().c_str());
    ImGui::PopStyleColor(1);

    // network info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
        }
    }
    else
        info.reset();

    ImGui::SetCursorPos(botom);
}


void ImGuiVisitor::visit (MultiFileSource& s)
{
    static uint64_t id = 0;

    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // information text
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() ) {
        // Filename pattern
        ImGuiTextBuffer info;
        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
        info.appendf("%s", SystemToolkit::base_filename(s.sequence().location).c_str());
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        ImGui::InputText("Filenames", (char *)info.c_str(), info.size(), ImGuiInputTextFlags_ReadOnly);
        ImGui::PopStyleColor(1);

        // change range
        static int _begin = -1;
        if (_begin < 0 || id != s.id())
            _begin = s.begin();
        static int _end = -1;
        if (_end < 0 || id != s.id())
            _end = s.end();
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        ImGui::DragIntRange2("##Range", &_begin, &_end, 1, s.sequence().min, s.sequence().max);
        if (ImGui::IsItemDeactivatedAfterEdit()){
            s.setRange( _begin, _end );
            oss << "Range " << _begin << "-" << _end;
            Action::manager().store(oss.str());
            _begin = _end = -1;
        }
        ImGui::SameLine(0, 0);
        if (ImGuiToolkit::TextButton("Range")) {
            s.setRange( s.sequence().min, s.sequence().max );
            oss << "Range " << s.sequence().min << "-" << s.sequence().max;
            Action::manager().store(oss.str());
            _begin = _end = -1;
        }

        // change framerate
        static int _fps = -1;
        if (_fps < 0 || id != s.id())
            _fps = s.framerate();
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        ImGui::SliderInt("##Framerate", &_fps, 1, 30, "%d fps");
        if (ImGui::IsItemDeactivatedAfterEdit()){
            s.setFramerate(_fps);
            oss << "Framerate " << _fps << " fps";
            Action::manager().store(oss.str());
            _fps = -1;
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Framerate")) {
            s.setFramerate(25);
            oss << "Framerate 25 fps";
            Action::manager().store(oss.str());
            _fps = -1;
        }

        botom = ImGui::GetCursorPos();

        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
            top.x += ImGui::GetFrameHeight();
        }

    }
    else
        info.reset();

    // offer to open file browser at location
    ImGui::SetCursorPos(top);
    if (ImGuiToolkit::IconButton(3, 5, "Show in finder"))
        SystemToolkit::open(SystemToolkit::path_filename( s.sequence().location ));

    ImGui::SetCursorPos(botom);

    if (id != s.id())
        id = s.id();
}

void ImGuiVisitor::visit (GenericStreamSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    float w = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // stream info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + w);
    s.accept(info);
    if ( !s.failed() )
        ImGui::Text("%s", info.str().c_str());
    else {
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_FAILED, 0.9f));
        ImGui::Text("%s", info.str().c_str());
        ImGui::PopStyleColor(1);
    }
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    // Prepare display pipeline text
    static int numlines = 0;
    const ImGuiContext& g = *GImGui;
    ImVec2 fieldsize(w,  MAX(3, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y);

    // Editor
    std::string _description = s.description();
    bool changed = ImGuiToolkit::InputCodeMultiline("Pipeline", &_description, fieldsize, &numlines);
    ImVec2 botom = ImGui::GetCursorPos();

    // Actions on the pipeline
    ImGui::SetCursorPos( ImVec2(top.x, botom.y - ImGui::GetFrameHeight()));
    if (ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy"))
        ImGui::SetClipboardText(_description.c_str());
    ImGui::SetCursorPos( ImVec2(top.x + 0.9 * ImGui::GetFrameHeight(), botom.y - ImGui::GetFrameHeight()));
    if (ImGuiToolkit::IconButton(ICON_FA_PASTE, "Paste")) {
        _description = std::string ( ImGui::GetClipboardText() );
        changed = true;
    }

    if (changed) {
        info.reset();
        s.setDescription(_description);
        oss << "Change pipeline";
        Action::manager().store( oss.str() );
        // ensure all sources are updated after the texture change of this one
        Mixer::manager().session()->execute([](Source *so) { so->touch(Source::SourceUpdate_Mask); });
    }

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.playable() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
        }
    }

    ImGui::SetCursorPos(botom);
}


void ImGuiVisitor::visit (SrtReceiverSource& s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // network info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    ImVec2 botom = ImGui::GetCursorPos();

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.stream()->isOpen() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
        }
        else
            info.reset();
    }
    else
        info.reset();

    ImGui::SetCursorPos(botom);

}


void ImGuiVisitor::visit(TextSource &s)
{
    ImVec2 top = ImGui::GetCursorPos();
    top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;
    float w = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;

    // network info
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x
                           + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
    s.accept(info);
    ImGui::Text("%s", info.str().c_str());
    ImGui::PopTextWrapPos();
    ImGui::Spacing();

    ImVec2 botom = ImGui::GetCursorPos();

    TextContents *tc = s.contents();
    if (tc) {
        // Prepare display text
        static int numlines = 0;
        const ImGuiContext &g = *GImGui;
        ImVec2 fieldsize(w, MAX(4, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y);

        // Text contents
        std::string _contents = tc->text();
        // if the content is a subtitle file
        if (tc->isSubtitle()) {
            static char dummy_str[1024];
            ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
            snprintf(dummy_str, 1024, "%s", _contents.c_str());
            ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
            ImGui::InputText("##Filesubtitle", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
            ImGui::PopStyleColor(1);
            botom = ImGui::GetCursorPos();
            // Action on the filename
            ImGui::SetCursorPos(ImVec2(top.x, botom.y - ImGui::GetFrameHeight()));
            if (ImGuiToolkit::IconButton(ICON_FA_EDIT, "Open in editor"))
                SystemToolkit::open(_contents.c_str());
            ImGui::SetCursorPos(
                ImVec2(top.x + 0.95 * ImGui::GetFrameHeight(), botom.y - ImGui::GetFrameHeight()));
            if (ImGuiToolkit::IconButton(ICON_FA_REDO_ALT, "Reload"))
                s.reload();
        }
        // general case of free text : text editor
        else {
            if (ImGuiToolkit::InputTextMultiline("Text", &_contents, fieldsize, &numlines)) {
                info.reset();
                tc->setText(_contents);
                oss << "Text changed";
                Action::manager().store(oss.str());
            }
            botom = ImGui::GetCursorPos();

            // Actions on the text
            ImGui::SetCursorPos(ImVec2(top.x, botom.y - ImGui::GetFrameHeight()));
            if (ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy"))
                ImGui::SetClipboardText(_contents.c_str());
            ImGui::SetCursorPos(
                ImVec2(top.x + 0.95 * ImGui::GetFrameHeight(), botom.y - ImGui::GetFrameHeight()));
            _contents = std::string(ImGui::GetClipboardText());
            if (_contents.empty())
                ImGui::TextDisabled(ICON_FA_PASTE);
            else {
                if (ImGuiToolkit::IconButton(ICON_FA_PASTE, "Paste")) {
                    info.reset();
                    tc->setText(_contents);
                    oss << "Text changed";
                    Action::manager().store(oss.str());
                }
            }
            ImGui::SetCursorPos(ImVec2(top.x, botom.y - 2.f * ImGui::GetFrameHeight()));
            if (ImGuiToolkit::IconButton(ICON_FA_CODE, "Pango markup"))
                SystemToolkit::open("https://docs.gtk.org/Pango/pango_markup.html");
        }

        // Text Properties
        ImGui::SetCursorPos(botom);

        ImVec4 color = ImGuiToolkit::ColorConvertARGBToFloat4(tc->color());
        if (ImGui::ColorEdit4("Text Color", (float *) &color, ImGuiColorEditFlags_AlphaPreview |
                          ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ) {
            tc->setColor( ImGuiToolkit::ColorConvertFloat4ToARGB(color) );
        }
        if (ImGui::IsItemDeactivatedAfterEdit()) {
            oss << "Change font color";
            Action::manager().store(oss.str());
        }
        std::string font = tc->fontDescriptor();
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        ImGuiToolkit::InputText("##Font", &font, ImGuiInputTextFlags_EnterReturnsTrue);
        if (ImGui::IsItemDeactivatedAfterEdit()) {
            tc->setFontDescriptor(font);
            oss << "Change font";
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Font")) {
            tc->setFontDescriptor("arial 24");
            tc->setColor(0xffffffff);
            oss << "Reset font";
            Action::manager().store(oss.str());
        }
        botom = ImGui::GetCursorPos();
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::IconButton(1, 13, "Pango font description"))
            SystemToolkit::open("https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html");
        ImGui::SetCursorPos(botom);

        color = ImGuiToolkit::ColorConvertARGBToFloat4(tc->outlineColor());
        if (ImGui::ColorEdit4("Text Outline Color", (float *) &color, ImGuiColorEditFlags_AlphaPreview |
                              ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ) {
            tc->setOutlineColor( ImGuiToolkit::ColorConvertFloat4ToARGB(color) );
        }
        if (ImGui::IsItemDeactivatedAfterEdit()) {
            oss << "Change outline color";
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        int outline = tc->outline();
        ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
        if (ImGui::Combo("##Outline", &outline, "None\0Border\0Border & shadow\0")) {
            tc->setOutline(outline);
            oss << "Change outline";
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Outline")) {
            tc->setOutline(2);
            tc->setOutlineColor(0xFF000000);
            oss << "Reset outline";
            Action::manager().store(oss.str());
        }

        // HORIZONTAL alignment
        bool on = true;
        uint var = tc->horizontalAlignment();
        on = var == 0;
        float _x = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;
        _x -= 4.f * (g.FontSize + g.Style.FramePadding.x + IMGUI_SAME_LINE);
        if (var != 1) {
            // not centered
            ImGui::SetNextItemWidth(_x);
            if (var > 2) {
                // Absolute position
                float align_x = tc->horizontalPadding();
                if (ImGui::SliderFloat("##Posx", &align_x, 0.f, 1.f, "%.2f")) {
                    tc->setHorizontalPadding(align_x);
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) {
                    oss << "Change h-align coordinates";
                    Action::manager().store(oss.str());
                }
                if (ImGui::IsItemHovered())
                    ImGuiToolkit::ToolTip( "Coordinates" );
            } else {
                // padding top or bottom
                int pad_x = (int) tc->horizontalPadding();
                if (ImGui::SliderInt("##Padx", &pad_x, 0, 1000)) {
                    tc->setHorizontalPadding((float) pad_x);
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) {
                    oss << "Change h-align padding";
                    Action::manager().store(oss.str());
                }
                if (ImGui::IsItemHovered())
                    ImGuiToolkit::ToolTip( "Padding" );
            }
            ImGui::SameLine(0, IMGUI_SAME_LINE);
        } else {
            _x += IMGUI_SAME_LINE + g.Style.FramePadding.x;
            ImGui::SetCursorPosX(_x);
        }
        if (ImGuiToolkit::ButtonIconToggle(17, 16, &on, "Left"))
            tc->setHorizontalAlignment(0);
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        on = var == 1;
        if (ImGuiToolkit::ButtonIconToggle(18, 16, &on, "Center"))
            tc->setHorizontalAlignment(1);
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        on = var == 2;
        if (ImGuiToolkit::ButtonIconToggle(19, 16, &on, "Right"))
            tc->setHorizontalAlignment(2);
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        on = var == 3;
        if (ImGuiToolkit::ButtonIconToggle(6, 10, &on, "Absolute"))
            tc->setHorizontalAlignment(3);
        if (var != tc->horizontalAlignment()){
            oss << "Change h-align";
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Horizontal")) {
            tc->setHorizontalAlignment(1);
            oss << "Reset h-align";
            Action::manager().store(oss.str());
        }

        // VERTICAL alignment
        var = tc->verticalAlignment();
        on = var == 0;
        _x = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;
        _x -= 4.f * (g.FontSize + g.Style.FramePadding.x + IMGUI_SAME_LINE);
        if (var != 2) {
            // not centered
            ImGui::SetNextItemWidth(_x);
            if (var > 2) {
                // Absolute position
                float align_y = tc->verticalPadding();
                if (ImGui::SliderFloat("##Posy", &align_y, 0.f, 1.f, "%.2f")) {
                    tc->setVerticalPadding(align_y);
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) {
                    oss << "Change v-align coordinates";
                    Action::manager().store(oss.str());
                }
                if (ImGui::IsItemHovered())
                    ImGuiToolkit::ToolTip( "Coordinates" );
            } else {
                // padding top or bottom
                int pad_y = (int) tc->verticalPadding();
                if (ImGui::SliderInt("##Pady", &pad_y, 0, 1000)) {
                    tc->setVerticalPadding((float) pad_y);
                }
                if (ImGui::IsItemDeactivatedAfterEdit()) {
                    oss << "Change v-align padding";
                    Action::manager().store(oss.str());
                }
                if (ImGui::IsItemHovered())
                    ImGuiToolkit::ToolTip( "Padding" );
            }
            ImGui::SameLine(0, IMGUI_SAME_LINE);
        } else {
            // no value slider for centered
            _x += IMGUI_SAME_LINE + g.Style.FramePadding.x;
             ImGui::SetCursorPosX(_x);
        }
        if (ImGuiToolkit::ButtonIconToggle(1, 17, &on, "Bottom"))
            tc->setVerticalAlignment(0);
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        on = var == 2;
        if (ImGuiToolkit::ButtonIconToggle(3, 17, &on, "Center"))
            tc->setVerticalAlignment(2);
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        on = var == 1;
        if (ImGuiToolkit::ButtonIconToggle(2, 17, &on, "Top"))
            tc->setVerticalAlignment(1);
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        on = var == 3;
        if (ImGuiToolkit::ButtonIconToggle(3, 10, &on, "Absolute"))
            tc->setVerticalAlignment(3);
        if (var != tc->verticalAlignment()){
            oss << "Change v-align";
            Action::manager().store(oss.str());
        }
        ImGui::SameLine(0, IMGUI_SAME_LINE);
        if (ImGuiToolkit::TextButton("Vertical")) {
            tc->setVerticalAlignment(2);
            oss << "Reset v-align";
            Action::manager().store(oss.str());
        }

        botom = ImGui::GetCursorPos();
    }

    if ( !s.failed() ) {
        // icon (>) to open player
        if ( s.stream()->isOpen() ) {
            ImGui::SetCursorPos(top);
            std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
            if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
                UserInterface::manager().showSourceEditor(&s);
        }
        else
            info.reset();
    }
    else
        info.reset();

    ImGui::SetCursorPos(botom);

}
