/*=========================================================================

   Program: ParaView
   Module:  pqTimelineWidget.cxx

   Copyright (c) 2005,2006 Sandia Corporation, Kitware Inc.
   All rights reserved.

   ParaView is a free software; you can redistribute it and/or modify it
   under the terms of the ParaView license version 1.2.

   See License_v1.2.txt for the full ParaView license.
   A copy of this license can be obtained by contacting
   Kitware Inc.
   28 Corporate Drive
   Clifton Park, NY 12065
   USA

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

========================================================================*/

#include "pqTimelineWidget.h"

#include "pqActiveObjects.h"
#include "pqAnimationManager.h"
#include "pqAnimationScene.h"
#include "pqAnimationTrackEditor.h"
#include "pqApplicationCore.h"
#include "pqCoreUtilities.h"
#include "pqPVApplicationCore.h"
#include "pqPropertyLinks.h"
#include "pqServerManagerModel.h"
#include "pqServerManagerModelItem.h"
#include "pqServerManagerObserver.h"
#include "pqTimelineItemDelegate.h"
#include "pqTimelineModel.h"
#include "pqTimelineView.h"
#include "pqUndoStack.h"

#include "vtkCompositeAnimationPlayer.h"
#include "vtkEventQtSlotConnect.h"
#include "vtkNew.h"
#include "vtkPVDataInformation.h"
#include "vtkSMDoubleVectorProperty.h"
#include "vtkSMParaViewPipelineController.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxy.h"
#include "vtkSMProxyManager.h"
#include "vtkSMProxyProperty.h"
#include "vtkSMRenderViewProxy.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMTrace.h"

#include <QHeaderView>
#include <QList>
#include <QToolButton>
#include <QVBoxLayout>

using TreeRow = QList<QStandardItem*>;

namespace
{
QString getRegistrationName(vtkSMProxy* proxy)
{
  pqServerManagerModel* smmodel = pqApplicationCore::instance()->getServerManagerModel();
  pqProxy* pqproxy = smmodel->findItem<pqProxy*>(proxy);

  return pqproxy ? pqproxy->getSMName() : QString();
}
}

struct pqTimelineWidget::pqInternals
{
  pqTimelineView* TimelineView = nullptr;
  pqTimelineModel* TimeModel = nullptr;
  pqTimelineWidget* Self = nullptr;
  TreeRow TimeKeeperRow;

  vtkNew<vtkEventQtSlotConnect> TimekeeperConnector;

  // Used to avoid updates loop when settings properties from UI modifications.
  bool IsUpdatingSceneFromUIFlag = false;

  // Store each animation cue by registration name.
  QMap<QString, pqAnimationCue*> Cues;

  pqInternals(pqTimelineWidget* self)
    : Self(self)
  {
    this->TimelineView = new pqTimelineView(self);
    this->TimeModel = new pqTimelineModel(self);
    this->TimeModel->setColumnCount(pqTimelineColumn::COUNT);
    this->TimelineView->setModel(this->TimeModel);

    QObject::connect(this->TimeModel, &QAbstractItemModel::rowsInserted,
      [&](const QModelIndex& parent, int first, int) {
        QModelIndexList newIndexes;
        newIndexes << this->TimeModel->index(first, pqTimelineColumn::NAME, parent);
        newIndexes << this->TimeModel->index(first, pqTimelineColumn::TIMELINE, parent);
        newIndexes << this->TimeModel->index(first, pqTimelineColumn::WIDGET, parent);
        this->TimelineView->createRowWidgets(newIndexes);
      });

    QObject::connect(this->TimelineView, &pqTimelineView::validateTrackRequested,
      [&](const pqAnimatedPropertyInfo& propInfo) { this->validateAnimationCue(propInfo); });

    QObject::connect(this->TimelineView, &pqTimelineView::newTrackRequested,
      [&](const pqAnimatedPropertyInfo& propInfo) { this->createAnimationCue(propInfo); });

    QObject::connect(this->TimelineView, &pqTimelineView::deleteTrackRequested,
      [&](const QString& registrationName) { this->deleteAnimationCue(registrationName); });

    QObject::connect(this->TimelineView, &pqTimelineView::editTrackRequested,
      [&](const QString& registrationName) { this->editAnimationCue(registrationName); });

    QObject::connect(this->TimelineView, &pqTimelineView::resetStartEndTimeRequested,
      [&]() { this->resetStartEndTime(); });

    this->TimelineView->setHeaderHidden(true);
    this->TimelineView->setIndentation(10);

    auto delegate = new pqTimelineItemDelegate(self, this->TimelineView);
    this->TimelineView->setItemDelegateForColumn(pqTimelineColumn::TIMELINE, delegate);

    QObject::connect(delegate, &pqTimelineItemDelegate::needsRepaint,
      [&]() { this->TimelineView->updateTimelines(); });

    // create time header row
    QMap<int, QVariant> timeData;
    timeData[Qt::ToolTipRole] = tr("If checked, scene use times from time sources.\nOtherwise, "
                                   "generate NumberOfFrames time entries.");
    this->TimeModel->createRow(pqTimelineTrack::TIME, tr("Time Sources"), timeData);
    this->TimeModel->setRowEnabled(pqTimelineTrack::TIME, false);

    // create animation header row
    QMap<int, QVariant> animationData;
    animationData[Qt::ToolTipRole] = tr("Check / Uncheck to enable all animation tracks.");
    this->TimeModel->createRow(pqTimelineTrack::ANIMATION_HEADER, "Animations", animationData);

    auto header = this->TimelineView->header();
    header->setStretchLastSection(false);
    header->setSectionResizeMode(pqTimelineColumn::TIMELINE, QHeaderView::Stretch);
    header->setSectionResizeMode(pqTimelineColumn::WIDGET, QHeaderView::ResizeToContents);
    header->setSectionResizeMode(pqTimelineColumn::NAME, QHeaderView::ResizeToContents);

    auto vlay = new QVBoxLayout(self);
    vlay->addWidget(this->TimelineView);
  }

  // store list of times under TIMES data role
  void setTrackTimes(const TreeRow& row, vtkSMProxy* proxy)
  {
    QVariantList times;
    vtkSMDoubleVectorProperty* dvp =
      vtkSMDoubleVectorProperty::SafeDownCast(proxy->GetProperty("TimestepValues"));
    if (dvp)
    {
      unsigned int numElems = dvp->GetNumberOfElements();
      for (unsigned int cc = 0; cc < numElems; cc++)
      {
        times.append(dvp->GetElement(cc));
      }
    }

    for (auto item : row)
    {
      item->setData(times, pqTimelineItemRole::TIMES);
    }
  }

  // delete cue from the scene
  void deleteAnimationCue(const QString& registrationName)
  {
    auto cue = this->Cues[registrationName];
    if (cue)
    {
      pqAnimationManager* mgr = pqPVApplicationCore::instance()->animationManager();
      pqAnimationScene* scene = mgr->getActiveScene();
      BEGIN_UNDO_SET(tr("Remove Animation Track"));
      SM_SCOPED_TRACE(Delete).arg(cue->getProxy());
      // The timeline model will be automatically updated by the cue removal.
      scene->removeCue(cue);
      END_UNDO_SET();
    }
  }

  // Return true if track can be added:
  // - always true for python track
  // - only one camera per view (so only proxy matters)
  // - only one (Proxy, Property) pair in other cases
  bool canTrackBeAdded(const pqAnimatedPropertyInfo& propInfo)
  {
    if (propInfo.Proxy == nullptr)
    {
      return true;
    }

    // look in existing cues
    for (auto cue : this->Cues)
    {
      // skip python
      if (!cue || cue->isPythonCue())
      {
        continue;
      }

      // skip other proxies
      if (cue->getAnimatedProxy() != propInfo.Proxy)
      {
        continue;
      }

      if (cue->isCameraCue() ||
        (cue->getAnimatedPropertyName() == propInfo.Name &&
          cue->getAnimatedPropertyIndex() == propInfo.Index))
      {
        return false;
      }
    }

    return true;
  }

  void validateAnimationCue(const pqAnimatedPropertyInfo& propInfo)
  {
    this->TimelineView->enableTrackCreationWidget(this->canTrackBeAdded(propInfo));
  }

  // add a cue in the scene, and update internal cache of cue list.
  // sanity check to avoid duplicates.
  void createAnimationCue(const pqAnimatedPropertyInfo& propInfo)
  {
    pqAnimationManager* mgr = pqPVApplicationCore::instance()->animationManager();
    pqAnimationScene* scene = mgr->getActiveScene();

    // no proxy means python track.
#if !VTK_MODULE_ENABLE_ParaView_pqPython
    if (!propInfo.Proxy)
    {
      qCritical() << "Python support not enabled. Please recompile ParaView "
                     "with Python enabled.";
      return;
    }
#endif

    if (!this->canTrackBeAdded(propInfo))
    {
      qWarning("Animation track already exists.");
      return;
    }

    BEGIN_UNDO_SET(tr("Add Animation Track"));
    // The timeline model will be automatically updated by the cue creation.
    auto cue = scene->createCue(propInfo);
    SM_SCOPED_TRACE(CreateAnimationTrack).arg("cue", cue->getProxy());
    this->Cues[cue->getSMName()] = cue;
    END_UNDO_SET();

    this->TimelineView->enableTrackCreationWidget(this->canTrackBeAdded(propInfo));
  }

  // show cue editor
  void editAnimationCue(const QString& registrationName)
  {
    if (!this->Cues.contains(registrationName))
    {
      return;
    }

    auto cue = this->Cues[registrationName];
    if (cue)
    {
      pqAnimationManager* mgr = pqPVApplicationCore::instance()->animationManager();
      pqAnimationScene* scene = mgr->getActiveScene();
      auto editor = new pqAnimationTrackEditor(scene, cue);
      editor->showEditor();
    }
  }

  // update lists stored under TIMES and LABELS data role for given cue, based on keyframes info.
  void updateCueKeyframesData(pqAnimationCue* cue, const TreeRow& row)
  {
    QList<vtkSMProxy*> newKeyFrames = cue->getKeyFrames();
    QVariantList newValues;
    QVariantList newTimes;
    for (auto frame : newKeyFrames)
    {
      newTimes << vtkSMPropertyHelper(frame->GetProperty("KeyTime")).GetAsDouble();
      if (!cue->isPythonCue() && !cue->isCameraCue())
      {
        newValues << vtkSMPropertyHelper(frame->GetProperty("KeyValues")).GetAsDouble();
      }
    }

    for (auto item : row)
    {
      item->setData(newTimes, pqTimelineItemRole::TIMES);
      item->setData(newValues, pqTimelineItemRole::LABELS);
    }
  }

  // set scene start and end time based on timekeeper.
  void resetStartEndTime()
  {
    pqAnimationManager* mgr = pqPVApplicationCore::instance()->animationManager();
    auto scene = mgr->getActiveScene();
    if (!scene)
    {
      return;
    }

    auto times = scene->getTimeSteps();

    if (!times.empty())
    {
      auto sceneProxy = scene->getProxy();
      vtkSMPropertyHelper(sceneProxy->GetProperty("StartTime")).Set(times.first());
      vtkSMPropertyHelper(sceneProxy->GetProperty("EndTime")).Set(times.last());
      sceneProxy->UpdateVTKObjects();
    }
  }

  // add a SOURCE row in the model
  TreeRow createSourceRow(vtkSMProxy* proxy)
  {
    QMap<int, QVariant> additionalData;
    additionalData[pqTimelineItemRole::PROXY] = QVariant::fromValue(static_cast<void*>(proxy));
    additionalData[Qt::ToolTipRole] =
      tr("Check/Uncheck to make timesteps available in the scene time list.");

    auto row = this->TimeModel->createRow(
      pqTimelineTrack::SOURCE, ::getRegistrationName(proxy), additionalData);
    this->setTrackTimes(row, proxy);

    return row;
  }

  // return timekeeper for active server.
  vtkSMProxy* activeTimeKeeper()
  {
    auto server = pqActiveObjects::instance().activeServer();
    if (!server)
    {
      return nullptr;
    }

    vtkNew<vtkSMParaViewPipelineController> controller;
    return controller->FindTimeKeeper(server->session());
  }
};

//-----------------------------------------------------------------------------
pqTimelineWidget::pqTimelineWidget(QWidget* parent)
  : Superclass(parent)
  , Internals(new pqTimelineWidget::pqInternals(this))
{
  pqCoreUtilities::connect(vtkSMProxyManager::GetProxyManager(),
    vtkSMProxyManager::ActiveSessionChanged, this, SLOT(onNewSession()));

  pqServerManagerObserver* smobserver = pqApplicationCore::instance()->getServerManagerObserver();

  this->connect(smobserver, SIGNAL(proxyUnRegistered(const QString&, const QString&, vtkSMProxy*)),
    this, SLOT(updateCueCache(const QString&, const QString&, vtkSMProxy*)));

  pqServerManagerModel* smmodel = pqApplicationCore::instance()->getServerManagerModel();
  this->connect(
    smmodel, &pqServerManagerModel::nameChanged, this, &pqTimelineWidget::renameTrackFromProxy);
  this->connect(smmodel, &pqServerManagerModel::dataUpdated, this,
    &pqTimelineWidget::updateSourceTimeFromProxy);

  this->connect(smmodel, &pqServerManagerModel::sourceRemoved, this,
    &pqTimelineWidget::updateSceneFromEnabledSources);
  this->connect(
    smmodel, &pqServerManagerModel::aboutToRemoveServer, this, &pqTimelineWidget::onNewSession);

  this->connect(this->Internals->TimeModel, &pqTimelineModel::sourceStateChanged, this,
    &pqTimelineWidget::updateSceneFromEnabledSources);
  this->connect(this->Internals->TimeModel, &pqTimelineModel::animationStateChanged, this,
    &pqTimelineWidget::updateSceneFromEnabledAnimations);

  pqAnimationManager* mgr = pqPVApplicationCore::instance()->animationManager();
  this->connect(mgr, &pqAnimationManager::activeSceneChanged, [&](pqAnimationScene* scene) {
    if (scene)
    {
      this->connect(scene, &pqAnimationScene::playModeChanged, this,
        &pqTimelineWidget::updateTimeTrackFromScene);
      this->connect(scene, &pqAnimationScene::clockTimeRangesChanged, this,
        &pqTimelineWidget::updateTimeTrackFromScene);
      this->connect(scene, &pqAnimationScene::frameCountChanged, this,
        &pqTimelineWidget::updateTimeTrackFromScene);
      this->connect(scene, &pqAnimationScene::addedCue, this, &pqTimelineWidget::addCueTrack);
      this->connect(
        scene, &pqAnimationScene::preRemovedCue, this, &pqTimelineWidget::removeCueTrack);

      // add already existing cues, as TimeKeeper
      for (auto cue : scene->getCues())
      {
        this->addCueTrack(cue);
      }

      this->updateTimeTrackFromScene();
    }
  });
}

//-----------------------------------------------------------------------------
pqTimelineWidget::~pqTimelineWidget() = default;

//-----------------------------------------------------------------------------
void pqTimelineWidget::setAdvancedMode(bool advanced)
{
  if (this->Internals->TimeKeeperRow.isEmpty())
  {
    return;
  }

  QModelIndex keeperFirstIndex =
    this->Internals->TimeModel->indexFromItem(this->Internals->TimeKeeperRow.first());
  this->Internals->TimelineView->setRowHidden(
    keeperFirstIndex.row(), keeperFirstIndex.parent(), !advanced);
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::updateTimeTrackFromScene()
{
  pqAnimationManager* mgr = pqPVApplicationCore::instance()->animationManager();
  auto scene = mgr->getActiveScene();
  if (!scene)
  {
    return;
  }

  auto sceneProxy = scene->getProxy();

  // update source time rows
  auto playMode = vtkSMPropertyHelper(sceneProxy->GetProperty("PlayMode")).GetAsInt();
  bool snapToTimestep = playMode == vtkCompositeAnimationPlayer::SNAP_TO_TIMESTEPS;
  auto nbOfFrames = vtkSMPropertyHelper(sceneProxy->GetProperty("NumberOfFrames")).GetAsInt();

  auto timeRow = this->Internals->TimeModel->rows(pqTimelineTrack::TIME).first();
  if (!snapToTimestep && nbOfFrames > 1)
  {
    auto start = scene->getClockTimeRange().first;
    auto end = scene->getClockTimeRange().second;
    QVariantList times;
    for (int i = 0; i < nbOfFrames; i++)
    {
      times << start + i * (end - start) / (nbOfFrames - 1);
    }

    timeRow[pqTimelineColumn::TIMELINE]->setData(times, pqTimelineItemRole::TIMES);
  }
  else if (snapToTimestep)
  {
    this->Internals->setTrackTimes(timeRow, this->Internals->activeTimeKeeper());
  }

  if (!this->Internals->IsUpdatingSceneFromUIFlag)
  {
    this->Internals->TimeModel->setRowEnabled(pqTimelineTrack::TIME, snapToTimestep);
  }
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::onNewSession()
{
  this->Internals->TimekeeperConnector->Disconnect();

  this->Internals->Cues.clear();
  this->Internals->TimeModel->clearRows(pqTimelineTrack::ANIMATION);

  vtkSMProxy* timeKeeper = this->Internals->activeTimeKeeper();
  if (timeKeeper)
  {
    this->Internals->TimekeeperConnector->Connect(timeKeeper->GetProperty("TimeSources"),
      vtkCommand::UncheckedPropertyModifiedEvent, this, SLOT(recreateTimeSources()));
    this->Internals->TimekeeperConnector->Connect(timeKeeper->GetProperty("TimestepValues"),
      vtkCommand::UncheckedPropertyModifiedEvent, this, SLOT(recreateTimeSources()));
    this->Internals->TimekeeperConnector->Connect(timeKeeper->GetProperty("SuppressedTimeSources"),
      vtkCommand::UncheckedPropertyModifiedEvent, this, SLOT(recreateTimeSources()));
    this->recreateTimeSources();
  }
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::recreateTimeSources()
{
  vtkSMProxy* timeKeeper = this->Internals->activeTimeKeeper();

  auto timeRow = this->Internals->TimeModel->rows(pqTimelineTrack::TIME).first();
  for (auto item : timeRow)
  {
    item->setData(QVariant::fromValue(static_cast<void*>(timeKeeper)), pqTimelineItemRole::PROXY);
  }
  this->updateTimeTrackFromScene();

  // if we come here because of a UI modification, do not re-update the ui.
  if (this->Internals->IsUpdatingSceneFromUIFlag)
  {
    return;
  }

  this->Internals->TimeModel->clearRows(pqTimelineTrack::SOURCE);
  auto sourcesProp = vtkSMProxyProperty::SafeDownCast(timeKeeper->GetProperty("TimeSources"));
  int nbOfTimeSources = sourcesProp->GetNumberOfProxies();
  for (int idx = 0; idx < nbOfTimeSources; idx++)
  {
    auto sourceProxy = sourcesProp->GetProxy(idx);
    if (sourceProxy &&
      (sourceProxy->GetProperty("TimestepValues") || sourceProxy->GetProperty("TimeRange")))
    {
      auto row = this->Internals->createSourceRow(sourceProxy);
    }
  }

  auto uncheckedSources =
    vtkSMProxyProperty::SafeDownCast(timeKeeper->GetProperty("SuppressedTimeSources"));
  nbOfTimeSources = uncheckedSources->GetNumberOfProxies();
  for (int idx = 0; idx < nbOfTimeSources; idx++)
  {
    auto sourceProxy = uncheckedSources->GetProxy(idx);
    if (sourceProxy)
    {
      this->Internals->TimeModel->setRowEnabled(
        pqTimelineTrack::SOURCE, false, ::getRegistrationName(sourceProxy));
    }
  }
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::updateSceneFromEnabledSources()
{
  pqAnimationManager* mgr = pqPVApplicationCore::instance()->animationManager();
  pqAnimationScene* scene = mgr->getActiveScene();
  if (!scene)
  {
    return;
  }

  auto timeRange = scene->getClockTimeRange();

  this->Internals->IsUpdatingSceneFromUIFlag = true;
  // update timekeeper
  vtkSMProxy* timeKeeper = this->Internals->activeTimeKeeper();
  auto uncheckedSourcesProp =
    vtkSMProxyProperty::SafeDownCast(timeKeeper->GetProperty("SuppressedTimeSources"));

  auto rows = this->Internals->TimeModel->uncheckedRows(pqTimelineTrack::SOURCE);

  uncheckedSourcesProp->RemoveAllProxies();
  std::vector<vtkSMProxy*> proxies;
  for (const auto& row : rows)
  {
    QVariant proxyVariant = row[pqTimelineColumn::NAME]->data(pqTimelineItemRole::PROXY);
    vtkSMProxy* proxy = reinterpret_cast<vtkSMProxy*>(proxyVariant.value<void*>());
    proxies.push_back(proxy);
  }
  uncheckedSourcesProp->SetProxies(static_cast<unsigned int>(proxies.size()), proxies.data());
  timeKeeper->UpdateVTKObjects();

  // update scene
  vtkSMProxy* sceneProxy = scene->getProxy();

  vtkSMPropertyHelper playModeHelper(sceneProxy->GetProperty("PlayMode"));
  int prevMode = playModeHelper.GetAsInt();
  bool snapToTimeStep = this->Internals->TimeModel->isRowEnabled(pqTimelineTrack::TIME);
  int newMode = snapToTimeStep ? vtkCompositeAnimationPlayer::SNAP_TO_TIMESTEPS
                               : vtkCompositeAnimationPlayer::SEQUENCE;

  // keep end and start times when mode changes to Sequence
  if (!snapToTimeStep && prevMode != newMode)
  {
    vtkSMPropertyHelper(sceneProxy->GetProperty("StartTime")).Set(timeRange.first);
    vtkSMPropertyHelper(sceneProxy->GetProperty("EndTime")).Set(timeRange.second);
  }

  playModeHelper.Set(newMode);

  sceneProxy->UpdateVTKObjects();

  this->Internals->IsUpdatingSceneFromUIFlag = false;
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::updateSceneFromEnabledAnimations()
{
  auto tracks = this->Internals->TimeModel->rows(pqTimelineTrack::ANIMATION);
  for (const auto& track : tracks)
  {
    QString name =
      track[pqTimelineColumn::NAME]->data(pqTimelineItemRole::REGISTRATION_NAME).toString();
    bool enable = this->Internals->TimeModel->isRowEnabled(pqTimelineTrack::ANIMATION, name);
    if (this->Internals->Cues.contains(name))
    {
      this->Internals->Cues[name]->setEnabled(enable);
    }
  }
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::renameTrackFromProxy(pqServerManagerModelItem* item)
{
  auto pqproxy = dynamic_cast<pqProxy*>(item);
  if (pqproxy)
  {
    for (auto row : this->Internals->TimeModel->rows(pqTimelineTrack::SOURCE))
    {
      auto proxyVariant = row[pqTimelineColumn::NAME]->data(pqTimelineItemRole::PROXY);
      vtkSMProxy* proxy = reinterpret_cast<vtkSMProxy*>(proxyVariant.value<void*>());
      if (proxy == pqproxy->getProxy())
      {
        row[pqTimelineColumn::NAME]->setData(pqproxy->getSMName(), Qt::DisplayRole);
        for (auto rowItem : row)
        {
          rowItem->setData(pqproxy->getSMName(), pqTimelineItemRole::REGISTRATION_NAME);
        }
        return;
      }
    }
  }
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::updateSourceTimeFromProxy(pqServerManagerModelItem* item)
{
  pqProxy* pqproxy = qobject_cast<pqProxy*>(item);
  if (pqproxy)
  {
    for (auto row : this->Internals->TimeModel->rows(pqTimelineTrack::SOURCE))
    {
      auto proxyVariant = row[pqTimelineColumn::NAME]->data(pqTimelineItemRole::PROXY);
      vtkSMProxy* proxy = reinterpret_cast<vtkSMProxy*>(proxyVariant.value<void*>());
      if (proxy == pqproxy->getProxy())
      {
        vtkPVDataInformation* dinfo = vtkSMSourceProxy::SafeDownCast(proxy)->GetDataInformation(0);
        row[pqTimelineColumn::TIMELINE]->setData(dinfo->GetTime(), pqTimelineItemRole::SOURCE_TIME);
      }
    }
  }
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::addCueTrack(pqAnimationCue* cue)
{
  QMap<int, QVariant> additionalData;
  additionalData[pqTimelineItemRole::PROXY] =
    QVariant::fromValue(static_cast<void*>(cue->getProxy()));
  additionalData[pqTimelineItemRole::REGISTRATION_NAME] = cue->getSMName();
  additionalData[Qt::ToolTipRole] =
    tr("Check / uncheck to enable the animation. Double click on name or timeline to edit.");

  auto newRow = this->Internals->TimeModel->createRow(
    pqTimelineTrack::ANIMATION, cue->getDisplayName(), additionalData);
  if (cue->isTimekeeperCue())
  {
    this->Internals->TimeKeeperRow = newRow;
    QString toolTip = tr("Timekeeper updates pipeline. It maps scene time to a requested pipeline "
                         "time.\n Uncheck to freeze pipeline time.");
    newRow[pqTimelineColumn::NAME]->setData(toolTip, Qt::ToolTipRole);
    newRow[pqTimelineColumn::TIMELINE]->setData(toolTip, Qt::ToolTipRole);
  }

  this->Internals->Cues[cue->getSMName()] = cue;
  this->Internals->updateCueKeyframesData(cue, newRow);

  this->connect(
    cue, &pqAnimationCue::keyframesModified, this, &pqTimelineWidget::updateCueTracksData);
  this->connect(cue, &pqAnimationCue::enabled, this, &pqTimelineWidget::updateCueTracksState);
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::removeCueTrack(pqAnimationCue* cue)
{
  this->Internals->Cues.remove(cue->getSMName());
  this->Internals->TimeModel->deleteRow(pqTimelineTrack::ANIMATION, cue->getSMName());

  this->Internals->TimelineView->validateTrackCreationWidget();
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::updateCueTracksState()
{
  auto animationRows = this->Internals->TimeModel->rows(pqTimelineTrack::ANIMATION);
  for (auto row : animationRows)
  {
    QString name =
      row[pqTimelineColumn::NAME]->data(pqTimelineItemRole::REGISTRATION_NAME).toString();
    auto cue = this->Internals->Cues[name];
    if (!cue)
    {
      continue;
    }

    bool enabled = cue->isEnabled();
    this->Internals->TimeModel->setRowEnabled(pqTimelineTrack::ANIMATION, enabled, name);
  }
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::updateCueTracksData()
{
  auto animationRows = this->Internals->TimeModel->rows(pqTimelineTrack::ANIMATION);
  for (auto row : animationRows)
  {
    QString name =
      row[pqTimelineColumn::NAME]->data(pqTimelineItemRole::REGISTRATION_NAME).toString();
    auto cue = this->Internals->Cues[name];
    if (!cue)
    {
      continue;
    }

    this->Internals->updateCueKeyframesData(cue, row);
  }
}

//-----------------------------------------------------------------------------
void pqTimelineWidget::updateCueCache(const QString& group, const QString& name, vtkSMProxy* proxy)
{
  Q_UNUSED(group);
  Q_UNUSED(proxy);
  this->Internals->Cues.remove(name);
}
