/*
  Q Light Controller
  showmanager.cpp

  Copyright (C) Massimo Callegari

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0.txt

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include <QInputDialog>
#include <QColorDialog>
#include <QMessageBox>
#include <QFileDialog>
#include <QVBoxLayout>
#include <QMouseEvent>
#include <QScrollBar>
#include <QComboBox>
#include <QSplitter>
#include <QSettings>
#include <QToolBar>
#include <QSpinBox>
#include <QLabel>
#include <QDebug>
#include <QUrl>

#include "functionselection.h"
#include "audioplugincache.h"
#include "rgbmatrixeditor.h"
#include "multitrackview.h"
#include "chasereditor.h"
#include "audioeditor.h"
#include "efxeditor.h"
#include "videoeditor.h"
#include "showmanager.h"
#include "sceneeditor.h"
#include "timingstool.h"
#include "qlcmacros.h"
#include "sequence.h"
#include "chaser.h"

#define SETTINGS_HSPLITTER "showmanager/hsplitter"
#define SETTINGS_VSPLITTER "showmanager/vsplitter"

ShowManager* ShowManager::s_instance = NULL;

ShowManager::ShowManager(QWidget* parent, Doc* doc)
    : QWidget(parent)
    , m_doc(doc)
    , m_show(NULL)
    , m_currentTrack(NULL)
    , m_currentScene(NULL)
    , m_sceneEditor(NULL)
    , m_currentEditor(NULL)
    , m_editorFunctionID(Function::invalidId())
    , m_selectedShowIndex(-1)
    , m_splitter(NULL)
    , m_vsplitter(NULL)
    , m_showview(NULL)
    , m_toolbar(NULL)
    , m_showsCombo(NULL)
    , m_addShowAction(NULL)
    , m_addTrackAction(NULL)
    , m_addSequenceAction(NULL)
    , m_addAudioAction(NULL)
    , m_addVideoAction(NULL)
    , m_copyAction(NULL)
    , m_pasteAction(NULL)
    , m_deleteAction(NULL)
    , m_colorAction(NULL)
    , m_lockAction(NULL)
    , m_timingsAction(NULL)
    , m_snapGridAction(NULL)
    , m_stopAction(NULL)
    , m_playAction(NULL)
{
    Q_ASSERT(s_instance == NULL);
    s_instance = this;

    Q_ASSERT(doc != NULL);

    new QVBoxLayout(this);
    layout()->setContentsMargins(0, 0, 0, 0);
    layout()->setSpacing(0);

    initActions();
    initToolbar();

    m_splitter = new QSplitter(Qt::Vertical, this);
    layout()->addWidget(m_splitter);
    //initMultiTrackView();
    m_showview = new MultiTrackView();
    // add container for multitrack & function editors view
    QWidget* gcontainer = new QWidget(this);
    m_splitter->addWidget(gcontainer);
    gcontainer->setLayout(new QVBoxLayout);
    gcontainer->layout()->setContentsMargins(0, 0, 0, 0);

    m_showview->setRenderHint(QPainter::Antialiasing);
    m_showview->setAcceptDrops(true);
    m_showview->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    m_showview->setBackgroundBrush(QBrush(QColor(88, 88, 88, 255), Qt::SolidPattern));
    connect(m_showview, SIGNAL(viewClicked ( QMouseEvent * )),
            this, SLOT(slotViewClicked( QMouseEvent * )));

    connect(m_showview, SIGNAL(showItemMoved(ShowItem*,quint32,bool)),
            this, SLOT(slotShowItemMoved(ShowItem*,quint32,bool)));
    connect(m_showview, SIGNAL(timeChanged(quint32)),
            this, SLOT(slotUpdateTime(quint32)));
    connect(m_showview, SIGNAL(trackClicked(Track*)),
            this, SLOT(slotTrackClicked(Track*)));
    connect(m_showview, SIGNAL(trackDoubleClicked(Track*)),
            this, SLOT(slotTrackDoubleClicked(Track*)));
    connect(m_showview, SIGNAL(trackMoved(Track*,int)),
            this, SLOT(slotTrackMoved(Track*,int)));
    connect(m_showview, SIGNAL(trackDelete(Track*)),
            this, SLOT(slotTrackDelete(Track*)));

    // split the multitrack view into two (left: tracks, right: function editors)
    m_vsplitter = new QSplitter(Qt::Horizontal, this);
    m_splitter->widget(0)->layout()->addWidget(m_vsplitter);
    QWidget* mcontainer = new QWidget(this);
    mcontainer->setLayout(new QHBoxLayout);
    mcontainer->layout()->setContentsMargins(0, 0, 0, 0);
    m_vsplitter->addWidget(mcontainer);
    m_vsplitter->widget(0)->layout()->addWidget(m_showview);

    // add container for function editors
    QWidget* ccontainer = new QWidget(this);
    m_vsplitter->addWidget(ccontainer);
    ccontainer->setLayout(new QVBoxLayout);
    ccontainer->layout()->setContentsMargins(0, 0, 0, 0);
    m_vsplitter->widget(1)->hide();

    // add container for scene editor
    QWidget* container = new QWidget(this);
    m_splitter->addWidget(container);
    container->setLayout(new QVBoxLayout);
    container->layout()->setContentsMargins(0, 0, 0, 0);
    m_splitter->widget(1)->hide();

    connect(m_doc, SIGNAL(clearing()), this, SLOT(slotDocClearing()));
    connect(m_doc, SIGNAL(functionRemoved(quint32)), this, SLOT(slotFunctionRemoved(quint32)));
    connect(m_doc, SIGNAL(loaded()), this, SLOT(slotDocLoaded()));

    QSettings settings;
    QVariant var = settings.value(SETTINGS_HSPLITTER);
    if (var.isValid() == true)
        m_splitter->restoreState(var.toByteArray());
    else
        m_splitter->setSizes(QList <int> () << int(this->width() / 2) << int(this->width() / 2));

    QVariant var2 = settings.value(SETTINGS_VSPLITTER);
    if (var2.isValid() == true)
        m_vsplitter->restoreState(var2.toByteArray());
    else
        m_vsplitter->setSizes(QList <int> () << int(this->width() / 2) << int(this->width() / 2));
}

ShowManager::~ShowManager()
{
    QSettings settings;
    settings.setValue(SETTINGS_HSPLITTER, m_splitter->saveState());
    settings.setValue(SETTINGS_VSPLITTER, m_vsplitter->saveState());

    ShowManager::s_instance = NULL;
}

ShowManager* ShowManager::instance()
{
    return s_instance;
}

void ShowManager::clearContents()
{
    hideRightEditor();
    showSceneEditor(NULL);
    m_showview->resetView();
    m_showsCombo->clear();
    m_show = NULL;
    m_currentScene = NULL;
    m_currentTrack = NULL;
}

void ShowManager::initActions()
{
    /* Manage actions */
    m_addShowAction = new QAction(QIcon(":/show.png"),
                                   tr("New s&how"), this);
    m_addShowAction->setShortcut(QKeySequence("CTRL+H"));
    connect(m_addShowAction, SIGNAL(triggered(bool)),
            this, SLOT(slotAddShow()));

    m_addTrackAction = new QAction(QIcon(":/edit_add.png"),
                                   tr("Add a &track or an existing function"), this);
    m_addTrackAction->setShortcut(QKeySequence("CTRL+N"));
    connect(m_addTrackAction, SIGNAL(triggered(bool)),
            this, SLOT(slotAddItem()));

    m_addSequenceAction = new QAction(QIcon(":/sequence.png"),
                                    tr("New s&equence"), this);
    m_addSequenceAction->setShortcut(QKeySequence("CTRL+E"));
    connect(m_addSequenceAction, SIGNAL(triggered(bool)),
            this, SLOT(slotAddSequence()));

    m_addAudioAction = new QAction(QIcon(":/audio.png"),
                                    tr("New &audio"), this);
    m_addAudioAction->setShortcut(QKeySequence("CTRL+A"));
    connect(m_addAudioAction, SIGNAL(triggered(bool)),
            this, SLOT(slotAddAudio()));

    m_addVideoAction = new QAction(QIcon(":/video.png"),
                                    tr("New vi&deo"), this);
    m_addVideoAction->setShortcut(QKeySequence("CTRL+D"));
    connect(m_addVideoAction, SIGNAL(triggered(bool)),
            this, SLOT(slotAddVideo()));

    /* Edit actions */
    m_copyAction = new QAction(QIcon(":/editcopy.png"),
                                tr("&Copy"), this);
    m_copyAction->setShortcut(QKeySequence("CTRL+C"));
    connect(m_copyAction, SIGNAL(triggered(bool)),
            this, SLOT(slotCopy()));
    m_copyAction->setEnabled(false);

    m_pasteAction = new QAction(QIcon(":/editpaste.png"),
                               tr("&Paste"), this);
    m_pasteAction->setShortcut(QKeySequence("CTRL+V"));
    connect(m_pasteAction, SIGNAL(triggered(bool)),
            this, SLOT(slotPaste()));
    m_pasteAction->setEnabled(false);

    m_deleteAction = new QAction(QIcon(":/editdelete.png"),
                                 tr("&Delete"), this);
    m_deleteAction->setShortcut(QKeySequence("Delete"));
    connect(m_deleteAction, SIGNAL(triggered(bool)),
            this, SLOT(slotDelete()));
    m_deleteAction->setEnabled(false);

    m_colorAction = new QAction(QIcon(":/color.png"),
                                tr("Change Co&lor"), this);
    m_colorAction->setShortcut(QKeySequence("CTRL+L"));
    connect(m_colorAction, SIGNAL(triggered(bool)),
           this, SLOT(slotChangeColor()));
    m_colorAction->setEnabled(false);

    m_lockAction = new QAction(QIcon(":/lock.png"),
                               tr("Lock item"), this);
    m_lockAction->setShortcut(QKeySequence("CTRL+K"));
    connect(m_lockAction, SIGNAL(triggered()),
            this, SLOT(slotChangeLock()));
    m_lockAction->setEnabled(false);

    m_timingsAction = new QAction(QIcon(":/speed.png"),
                                  tr("Item start time and duration"), this);
    m_timingsAction->setShortcut(QKeySequence("CTRL+T"));
    connect(m_timingsAction, SIGNAL(triggered()),
            this, SLOT(slotShowTimingsTool()));
    m_timingsAction->setEnabled(false);

    m_snapGridAction = new QAction(QIcon(":/grid.png"),
                                   tr("Snap to &Grid"), this);
    m_snapGridAction->setShortcut(QKeySequence("CTRL+G"));
    m_snapGridAction->setCheckable(true);
    connect(m_snapGridAction, SIGNAL(triggered(bool)),
           this, SLOT(slotToggleSnapToGrid(bool)));

    m_stopAction = new QAction(QIcon(":/player_stop.png"),
                                 tr("St&op"), this);
    m_stopAction->setShortcut(QKeySequence("CTRL+SPACE"));
    connect(m_stopAction, SIGNAL(triggered(bool)),
            this, SLOT(slotStopPlayback()));

    m_playAction = new QAction(QIcon(":/player_play.png"),
                                 tr("&Play"), this);
    m_playAction->setShortcut(QKeySequence("SPACE"));
    connect(m_playAction, SIGNAL(triggered(bool)),
            this, SLOT(slotStartPlayback()));
}

void ShowManager::initToolbar()
{
    // Add a toolbar to the dock area
    m_toolbar = new QToolBar("Show Manager", this);
    m_toolbar->setFloatable(false);
    m_toolbar->setMovable(false);
    layout()->addWidget(m_toolbar);
    m_toolbar->addAction(m_addShowAction);
    m_showsCombo = new QComboBox();
    m_showsCombo->setFixedWidth(250);
    m_showsCombo->setMaxVisibleItems(30);
    connect(m_showsCombo, SIGNAL(currentIndexChanged(int)),
            this, SLOT(slotShowsComboChanged(int)));
    m_toolbar->addWidget(m_showsCombo);
    m_toolbar->addSeparator();

    m_toolbar->addAction(m_addTrackAction);
    m_toolbar->addAction(m_addSequenceAction);
    m_toolbar->addAction(m_addAudioAction);
    m_toolbar->addAction(m_addVideoAction);

    m_toolbar->addSeparator();
    m_toolbar->addAction(m_copyAction);
    m_toolbar->addAction(m_pasteAction);
    m_toolbar->addAction(m_deleteAction);
    m_toolbar->addSeparator();

    m_toolbar->addAction(m_colorAction);
    m_toolbar->addAction(m_lockAction);
    m_toolbar->addAction(m_timingsAction);
    m_toolbar->addAction(m_snapGridAction);
    m_toolbar->addSeparator();

    // Time label and playback buttons
    m_timeLabel = new QLabel("00:00:00.00");
    m_timeLabel->setFixedWidth(150);
    m_timeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
    QFont timeFont = QApplication::font();
    timeFont.setBold(true);
    timeFont.setPixelSize(20);
    m_timeLabel->setFont(timeFont);
    m_toolbar->addWidget(m_timeLabel);
    m_toolbar->addSeparator();

    m_toolbar->addAction(m_stopAction);
    m_toolbar->addAction(m_playAction);

    /* Create an empty widget between help items to flush them to the right */
    QWidget* widget = new QWidget(this);
    widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    m_toolbar->addWidget(widget);

    /* Add time division elements */
    QLabel* timeLabel = new QLabel(tr("Time division:"));
    m_toolbar->addWidget(timeLabel);

    m_timeDivisionCombo = new QComboBox();
    m_timeDivisionCombo->setFixedWidth(100);
    m_timeDivisionCombo->addItem(tr("Time"), ShowHeaderItem::Time);
    m_timeDivisionCombo->addItem("BPM 4/4", ShowHeaderItem::BPM_4_4);
    m_timeDivisionCombo->addItem("BPM 3/4", ShowHeaderItem::BPM_3_4);
    m_timeDivisionCombo->addItem("BPM 2/4", ShowHeaderItem::BPM_2_4);
    m_toolbar->addWidget(m_timeDivisionCombo);
    connect(m_timeDivisionCombo, SIGNAL(currentIndexChanged(int)),
            this, SLOT(slotTimeDivisionTypeChanged(int)));

    m_bpmField = new QSpinBox();
    m_bpmField->setFixedWidth(70);
    m_bpmField->setMinimum(10);
    m_bpmField->setMaximum(240);
    m_bpmField->setValue(120);
    m_bpmField->setEnabled(false);
    m_toolbar->addWidget(m_bpmField);
    connect(m_bpmField, SIGNAL(valueChanged(int)),
            this, SLOT(slotBPMValueChanged(int)));
}

/*********************************************************************
 * Shows combo
 *********************************************************************/
void ShowManager::updateShowsCombo()
{
    int oldIndex = m_showsCombo->currentIndex();

    // protect poor Show Manager from drawing all the shows
    disconnect(m_showsCombo, SIGNAL(currentIndexChanged(int)),
            this, SLOT(slotShowsComboChanged(int)));

    m_showsCombo->clear();
    foreach (Function* f, m_doc->functionsByType(Function::ShowType))
    {
        // Insert in ascii order
        int insertPosition = 0;
        while (insertPosition < m_showsCombo->count() &&
               QString::localeAwareCompare(m_showsCombo->itemText(insertPosition), f->name()) <= 0)
                    ++insertPosition;
        m_showsCombo->insertItem(insertPosition, f->name(), QVariant(f->id()));
    }
    if (m_showsCombo->count() > 0)
    {
        m_addTrackAction->setEnabled(true);
    }
    else
    {
        m_addTrackAction->setEnabled(false);
        m_addSequenceAction->setEnabled(false);
        m_addAudioAction->setEnabled(false);
        m_addVideoAction->setEnabled(false);
    }

    if (m_show == NULL || m_show->getTracksCount() == 0)
    {
        m_deleteAction->setEnabled(false);
        m_pasteAction->setEnabled(false);
    }
    else
    {
        if (m_doc->clipboard()->hasFunction())
            m_pasteAction->setEnabled(true);
        m_deleteAction->setEnabled(true);
    }

    connect(m_showsCombo, SIGNAL(currentIndexChanged(int)),
            this, SLOT(slotShowsComboChanged(int)));

    if (m_showsCombo->count() == 0)
    {
        m_showview->resetView();
        m_show = NULL;
        m_currentScene = NULL;
        m_currentTrack = NULL;
        return;
    }

    if (m_selectedShowIndex < 0 || m_selectedShowIndex >= m_showsCombo->count())
        m_selectedShowIndex = 0;

    m_showsCombo->setCurrentIndex(m_selectedShowIndex);

    if (oldIndex != m_selectedShowIndex)
        updateMultiTrackView();
}

void ShowManager::slotShowsComboChanged(int idx)
{
    qDebug() << Q_FUNC_INFO << "Idx: " << idx;
    if (m_selectedShowIndex != idx)
    {
        m_selectedShowIndex = idx;
        hideRightEditor();
        updateMultiTrackView();
    }
}

void ShowManager::showSceneEditor(Scene *scene)
{
    if (m_sceneEditor != NULL)
    {
        emit functionManagerActive(false);       
        m_splitter->widget(1)->layout()->removeWidget(m_sceneEditor);
        m_splitter->widget(1)->hide();
        delete m_sceneEditor;
        m_sceneEditor = NULL;
    }

    if (scene == NULL)
        return;

    if (this->isVisible())
    {
        m_sceneEditor = new SceneEditor(m_splitter->widget(1), scene, m_doc, false);
        if (m_sceneEditor != NULL)
        {
            m_splitter->widget(1)->layout()->addWidget(m_sceneEditor);
            m_splitter->widget(1)->show();

            connect(this, SIGNAL(functionManagerActive(bool)),
                    m_sceneEditor, SLOT(slotFunctionManagerActive(bool)));
        }
    }
}

void ShowManager::hideRightEditor()
{
    if (m_currentEditor != NULL)
    {
        m_vsplitter->widget(1)->layout()->removeWidget(m_currentEditor);
        m_vsplitter->widget(1)->hide();
        delete m_currentEditor;
        m_currentEditor = NULL;
        m_editorFunctionID = Function::invalidId();
    }
}

void ShowManager::showRightEditor(Function *function)
{
    if (function != NULL && m_editorFunctionID == function->id())
        return;

    hideRightEditor();

    if (function == NULL || this->isVisible() == false)
        return;

    if (function->type() == Function::ChaserType)
    {
        Chaser *chaser = qobject_cast<Chaser*> (function);
        m_currentEditor = new ChaserEditor(m_vsplitter->widget(1), chaser, m_doc);
        if (m_currentEditor != NULL)
        {
            connect(m_currentEditor, SIGNAL(stepSelectionChanged(int)),
                    this, SLOT(slotStepSelectionChanged(int)));
        }
    }
    else if (function->type() == Function::SequenceType)
    {
        Sequence *sequence = qobject_cast<Sequence*> (function);
        m_currentEditor = new ChaserEditor(m_vsplitter->widget(1), sequence, m_doc);
        if (m_currentEditor != NULL)
        {
            ChaserEditor *editor = qobject_cast<ChaserEditor*>(m_currentEditor);

            editor->showOrderAndDirection(false);

            /** Signal from chaser editor to scene editor.
             *  When a step is clicked apply values immediately */
            connect(m_currentEditor, SIGNAL(applyValues(QList<SceneValue>&)),
                    m_sceneEditor, SLOT(slotSetSceneValues(QList <SceneValue>&)));

            /** Signal from scene editor to chaser editor.
             *  When a fixture value is changed, update the selected chaser step */
            connect(m_sceneEditor, SIGNAL(fixtureValueChanged(SceneValue,bool)),
                    m_currentEditor, SLOT(slotUpdateCurrentStep(SceneValue,bool)));

            connect(m_currentEditor, SIGNAL(stepSelectionChanged(int)),
                    this, SLOT(slotStepSelectionChanged(int)));
        }
    }
    else if (function->type() == Function::AudioType)
    {
        m_currentEditor = new AudioEditor(m_vsplitter->widget(1), qobject_cast<Audio*> (function), m_doc);
    }
    else if (function->type() == Function::RGBMatrixType)
    {
        m_currentEditor = new RGBMatrixEditor(m_vsplitter->widget(1), qobject_cast<RGBMatrix*> (function), m_doc);
    }
    else if (function->type() == Function::EFXType)
    {
        m_currentEditor = new EFXEditor(m_vsplitter->widget(1), qobject_cast<EFX*> (function), m_doc);
    }
    else if (function->type() == Function::VideoType)
    {
        m_currentEditor = new VideoEditor(m_vsplitter->widget(1), qobject_cast<Video*> (function), m_doc);
    }
    else
        return;

    if (m_currentEditor != NULL)
    {
        m_vsplitter->widget(1)->layout()->addWidget(m_currentEditor);
        m_vsplitter->widget(1)->show();
        m_currentEditor->show();
        m_editorFunctionID = function->id();
    }

}

void ShowManager::slotAddShow()
{
    bool ok;
    QString defaultName = QString("%1 %2").arg(tr("New Show")).arg(m_doc->nextFunctionID());
    QString showName = QInputDialog::getText(this, tr("Show name setup"),
                                         tr("Show name:"), QLineEdit::Normal,
                                         defaultName, &ok);

    if (ok == true)
    {
        m_show = new Show(m_doc);
        if (showName.isEmpty() == false)
            m_show->setName(showName);
        else
            m_show->setName(defaultName);
        Function *f = qobject_cast<Function*>(m_show);
        if (m_doc->addFunction(f) == true)
        {
            // modify the new selected Show index
            int insertPosition = 0;
            while (insertPosition < m_showsCombo->count() &&
                    QString::localeAwareCompare(m_showsCombo->itemText(insertPosition), m_show->name()) <= 0)
                ++insertPosition;
            m_selectedShowIndex = insertPosition;
            updateShowsCombo();
            m_copyAction->setEnabled(false);
            if (m_doc->clipboard()->hasFunction())
                m_pasteAction->setEnabled(true);
            showSceneEditor(NULL);
            hideRightEditor();
            m_currentScene = NULL;
            m_currentTrack = NULL;
        }
    }
}

void ShowManager::slotAddItem()
{
    if (m_show == NULL)
        return;

    FunctionSelection fs(this, m_doc);
    // Forbid self-containment
    QList<quint32> disabledList;
    foreach (Function* function, m_doc->functions())
    {
        if (function->contains(m_show->id()))
            disabledList << function->id();
    }
    fs.setDisabledFunctions(disabledList);

    fs.setMultiSelection(false);
    fs.setFilter(Function::SceneType | Function::ChaserType | Function::SequenceType | Function::AudioType | Function::RGBMatrixType | Function::EFXType);
    fs.disableFilters(Function::ShowType | Function::ScriptType | Function::CollectionType);
    fs.showNewTrack(true);

    if (fs.exec() == QDialog::Accepted)
    {
        QList <quint32> ids = fs.selection();
        if (ids.count() == 0)
            return;
        quint32 selectedID = ids.first();

        /**
         * Here there are 8 cases:
         * 1) a new empty track
         * 2) an existing scene: create a new track with a 10 seconds Sequence
         * 3) an existing sequence
         *    3.1) append to an existing track
         *    3.2) create a new track bound to the Sequence's Scene ID
         * 4) an existing chaser
         *    4.1) append to the selected track
         *    4.3) create a new track
         * 5) an existing audio:
         *    5.1) append to the selected track
         *    5.2) create a new track
         * 6) an existing RGB Matrix:
         *    6.1) append to the selected track
         *    6.2) create a new track
         * 7) an existing EFX:
         *    7.1) append to the selected track
         *    7.2) create a new track
         * 8) an existing video:
         *    8.1) append to the selected track
         *    8.2) create a new track
         **/

        bool createTrack = false;
        quint32 newTrackBoundID = Function::invalidId();

        if (selectedID == Function::invalidId())
        {
            createTrack = true;
        }
        else
        {
            Function *selectedFunc = m_doc->function(selectedID);
            if (selectedFunc == NULL) // maybe a popup here ?
                return;

            /** 2) an existing scene */
            if (selectedFunc->type() == Function::SceneType)
            {
                m_currentScene = qobject_cast<Scene*>(selectedFunc);
                newTrackBoundID = selectedFunc->id();
                createTrack = true;
            }
            else if (selectedFunc->type() == Function::ChaserType)
            {
                /** 4.1) add chaser to the currently selected track */
                if (m_currentTrack != NULL)
                {
                    m_showview->addSequence(qobject_cast<Chaser*>(selectedFunc), m_currentTrack);
                    m_doc->setModified();
                    return;
                }
                /** 4.2) It is necessary to create a new track (below) */
                createTrack = true;
            }
            else if (selectedFunc->type() == Function::SequenceType)
            {
                Sequence *sequence = qobject_cast<Sequence*>(selectedFunc);
                quint32 chsSceneID = sequence->boundSceneID();
                foreach (Track *track, m_show->tracks())
                {
                    /** 3.1) append to an existing track */
                    if (track->getSceneID() == chsSceneID)
                    {
                        Sequence *newSequence = qobject_cast<Sequence*>(sequence->createCopy(m_doc, true));
                        newSequence->setName(sequence->name() + tr(" (Copy)"));
                        newSequence->setDirection(Function::Forward);
                        newSequence->setRunOrder(Function::SingleShot);
                        m_showview->addSequence(newSequence, track);
                        m_doc->setModified();
                        return;
                    }
                }
                /** 3.2) It is necessary to create a new track (below) */
                createTrack = true;
                newTrackBoundID = sequence->boundSceneID();
                m_currentScene = qobject_cast<Scene*>(m_doc->function(newTrackBoundID));
            }
            else if (selectedFunc->type() == Function::AudioType)
            {
                /** 5.1) add audio to the currently selected track */
                if (m_currentTrack != NULL)
                {
                    m_showview->addAudio(qobject_cast<Audio*>(selectedFunc), m_currentTrack);
                    m_doc->setModified();
                    return;
                }
                /** 5.2) It is necessary to create a new track (below) */
                createTrack = true;
            }
            else if (selectedFunc->type() == Function::RGBMatrixType)
            {
                /** 6.1) add RGB Matrix to the currently selected track */
                if (m_currentTrack != NULL)
                {
                    m_showview->addRGBMatrix(qobject_cast<RGBMatrix*>(selectedFunc), m_currentTrack);
                    m_doc->setModified();
                    return;
                }
                /** 6.2) It is necessary to create a new track (below) */
                createTrack = true;
            }
            else if (selectedFunc->type() == Function::EFXType)
            {
                /** 7.1) add EFX to the currently selected track */
                if (m_currentTrack != NULL)
                {
                    m_showview->addEFX(qobject_cast<EFX*>(selectedFunc), m_currentTrack);
                    m_doc->setModified();
                    return;
                }
                /** 7.2) It is necessary to create a new track (below) */
                createTrack = true;
            }
            else if (selectedFunc->type() == Function::VideoType)
            {
                /** 8.1) add video to the currently selected track */
                if (m_currentTrack != NULL)
                {
                    m_showview->addVideo(qobject_cast<Video*>(selectedFunc), m_currentTrack);
                    m_doc->setModified();
                    return;
                }
                /** 8.2) It is necessary to create a new track (below) */
                createTrack = true;
            }
        }

        if (createTrack == true)
        {
            Track* newTrack = new Track(newTrackBoundID);
            if (newTrackBoundID != Function::invalidId() && m_currentScene != NULL)
                newTrack->setName(m_currentScene->name());
            else
                newTrack->setName(tr("Track %1").arg(m_show->tracks().count() + 1));

            m_show->addTrack(newTrack);
            m_showview->addTrack(newTrack);
            m_currentTrack = newTrack;
            if (newTrackBoundID == Function::invalidId())
                m_currentScene = NULL;
            else
                m_currentScene = qobject_cast<Scene*>(m_doc->function(newTrackBoundID));
        }

        if (selectedID != Function::invalidId())
        {
            Function *selectedFunc = m_doc->function(selectedID);
            if (selectedFunc == NULL) // maybe a popup here ?
                return;

            /** 2) create a 10 seconds Sequence on the current track */
            if (selectedFunc->type() == Function::SceneType)
            {
                Function* f = new Sequence(m_doc);
                Sequence *sequence = qobject_cast<Sequence*> (f);
                sequence->setBoundSceneID(m_currentScene->id());
                if (m_doc->addFunction(f) == true)
                {
                    sequence->setDirection(Function::Forward);
                    sequence->setRunOrder(Function::SingleShot);
                    sequence->setDurationMode(Chaser::PerStep);
                    m_currentScene->setVisible(false);
                    f->setName(QString("%1 %2").arg(tr("New Sequence")).arg(f->id()));
                    m_showview->addSequence(sequence, m_currentTrack);
                    ChaserStep step(m_currentScene->id(), m_currentScene->fadeInSpeed(), 10000, m_currentScene->fadeOutSpeed());
                    step.note = QString();
                    step.values.append(m_currentScene->values());
                    sequence->addStep(step);
                }
            }
            else if (selectedFunc->type() == Function::ChaserType)
            {
                /** 4.2) add chaser to the new track */
                m_showview->addSequence(qobject_cast<Chaser*>(selectedFunc), m_currentTrack);
            }
            else if (selectedFunc->type() == Function::SequenceType)
            {
                /** 3.2) create a new Scene and bind a Sequence clone to it */
                Sequence *sequence = qobject_cast<Sequence*>(selectedFunc);
                Sequence *newSequence = qobject_cast<Sequence*>(sequence->createCopy(m_doc, true));
                newSequence->setName(sequence->name() + tr(" (Copy)"));
                newSequence->setDirection(Function::Forward);
                newSequence->setRunOrder(Function::SingleShot);
                m_showview->addSequence(newSequence, m_currentTrack);
            }
            else if (selectedFunc->type() == Function::AudioType)
            {
                /** 5.2) add audio to the new track */
                Audio *audio = qobject_cast<Audio*> (selectedFunc);
                m_showview->addAudio(audio, m_currentTrack);
            }
            else if (selectedFunc->type() == Function::RGBMatrixType)
            {
                /** 6.2) add RGBMatrix to the new track */
                RGBMatrix *rgbm = qobject_cast<RGBMatrix*> (selectedFunc);
                m_showview->addRGBMatrix(rgbm, m_currentTrack);
            }
            else if (selectedFunc->type() == Function::EFXType)
            {
                /** 7.2) add EFX to the new track */
                EFX *efx = qobject_cast<EFX*> (selectedFunc);
                m_showview->addEFX(efx, m_currentTrack);
            }
            else if (selectedFunc->type() == Function::VideoType)
            {
                /** 8.2) add video to the new track */
                Video *video = qobject_cast<Video*> (selectedFunc);
                m_showview->addVideo(video, m_currentTrack);
            }
        }
        m_doc->setModified();

        m_addSequenceAction->setEnabled(true);
        m_addAudioAction->setEnabled(true);
        m_addVideoAction->setEnabled(true);
        m_showview->activateTrack(m_currentTrack);
        m_deleteAction->setEnabled(true);
        m_showview->updateViewSize();
    }
}

void ShowManager::slotAddSequence()
{
    // Overlapping check
    if (checkOverlapping(m_showview->getTimeFromCursor(), 1000) == true)
    {
        QMessageBox::warning(this, tr("Overlapping error"), tr("Overlapping not allowed. Operation canceled."));
        return;
    }

    if (m_currentTrack->getSceneID() == Function::invalidId())
    {
        m_currentScene = new Scene(m_doc);
        m_currentScene->setVisible(false);

        if (m_doc->addFunction(m_currentScene))
            m_currentScene->setName(tr("Scene for %1 - Track %2").arg(m_show->name()).arg(m_currentTrack->id() + 1));
        m_currentTrack->setSceneID(m_currentScene->id());
    }

    Function* f = new Sequence(m_doc);
    Sequence *sequence = qobject_cast<Sequence*> (f);
    sequence->setBoundSceneID(m_currentScene->id());

    if (m_doc->addFunction(f) == true)
    {
        sequence->setRunOrder(Function::SingleShot);
        m_currentScene->setVisible(false);
        f->setName(QString("%1 %2").arg(tr("New Sequence")).arg(f->id()));
        showSceneEditor(m_currentScene);
        showRightEditor(f);
        m_showview->addSequence(sequence, m_currentTrack);
    }
}

void ShowManager::slotAddAudio()
{
    QString fn;

    /* Create a file open dialog */
    QFileDialog dialog(this);
    dialog.setWindowTitle(tr("Open Audio File"));
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
    //dialog.selectFile(fileName());

    /* Append file filters to the dialog */
    QStringList extList = m_doc->audioPluginCache()->getSupportedFormats();

    QStringList filters;
    qDebug() << Q_FUNC_INFO << "Extensions: " << extList.join(" ");
    filters << tr("Audio Files (%1)").arg(extList.join(" "));
#if defined(WIN32) || defined(Q_OS_WIN)
    filters << tr("All Files (*.*)");
#else
    filters << tr("All Files (*)");
#endif
    dialog.setNameFilters(filters);

    /* Append useful URLs to the dialog */
    QList <QUrl> sidebar;
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
    dialog.setSidebarUrls(sidebar);

    /* Get file name */
    if (dialog.exec() != QDialog::Accepted)
        return;

    fn = dialog.selectedFiles().first();
    if (fn.isEmpty() == true)
        return;

    Function* f = new Audio(m_doc);
    Audio *audio = qobject_cast<Audio*> (f);
    if (audio->setSourceFileName(fn) == false)
    {
        QMessageBox::warning(this, tr("Unsupported audio file"), tr("This audio file cannot be played with QLC+. Sorry."));
        delete f;
        return;
    }
    // Overlapping check
    if (checkOverlapping(m_showview->getTimeFromCursor(), audio->totalDuration()) == true)
    {
        QMessageBox::warning(this, tr("Overlapping error"), tr("Overlapping not allowed. Operation canceled."));
        delete f;
        return;
    }
    if (m_doc->addFunction(f) == true)
    {
        m_showview->addAudio(audio, m_currentTrack);
    }
}

void ShowManager::slotAddVideo()
{
    QString fn;

    /* Create a file open dialog */
    QFileDialog dialog(this);
    dialog.setWindowTitle(tr("Open Video File"));
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
    //dialog.selectFile(fileName());

    /* Append file filters to the dialog */
    QStringList extList = Video::getVideoCapabilities();

    QStringList filters;
    qDebug() << Q_FUNC_INFO << "Extensions: " << extList.join(" ");
    filters << tr("Video Files (%1)").arg(extList.join(" "));
#if defined(WIN32) || defined(Q_OS_WIN)
    filters << tr("All Files (*.*)");
#else
    filters << tr("All Files (*)");
#endif
    dialog.setNameFilters(filters);

    /* Append useful URLs to the dialog */
    QList <QUrl> sidebar;
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
    dialog.setSidebarUrls(sidebar);

    /* Get file name */
    if (dialog.exec() != QDialog::Accepted)
        return;

    fn = dialog.selectedFiles().first();
    if (fn.isEmpty() == true)
        return;

    Function* f = new Video(m_doc);
    Video *video = qobject_cast<Video*> (f);
    if (video->setSourceUrl(fn) == false)
    {
        QMessageBox::warning(this, tr("Unsupported video file"), tr("This video file cannot be played with QLC+. Sorry."));
        delete f;
        return;
    }
    // Overlapping check
    if (checkOverlapping(m_showview->getTimeFromCursor(), video->totalDuration()) == true)
    {
        QMessageBox::warning(this, tr("Overlapping error"), tr("Overlapping not allowed. Operation canceled."));
        delete f;
        return;
    }
    if (m_doc->addFunction(f) == true)
    {
        m_showview->addVideo(video, m_currentTrack);
    }
}

void ShowManager::slotCopy()
{
    ShowItem *item = m_showview->getSelectedItem();
    if (item != NULL)
    {
        Function* function = m_doc->function(item->functionID());
        Q_ASSERT(function != NULL);

        m_doc->clipboard()->copyContent(m_show->id(), function);
        m_pasteAction->setEnabled(true);
    }
}

void ShowManager::slotPaste()
{
    if (m_doc->clipboard()->hasFunction() == false)
        return;

    // Get the Function copy and add it to Doc
    Function* clipboardCopy = m_doc->clipboard()->getFunction();
    quint32 copyDuration = clipboardCopy->totalDuration();

    // Overlapping check
    if (checkOverlapping(m_showview->getTimeFromCursor(), copyDuration) == true)
    {
        QMessageBox::warning(this, tr("Paste error"), tr("Overlapping paste not allowed. Operation canceled."));
        return;
    }
    //qDebug() << "Check overlap... cursor time:" << cursorTime << "msec";

    if (clipboardCopy != NULL)
    {
        // copy the function again, to allow multiple copies of the same function
        Function* newCopy = clipboardCopy->createCopy(m_doc, false);
        if (newCopy == NULL)
            return;

        if (clipboardCopy->type() == Function::ChaserType)
        {
            Chaser *chaser = qobject_cast<Chaser*>(newCopy);

            if (m_doc->addFunction(newCopy) == false)
            {
                delete newCopy;
                return;
            }
            m_showview->addSequence(chaser, m_currentTrack);
        }
        else if (clipboardCopy->type() == Function::SequenceType)
        {
            Sequence *sequence = qobject_cast<Sequence*>(newCopy);

            if (m_currentScene == NULL)
            {
                // No scene on the current track -> copy it from source sequence
                Scene* clipboardCopyScene = qobject_cast<Scene*>(m_doc->function(sequence->boundSceneID()));
                if (clipboardCopyScene == NULL)
                {
                    delete newCopy;
                    return;
                }
                Scene* newScene = static_cast<Scene*>(clipboardCopyScene->createCopy(m_doc, true));
                if (newScene == NULL)
                {
                    delete newCopy;
                    return;
                }
                m_currentScene = newScene;
                m_currentTrack->setSceneID(m_currentScene->id());
            }
            else
            {
                // Verify the Chaser copy steps against the current Scene
                foreach(ChaserStep cs, sequence->steps())
                {
                    foreach(SceneValue scv, cs.values)
                    {
                        if (m_currentScene->checkValue(scv) == false)
                        {
                            QMessageBox::warning(this, tr("Paste error"), tr("Trying to paste on an incompatible Scene. Operation canceled."));
                            delete newCopy;
                            return;
                        }
                    }
                }
            }

            // Bind the sequence to the track Scene ID
            sequence->setBoundSceneID(m_currentScene->id());

            if (m_doc->addFunction(newCopy) == false)
            {
                delete newCopy;
                return;
            }
            Track *track = m_currentTrack;
            track = m_show->getTrackFromSceneID(m_currentScene->id());
            m_showview->addSequence(sequence, track);
        }
        else if (clipboardCopy->type() == Function::AudioType)
        {
            if (m_doc->addFunction(newCopy) == false)
            {
                delete newCopy;
                return;
            }
            Audio *audio = qobject_cast<Audio*>(newCopy);
            m_showview->addAudio(audio, m_currentTrack);
        }
        else if (clipboardCopy->type() == Function::RGBMatrixType)
        {
            if (m_doc->addFunction(newCopy) == false)
            {
                delete newCopy;
                return;
            }
            RGBMatrix *rgbm = qobject_cast<RGBMatrix*>(newCopy);
            m_showview->addRGBMatrix(rgbm, m_currentTrack);
        }
        else if (clipboardCopy->type() == Function::EFXType)
        {
            if (m_doc->addFunction(newCopy) == false)
            {
                delete newCopy;
                return;
            }
            EFX *efx = qobject_cast<EFX*>(newCopy);
            m_showview->addEFX(efx, m_currentTrack);
        }
        else if (clipboardCopy->type() == Function::VideoType)
        {
            if (m_doc->addFunction(newCopy) == false)
            {
                delete newCopy;
                return;
            }
            Video *video = qobject_cast<Video*>(newCopy);
            m_showview->addVideo(video, m_currentTrack);
        }
        else if (clipboardCopy->type() == Function::SceneType)
        {
            if (m_doc->addFunction(newCopy) == false)
            {
                delete newCopy;
                return;
            }
            m_currentScene = qobject_cast<Scene*>(newCopy);
            Track* newTrack = new Track(m_currentScene->id());
            newTrack->setName(m_currentScene->name());
            m_show->addTrack(newTrack);
            //showSceneEditor(m_currentScene);
            m_showview->addTrack(newTrack);
            m_addSequenceAction->setEnabled(true);
            m_addAudioAction->setEnabled(true);
            m_addVideoAction->setEnabled(true);
            m_showview->activateTrack(newTrack);
            m_deleteAction->setEnabled(true);
            m_showview->updateViewSize();
        }
    }
}

void ShowManager::slotDelete()
{
    // find out if we're deleting a show item or a track
    bool isTrack = true;
    ShowItem *selectedItem = m_showview->getSelectedItem();
    if (selectedItem != NULL)
        isTrack = false;

    // get the ID of the function to delete (invalidId if nothing was selected)
    quint32 deleteID = m_showview->deleteSelectedItem();
    if (deleteID != Function::invalidId())
    {
        if (isTrack == false)
        {
            if (m_currentTrack != NULL)
            {
                hideRightEditor();
                showSceneEditor(NULL);
                m_currentTrack->removeShowFunction(selectedItem->showFunction());
            }
        }
        else
        {
            m_show->removeTrack(deleteID);
            m_doc->setModified();
            updateMultiTrackView();
        }
    }
}

void ShowManager::slotStopPlayback()
{
    m_playAction->setIcon(QIcon(":/player_play.png"));
    if (m_show != NULL && m_show->isRunning())
    {
        m_show->stop(functionParent());
        return;
    }
    m_showview->rewindCursor();
    m_timeLabel->setText("00:00:00.00");
}

void ShowManager::slotStartPlayback()
{
    if (m_showsCombo->count() == 0 || m_show == NULL)
        return;

    if (m_show->isRunning() == false)
    {
        m_show->start(m_doc->masterTimer(), functionParent(), m_showview->getTimeFromCursor());
        m_playAction->setIcon(QIcon(":/player_pause.png"));
    }
    else
    {
        if (m_show->isPaused())
        {
            m_playAction->setIcon(QIcon(":/player_pause.png"));
            m_show->setPause(false);
        }
        else
        {
            m_playAction->setIcon(QIcon(":/player_play.png"));
            m_show->setPause(true);
        }
    }
}

void ShowManager::slotShowStopped()
{
    slotUpdateTime(m_showview->getTimeFromCursor());
}

void ShowManager::slotTimeDivisionTypeChanged(int idx)
{
    QVariant var = m_timeDivisionCombo->itemData(idx);
    if (var.isValid())
    {
        m_showview->setHeaderType((ShowHeaderItem::TimeDivision)var.toInt());
        if (idx > 0)
            m_bpmField->setEnabled(true);
        else
            m_bpmField->setEnabled(false);
        if (m_show != NULL)
            m_show->setTimeDivision(ShowHeaderItem::tempoToString((ShowHeaderItem::TimeDivision)var.toInt()), m_bpmField->value());
    }
}

void ShowManager::slotBPMValueChanged(int value)
{
    m_showview->setBPMValue(value);
    QVariant var = m_timeDivisionCombo->itemData(m_timeDivisionCombo->currentIndex());
    if (var.isValid() && m_show != NULL)
        m_show->setTimeDivision(ShowHeaderItem::tempoToString((ShowHeaderItem::TimeDivision)var.toInt()), m_bpmField->value());
}

void ShowManager::slotViewClicked(QMouseEvent *event)
{
    Q_UNUSED(event)
    //qDebug() << Q_FUNC_INFO << "View clicked at pos: " << event->pos().x() << event->pos().y();
    showSceneEditor(NULL);
    hideRightEditor();
    m_colorAction->setEnabled(false);
    m_lockAction->setIcon(QIcon(":/lock.png"));
    m_lockAction->setEnabled(false);
    m_timingsAction->setEnabled(false);
    if (m_show != NULL && m_show->getTracksCount() == 0)
        m_deleteAction->setEnabled(false);
}

void ShowManager::slotShowItemMoved(ShowItem *item, quint32 time, bool moved)
{
    if (item == NULL)
        return;

    quint32 fid = item->functionID();
    Function *f = m_doc->function(fid);
    if (f == NULL)
        return;

    Sequence *sequence = NULL;

    if (f->type() == Function::SequenceType)
        sequence = qobject_cast<Sequence*>(f);

    if (sequence != NULL)
    {
        quint32 sceneID = sequence->boundSceneID();
        Function *sf = m_doc->function(sceneID);

        if (sf == NULL)
        {
            // The bound Scene no longer exists. Invalidate the Sequence
            sequence->setBoundSceneID(Function::invalidId());
        }
        else
        {
            Scene *boundScene = qobject_cast<Scene*>(sf);

            // if the clicked item represents another Sequence,
            // destroy the Scene editor cause they might share
            // the same bound Scene and Scene values might be overwritten
            if (fid != m_editorFunctionID)
                showSceneEditor(NULL);

            if (boundScene != m_currentScene || m_sceneEditor == NULL)
            {
                m_currentScene = boundScene;
                showSceneEditor(m_currentScene);
            }

            /* activate the new track */
            m_currentTrack = m_show->getTrackFromSceneID(sceneID);
            m_showview->activateTrack(m_currentTrack);
            showRightEditor(f);

            if (m_currentEditor != NULL)
            {
                ChaserEditor *editor = qobject_cast<ChaserEditor*>(m_currentEditor);
                editor->selectStepAtTime(time - item->getStartTime());
            }
        }
    }
    else
    {
        Track *track = m_show->tracks().at(item->getTrackIndex());
        m_showview->activateTrack(track);
        m_currentTrack = track;
        m_currentScene = NULL;
        showSceneEditor(NULL);
        showRightEditor(f);
    }

    m_copyAction->setEnabled(true);
    m_deleteAction->setEnabled(true);
    m_colorAction->setEnabled(true);
    m_lockAction->setEnabled(true);
    if (item->isLocked() == false)
        m_lockAction->setIcon(QIcon(":/lock.png"));
    else
        m_lockAction->setIcon(QIcon(":/unlock.png"));
    m_timingsAction->setEnabled(true);

    if (moved == true)
        m_doc->setModified();
}

void ShowManager::slotupdateTimeAndCursor(quint32 msec_time)
{
    //qDebug() << Q_FUNC_INFO << "time: " << msec_time;
    slotUpdateTime(msec_time);
    m_showview->moveCursor(msec_time);
}

void ShowManager::slotUpdateTime(quint32 msec_time)
{
    uint h, m, s;

    h = msec_time / MS_PER_HOUR;
    msec_time -= (h * MS_PER_HOUR);

    m = msec_time / MS_PER_MINUTE;
    msec_time -= (m * MS_PER_MINUTE);

    s = msec_time / MS_PER_SECOND;
    msec_time -= (s * MS_PER_SECOND);

    QString str;
    if (m_show && m_show->isRunning())
    {
        str = QString("%1:%2:%3.%4").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0'))
              .arg(s, 2, 10, QChar('0')).arg(msec_time / 100, 1, 10, QChar('0'));
    }
    else
        str = QString("%1:%2:%3.%4").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0'))
              .arg(s, 2, 10, QChar('0')).arg(msec_time / 10, 2, 10, QChar('0'));

    m_timeLabel->setText(str);
}

void ShowManager::slotTrackClicked(Track *track)
{
    m_currentTrack = track;
    if (track->getSceneID() == Function::invalidId())
        m_currentScene = NULL;
    else
    {
        Function *f = m_doc->function(track->getSceneID());
        if (f != NULL)
            m_currentScene = qobject_cast<Scene*>(f);
    }
    m_deleteAction->setEnabled(true);
    m_copyAction->setEnabled(true);
}

void ShowManager::slotTrackDoubleClicked(Track *track)
{
    bool ok;
    QString currentName = track->name();
    QString newTrackName = QInputDialog::getText(this, tr("Track name setup"),
                                         tr("Track name:"), QLineEdit::Normal,
                                         currentName, &ok);

    if (ok == true && newTrackName.isEmpty() == false)
    {
        track->setName(newTrackName);
        int idx = m_show->getAttributeIndex(track->name());
        m_show->renameAttribute(idx, track->name());
    }
}

void ShowManager::slotTrackMoved(Track *track, int direction)
{
    if (m_show != NULL)
        m_show->moveTrack(track, direction);
    updateMultiTrackView();
    m_doc->setModified();
}

void ShowManager::slotTrackDelete(Track *track)
{
    if (track == NULL)
        return;

    quint32 deleteID = m_showview->deleteSelectedItem();
    if (deleteID != Function::invalidId())
    {
        m_show->removeTrack(deleteID);
        m_doc->setModified();
        updateMultiTrackView();
    }
}

void ShowManager::slotChangeColor()
{
    ShowItem *item = m_showview->getSelectedItem();
    if (item != NULL)
    {
        QColor color = item->getColor();

        color = QColorDialog::getColor(color);
        if (!color.isValid())
            return;
        item->setColor(color);
        return;
    }
}

void ShowManager::slotChangeLock()
{
    ShowItem *item = m_showview->getSelectedItem();
    if (item != NULL)
    {
        if (item->isLocked() == false)
            m_lockAction->setIcon(QIcon(":/unlock.png"));
        else
            m_lockAction->setIcon(QIcon(":/lock.png"));
        item->setLocked(!item->isLocked());
    }
}

void ShowManager::slotShowTimingsTool()
{
    ShowItem *item = m_showview->getSelectedItem();

    if (item == NULL)
        return;

    TimingsTool *tt = new TimingsTool(item, this);

    Function *func = m_doc->function(item->functionID());
    if (func != NULL)
    {
        if (func->type() == Function::AudioType)
            tt->showDurationControls(false);
        if (func->type() == Function::RGBMatrixType || func->type() == Function::EFXType)
            tt->showDurationOptions(true);
    }

    connect(tt, SIGNAL(startTimeChanged(ShowItem*,int)),
            this, SLOT(slotShowItemStartTimeChanged(ShowItem*,int)));
    connect(tt, SIGNAL(durationChanged(ShowItem*,int,bool)),
            this, SLOT(slotShowItemDurationChanged(ShowItem*,int,bool)));
    tt->show();
}

void ShowManager::slotShowItemStartTimeChanged(ShowItem *item, int msec)
{
    if (item == NULL)
        return;

    if (item->isLocked() == false)
    {
        item->setStartTime(msec);
        item->setPos(m_showview->getPositionFromTime(msec), item->y());
        m_doc->setModified();
    }
}

void ShowManager::slotShowItemDurationChanged(ShowItem *item, int msec, bool stretch)
{
    if (item == NULL)
        return;

    item->setDuration(msec, stretch);
    m_doc->setModified();
}

void ShowManager::slotToggleSnapToGrid(bool enable)
{
    m_showview->setSnapToGrid(enable);
}

void ShowManager::slotChangeSize(int width, int height)
{
    if (m_showview != NULL)
        m_showview->setViewSize(width, height);
}

void ShowManager::slotStepSelectionChanged(int index)
{
    SequenceItem *seqItem = qobject_cast<SequenceItem*>(m_showview->getSelectedItem());
    if (seqItem != NULL)
        seqItem->setSelectedStep(index);
}

void ShowManager::slotDocClearing()
{
    m_showsCombo->clear();

    if (m_showview != NULL)
        m_showview->resetView();

    if (m_currentEditor != NULL)
    {
        m_vsplitter->widget(1)->layout()->removeWidget(m_currentEditor);
        delete m_currentEditor;
        m_currentEditor = NULL;
    }
    m_vsplitter->widget(1)->hide();

    if (m_sceneEditor != NULL)
    {
        emit functionManagerActive(false);
        m_splitter->widget(1)->layout()->removeWidget(m_sceneEditor);
        delete m_sceneEditor;
        m_sceneEditor = NULL;
    }
    m_splitter->widget(1)->hide();

    m_addTrackAction->setEnabled(false);
    m_addSequenceAction->setEnabled(false);
    m_addAudioAction->setEnabled(false);
    m_addVideoAction->setEnabled(false);
    m_copyAction->setEnabled(false);
    m_deleteAction->setEnabled(false);
    m_colorAction->setEnabled(false);
    m_timeLabel->setText("00:00:00.00");
}

void ShowManager::slotDocLoaded()
{
    m_show = NULL;
    m_currentScene = NULL;
    m_currentTrack = NULL;
    updateShowsCombo();
}

void ShowManager::slotFunctionRemoved(quint32 id)
{
    if (m_showsCombo->count() == 0)
        return;

    // check if ID is a Show
    for (int i = 0; i < m_showsCombo->count(); i++)
    {
        quint32 showID = m_showsCombo->itemData(i).toUInt();
        if (showID == id)
        {
            m_showsCombo->blockSignals(true);
            m_showsCombo->removeItem(i);

            if (i == m_selectedShowIndex)
            {
                m_show = NULL;
                m_selectedShowIndex = -1;
                updateMultiTrackView();
            }
            m_showsCombo->blockSignals(false);
            return;
        }
    }

    foreach (Function *function, m_doc->functionsByType(Function::ShowType))
    {
        Show *show = qobject_cast<Show*>(function);
        foreach(Track *track, show->tracks())
        {
            foreach (ShowFunction *sf, track->showFunctions())
            {
                if (sf->functionID() == id)
                    m_showview->deleteShowItem(track, sf);
            }

            // check if the Function being removed is a Scene bound to a Track
            if (track->getSceneID() == id)
                track->setSceneID(Function::invalidId());
        }
    }

    if (m_currentScene != NULL && m_currentScene->id() == id)
        m_currentScene = NULL;

    //if (isVisible())
    //    updateMultiTrackView();
}

void ShowManager::updateMultiTrackView()
{
    qDebug() << "[ShowManager] updateMultiTrackView...";
    m_showview->resetView();

    /* first of all get the ID of the selected Show */
    int idx = m_showsCombo->currentIndex();
    if (idx == -1)
        return;
    quint32 showID = m_showsCombo->itemData(idx).toUInt();

    m_show = qobject_cast<Show *>(m_doc->function(showID));
    if (m_show == NULL)
    {
        qDebug() << Q_FUNC_INFO << "Invalid show!";
        return;
    }

    // disconnect BPM field and update the view manually, to
    // prevent m_show time division override
    disconnect(m_bpmField, SIGNAL(valueChanged(int)), this, SLOT(slotBPMValueChanged(int)));

    m_bpmField->setValue(m_show->getTimeDivisionBPM());
    m_showview->setBPMValue(m_show->getTimeDivisionBPM());
    int tIdx = m_timeDivisionCombo->findData(QVariant(ShowHeaderItem::stringToTempo(m_show->getTimeDivisionType())));
    m_timeDivisionCombo->setCurrentIndex(tIdx);

    connect(m_bpmField, SIGNAL(valueChanged(int)), this, SLOT(slotBPMValueChanged(int)));
    connect(m_show, SIGNAL(timeChanged(quint32)), this, SLOT(slotupdateTimeAndCursor(quint32)));
    connect(m_show, SIGNAL(showFinished()), this, SLOT(slotStopPlayback()));
    connect(m_show, SIGNAL(stopped(quint32)), this, SLOT(slotShowStopped()));

    Track *firstTrack = NULL;

    foreach(Track *track, m_show->tracks())
    {
        if (firstTrack == NULL)
            firstTrack = track;

        quint32 boundSceneID = track->getSceneID();
        if (boundSceneID != Function::invalidId())
        {
            Function *f = m_doc->function(boundSceneID);
            if (f == NULL || f->type() != Function::SceneType)
                track->setSceneID(Function::invalidId());
        }

        m_showview->addTrack(track);

        foreach(ShowFunction *sf, track->showFunctions())
        {
            Function *fn = m_doc->function(sf->functionID());
            if (fn != NULL)
            {
                if (fn->type() == Function::ChaserType)
                {
                    Chaser *chaser = qobject_cast<Chaser*>(fn);
                    m_showview->addSequence(chaser, track, sf);
                }
                else if (fn->type() == Function::SequenceType)
                {
                    Sequence *sequence = qobject_cast<Sequence*>(fn);
                    m_showview->addSequence(sequence, track, sf);
                }
                else if (fn->type() == Function::AudioType)
                {
                    Audio *audio = qobject_cast<Audio*>(fn);
                    m_showview->addAudio(audio, track, sf);
                }
                else if (fn->type() == Function::RGBMatrixType)
                {
                    RGBMatrix *rgbm = qobject_cast<RGBMatrix*>(fn);
                    m_showview->addRGBMatrix(rgbm, track, sf);
                }
                else if (fn->type() == Function::EFXType)
                {
                    EFX *efx = qobject_cast<EFX*>(fn);
                    m_showview->addEFX(efx, track, sf);
                }
                else if (fn->type() == Function::VideoType)
                {
                    Video *video = qobject_cast<Video*>(fn);
                    m_showview->addVideo(video, track, sf);
                }
            }
        }
    }
    /** Set first track active */
    if (firstTrack != NULL)
    {
        m_currentTrack = firstTrack;
        if (m_currentTrack->getSceneID() != Function::invalidId())
            m_currentScene = qobject_cast<Scene*>(m_doc->function(m_currentTrack->getSceneID()));
        m_showview->activateTrack(m_currentTrack);
        m_copyAction->setEnabled(true);
        m_addSequenceAction->setEnabled(true);
        m_addAudioAction->setEnabled(true);
        m_addVideoAction->setEnabled(true);
    }
    else
    {
        m_addSequenceAction->setEnabled(false);
        m_addAudioAction->setEnabled(false);
        m_addVideoAction->setEnabled(false);
        m_currentScene = NULL;
        showSceneEditor(NULL);
    }
    if (m_doc->clipboard()->hasFunction())
        m_pasteAction->setEnabled(true);
    m_showview->updateViewSize();
}

bool ShowManager::checkOverlapping(quint32 startTime, quint32 duration)
{
    if (m_currentTrack == NULL)
        return false;

    foreach(ShowFunction *sf, m_currentTrack->showFunctions())
    {
        Function *func = m_doc->function(sf->functionID());
        if (func != NULL)
        {
            quint32 fst = sf->startTime();
            if ((startTime >= fst && startTime <= fst + sf->duration()) ||
                (fst >= startTime && fst <= startTime + duration))
            {
                return true;
            }
        }
    }

    return false;
}

void ShowManager::showEvent(QShowEvent* ev)
{
    qDebug() << Q_FUNC_INFO;
    emit functionManagerActive(true);
    QWidget::showEvent(ev);
    m_showview->show();
    m_showview->horizontalScrollBar()->setSliderPosition(0);
    m_showview->verticalScrollBar()->setSliderPosition(0);
    updateShowsCombo();
}

void ShowManager::hideEvent(QHideEvent* ev)
{
    qDebug() << Q_FUNC_INFO;
    emit functionManagerActive(false);
    QWidget::hideEvent(ev);

    if (m_currentEditor != NULL)
    {
        m_vsplitter->widget(1)->layout()->removeWidget(m_currentEditor);
        m_vsplitter->widget(1)->hide();
        delete m_currentEditor;
        m_currentEditor = NULL;
        m_editorFunctionID = Function::invalidId();
    }

    if (m_sceneEditor != NULL)
    {
        m_splitter->widget(1)->layout()->removeWidget(m_sceneEditor);
        m_splitter->widget(1)->hide();
        delete m_sceneEditor;
        m_sceneEditor = NULL;
    }

    ShowItem *item = m_showview->getSelectedItem();
    if (item != NULL)
        item->setSelected(false);
}

FunctionParent ShowManager::functionParent() const
{
    return FunctionParent::master();
}
