// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-FileCopyrightText: Copyright (c) Sandia Corporation
// SPDX-License-Identifier: BSD-3-Clause
#include "pqPipelineModel.h"

#include "pqBoxChartView.h"
#include "pqExtractor.h"
#include "pqLiveInsituManager.h"
#include "pqLiveInsituVisualizationManager.h"
#include "pqOutputPort.h"
#include "pqPipelineFilter.h"
#include "pqPipelineSource.h"
#include "pqPlotMatrixView.h"
#include "pqRenderView.h"
#include "pqServer.h"
#include "pqServerConfiguration.h"
#include "pqServerManagerModel.h"
#include "pqServerManagerObserver.h"
#include "pqSpreadSheetView.h"
#include "pqUndoStack.h"
#include "pqXYBarChartView.h"
#include "pqXYChartView.h"
#include "pqXYHistogramChartView.h"
#include "vtkCommand.h"
#include "vtkNew.h"
#include "vtkPVXMLElement.h"
#include "vtkSMLiveInsituLinkProxy.h"
#include "vtkSMParaViewPipelineControllerWithRendering.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMViewProxy.h"

#include <QApplication>
#include <QFont>
#include <QString>
#include <QStyle>
#include <QtDebug>

#include <cassert>

class ModifiedLiveInsituLink : public vtkCommand
{
public:
  ModifiedLiveInsituLink(pqServer* insituSession, pqPipelineModel* pipelineModel)
    : InsituServer(insituSession)
    , PipelineModel(pipelineModel)
  {
  }
  void Execute(vtkObject* vtkNotUsed(caller), unsigned long, void* vtkNotUsed(data)) override
  {
    this->PipelineModel->updateData(this->InsituServer);
  }

private:
  pqServer* InsituServer;
  pqPipelineModel* PipelineModel;
};

namespace PipelineModelIconType
{
static const QString SERVER = "SERVER";
static const QString SECURE_SERVER = "SECURE_SERVER";
static const QString LINK = "LINK";
static const QString GEOMETRY = "GEOMETRY";
static const QString BARCHART = pqXYBarChartView::XYBarChartViewType();
static const QString BOXCHART = pqBoxChartView::chartViewType();
static const QString HISTOGRAMCHART = pqXYHistogramChartView::XYHistogramChartViewType();
static const QString LINECHART = pqXYChartView::XYChartViewType();
static const QString PLOTMATRIX = pqPlotMatrixView::viewType();
static const QString TABLE = pqSpreadSheetView::spreadsheetViewType();
static const QString RENDER_VIEW = pqRenderView::renderViewType();
static const QString INDETERMINATE = "INDETERMINATE";
static const QString NONE = "None";
static const QString EYEBALL = "EYEBALL";
static const QString EYEBALL_GRAY = "EYEBALL_GRAY";
static const QString INSITU_EXTRACT = "INSITU_EXTRACT";
static const QString INSITU_EXTRACT_GRAY = "INSITU_EXTRACT_GRAY";
static const QString INSITU_SERVER_RUNNING = "INSITU_SERVER_RUNNING";
static const QString INSITU_SERVER_PAUSED = "INSITU_SERVER_PAUSED";
static const QString INSITU_BREAKPOINT = "INSITU_BREAKPOINT";
static const QString DATA_EXTRACTOR = "DATA_EXTRACTOR";
static const QString IMAGE_EXTRACTOR = "IMAGE_EXTRACTOR";
static const QString CINEMA_MARK = "CINEMA_MARK";
static const QString LAST = "LAST";
}

//-----------------------------------------------------------------------------
class pqPipelineModelDataItem : public QObject
{
  bool InConstructor;

public:
  static vtkNew<vtkSMParaViewPipelineControllerWithRendering> Controller;

  pqPipelineModel* Model;
  pqPipelineModelDataItem* Parent;
  QList<pqPipelineModelDataItem*> Children;
  pqServerManagerModelItem* Object;
  pqPipelineModel::ItemType Type;
  QString VisibilityIcon;
  bool Selectable;

  // This is a terrible iVar, agreed. But it makes my life easier.
  // This is valid only for elements of Type==Proxy. These refer to the link
  // items present for this item, if any. This list is automatically kept
  // updated.
  QList<pqPipelineModelDataItem*> Links;

  pqPipelineModelDataItem(QObject* p, pqServerManagerModelItem* object,
    pqPipelineModel::ItemType itemType, pqPipelineModel* model)
    : QObject(p)
  {
    this->InConstructor = true;
    this->Selectable = true;
    this->Model = model;
    this->Parent = nullptr;
    this->Object = object;
    this->Type = itemType;
    this->VisibilityIcon = PipelineModelIconType::LAST;
    if (itemType == pqPipelineModel::Link)
    {
      pqPipelineModelDataItem* proxyItem =
        model->getDataItem(object, nullptr, pqPipelineModel::Proxy);
      assert(proxyItem != 0);
      proxyItem->Links.push_back(this);
    }
    if (this->Object)
    {
      this->updateVisibilityIcon(this->Model->view(), false);
    }
    this->InConstructor = false;
  }
  ~pqPipelineModelDataItem() override
  {
    if (this->Type == pqPipelineModel::Link && this->Model->Internal)
    {
      pqPipelineModelDataItem* proxyItem =
        this->Model->getDataItem(this->Object, nullptr, pqPipelineModel::Proxy);
      if (proxyItem)
      {
        proxyItem->Links.removeAll(this);
      }
    }
  }

  pqPipelineModelDataItem& operator=(const pqPipelineModelDataItem& other)
  {
    this->Object = other.Object;
    this->Type = other.Type;
    this->VisibilityIcon = other.VisibilityIcon;
    Q_FOREACH (pqPipelineModelDataItem* otherChild, other.Children)
    {
      pqPipelineModelDataItem* child =
        new pqPipelineModelDataItem(this, nullptr, pqPipelineModel::Invalid, this->Model);
      this->addChild(child);
      *child = *otherChild;
    }
    return *this;
  }

  // no need to call this generally. Only needed when operator = is used.
  void updateLinks()
  {
    if (this->Type == pqPipelineModel::Link)
    {
      pqPipelineModelDataItem* proxyItem =
        this->Model->getDataItem(this->Object, nullptr, pqPipelineModel::Proxy);
      assert(proxyItem != 0);
      proxyItem->Links.push_back(this);
    }
    Q_FOREACH (pqPipelineModelDataItem* child, this->Children)
    {
      child->updateLinks();
    }
  }

  pqPipelineModel::ItemType getType() { return this->Type; }
  int getIndexInParent()
  {
    if (!this->Parent)
    {
      return 0;
    }
    return this->Parent->Children.indexOf(this);
  }

  QString getIconType() const
  {
    switch (this->Type)
    {
      case pqPipelineModel::Server:
      {
        pqServer* server = qobject_cast<pqServer*>(this->Object);
        if (server->getResource().configuration().isPortForwarding())
        {
          return PipelineModelIconType::SECURE_SERVER;
        }

        vtkSMLiveInsituLinkProxy* proxy = pqLiveInsituManager::linkProxy(server);
        return proxy ? ((vtkSMPropertyHelper(proxy, "SimulationPaused").GetAs<int>() == 1)
                           ? PipelineModelIconType::INSITU_SERVER_PAUSED
                           : PipelineModelIconType::INSITU_SERVER_RUNNING)
                     : PipelineModelIconType::SERVER;
      }

      case pqPipelineModel::Proxy:
      {
        pqPipelineSource* source = qobject_cast<pqPipelineSource*>(this->Object);
        if (source->getNumberOfOutputPorts() > 1)
        {
          // icon is shown on the output ports.
          return PipelineModelIconType::INDETERMINATE;
        }
        return this->getIconType(source->getOutputPort(0));
      }

      case pqPipelineModel::Port:
      {
        pqOutputPort* port = qobject_cast<pqOutputPort*>(this->Object);
        return this->getIconType(port);
      }

      case pqPipelineModel::Link:
        return PipelineModelIconType::LINK;

      case pqPipelineModel::Extractor:
        // TODO: respect `PipelineIcon` hint, if available.
        if (auto eg = qobject_cast<pqExtractor*>(this->Object))
        {
          return eg->isImageExtractor() ? PipelineModelIconType::IMAGE_EXTRACTOR
                                        : PipelineModelIconType::DATA_EXTRACTOR;
        }
        return PipelineModelIconType::DATA_EXTRACTOR;

      case pqPipelineModel::Invalid:
        return PipelineModelIconType::INDETERMINATE;
    }
    return PipelineModelIconType::INDETERMINATE;
  }

  void addChild(pqPipelineModelDataItem* child)
  {
    if (child->Parent)
    {
      qCritical() << "child has parent.";
      return;
    }
    child->setParent(this);
    child->Parent = this;
    this->Children.push_back(child);
  }

  void removeChild(pqPipelineModelDataItem* child)
  {
    if (child->Parent != this)
    {
      qCritical() << "Cannot remove a non-child.";
      return;
    }
    child->setParent(nullptr);
    child->Parent = nullptr;
    this->Children.removeAll(child);
  }

  // returns true when the icon has changed.
  bool updateVisibilityIcon(pqView* view, bool traverse_subtree)
  {
    QString newIcon = PipelineModelIconType::LAST;
    switch (this->Type)
    {
      case pqPipelineModel::Proxy:
      case pqPipelineModel::Link:
      {
        pqPipelineSource* source = qobject_cast<pqPipelineSource*>(this->Object);
        if (source && source->getNumberOfOutputPorts() == 1)
        {
          newIcon = this->getVisibilityIcon(source->getOutputPort(0), view);
        }
      }
      break;

      case pqPipelineModel::Port:
      {
        pqOutputPort* port = qobject_cast<pqOutputPort*>(this->Object);
        newIcon = this->getVisibilityIcon(port, view);
      }
      break;

      case pqPipelineModel::Server:
      {
        pqServer* server = qobject_cast<pqServer*>(this->Object);
        newIcon = (pqLiveInsituManager::isInsituServer(server) ? this->getBreakpointVisibilityIcon()
                                                               : PipelineModelIconType::LAST);
      }
      break;

      case pqPipelineModel::Extractor:
        if (auto extractor = qobject_cast<pqExtractor*>(this->Object))
        {
          newIcon = this->getVisibilityIcon(extractor, view);
        }
        break;

      default:
        break;
    }

    bool ret_val = false;
    if (this->VisibilityIcon != newIcon)
    {
      this->VisibilityIcon = newIcon;
      if (!this->InConstructor && this->Model)
      {
        this->Model->itemDataChanged(this);
      }
      ret_val = true;
    }
    if (traverse_subtree)
    {
      Q_FOREACH (pqPipelineModelDataItem* child, this->Children)
      {
        child->updateVisibilityIcon(view, traverse_subtree);
      }
    }
    return ret_val;
  }

  bool isModified() const
  {
    pqProxy* proxy = qobject_cast<pqProxy*>(this->Object);
    return (proxy && (proxy->modifiedState() != pqProxy::UNMODIFIED));
  }

private:
  QString getBreakpointVisibilityIcon() const
  {
    pqLiveInsituManager* server = pqLiveInsituManager::instance();
    return (server->hasBreakpoint() ? PipelineModelIconType::INSITU_BREAKPOINT
                                    : PipelineModelIconType::LAST);
  }

  QString getVisibilityIcon(pqOutputPort* port, pqView* view) const
  {
    if (!port)
    {
      return PipelineModelIconType::LAST;
    }

    // I'm not a huge fan of this hacky way we are dealing with catalyst. We'll
    // have to come back and cleanly address how the pqPipelineModel deals with
    // "visibility" in a more generic, session-centric way.
    if (pqLiveInsituManager::isInsituServer(port->getServer()))
    {
      if (pqLiveInsituManager::isWriterParametersProxy(port->getSourceProxy()))
      {
        return PipelineModelIconType::LAST;
      }
      pqLiveInsituVisualizationManager* mgr =
        pqLiveInsituManager::managerFromInsitu(port->getServer());
      if (mgr)
      {
        return mgr->hasExtracts(port) ? PipelineModelIconType::INSITU_EXTRACT
                                      : PipelineModelIconType::INSITU_EXTRACT_GRAY;
      }
      return PipelineModelIconType::LAST;
    }

    if (!view)
    {
      // If no view is present, it implies that a suitable type of view
      // will be created.
      return PipelineModelIconType::EYEBALL_GRAY;
    }
    if (this->Controller->GetVisibility(
          port->getSourceProxy(), port->getPortNumber(), view->getViewProxy()))
    {
      return PipelineModelIconType::EYEBALL;
    }
    return view->getViewProxy()->CanDisplayData(port->getSourceProxy(), port->getPortNumber())
      ? PipelineModelIconType::EYEBALL_GRAY
      : PipelineModelIconType::LAST;
  }

  QString getVisibilityIcon(pqExtractor* extractor, pqView* view) const
  {
    if (!extractor)
    {
      return PipelineModelIconType::LAST;
    }
    auto producer = extractor->producer();
    if (extractor->isImageExtractor() && view != producer)
    {
      // show no-icon.
      return PipelineModelIconType::LAST;
    }

    // show icon based on enabled state.
    return extractor->isEnabled() ? PipelineModelIconType::EYEBALL
                                  : PipelineModelIconType::EYEBALL_GRAY;
  }

  QString getIconType(pqOutputPort* port) const
  {
    if (port->getSource()->property("INSITU_EXTRACT").toBool())
    {
      return PipelineModelIconType::INSITU_EXTRACT;
    }
    else if (port->getSourceProxy()->HasAnnotation("CINEMA"))
    {
      return PipelineModelIconType::CINEMA_MARK;
    }
    else if (pqLiveInsituManager::isWriterParametersProxy(port->getSourceProxy()))
    {
      return PipelineModelIconType::DATA_EXTRACTOR;
    }

    QString iconType =
      this->Controller->GetPipelineIcon(port->getSourceProxy(), port->getPortNumber());
    if (!iconType.isEmpty())
    {
      return iconType;
    }

    return PipelineModelIconType::GEOMETRY;
  }
};

vtkNew<vtkSMParaViewPipelineControllerWithRendering> pqPipelineModelDataItem::Controller;

//-----------------------------------------------------------------------------
class pqPipelineModelInternal
{
public:
  pqPipelineModelInternal(pqPipelineModel* parent)
    : Root(parent, nullptr, pqPipelineModel::Invalid, parent)
  {
    this->ModifiedFont.setBold(true);
    this->DelayedUpdateVisibilityTimer.setSingleShot(true);
  }

  QFont ModifiedFont;
  pqPipelineModelDataItem Root;
  pqTimer DelayedUpdateVisibilityTimer;
  QList<QPointer<pqPipelineSource>> DelayedUpdateVisibilityItems;
};

//-----------------------------------------------------------------------------
void pqPipelineModel::constructor()
{
  this->FilterRoleSession = nullptr;
  this->Internal = new pqPipelineModelInternal(this);
  QObject::connect(&this->Internal->DelayedUpdateVisibilityTimer, SIGNAL(timeout()), this,
    SLOT(delayedUpdateVisibilityTimeout()));

  this->Editable = true;
  this->View = nullptr;

  QObject::connect(pqLiveInsituManager::instance(), SIGNAL(connectionInitiated(pqServer*)), this,
    SLOT(onInsituConnectionInitiated(pqServer*)));

  this->PixmapMap[PipelineModelIconType::SERVER].load(":/pqWidgets/Icons/pqServer16.png");
  this->PixmapMap[PipelineModelIconType::SECURE_SERVER].load(
    ":/pqWidgets/Icons/pqSecureServer16.png");
  this->PixmapMap[PipelineModelIconType::LINK].load(":/pqWidgets/Icons/pqLinkBack16.png");
  this->PixmapMap[PipelineModelIconType::GEOMETRY].load(":/pqWidgets/Icons/pq3DView16.png");
  this->PixmapMap[PipelineModelIconType::RENDER_VIEW].load(":/pqWidgets/Icons/pq3DView16.png");
  this->PixmapMap[PipelineModelIconType::BARCHART].load(":/pqWidgets/Icons/pqHistogram16.png");
  this->PixmapMap[PipelineModelIconType::BOXCHART].load(":/pqWidgets/Icons/pqBoxChart16.png");
  this->PixmapMap[PipelineModelIconType::HISTOGRAMCHART].load(
    ":/pqWidgets/Icons/pqHistogram16.png");
  this->PixmapMap[PipelineModelIconType::LINECHART].load(":/pqWidgets/Icons/pqLineChart16.png");
  this->PixmapMap[PipelineModelIconType::PLOTMATRIX].load(":/pqWidgets/Icons/pqLineChart16.png");
  this->PixmapMap[PipelineModelIconType::TABLE].load(":/pqWidgets/Icons/pqSpreadsheet.svg");
  this->PixmapMap[PipelineModelIconType::INDETERMINATE].load(":/pqWidgets/Icons/pq3DView16.png");
  this->PixmapMap[PipelineModelIconType::NONE].load(":/pqWidgets/Icons/pq3DView16.png");
  this->PixmapMap[PipelineModelIconType::EYEBALL].load(":/pqWidgets/Icons/pqEyeball.svg");
  this->PixmapMap[PipelineModelIconType::EYEBALL_GRAY].load(
    ":/pqWidgets/Icons/pqEyeballClosed.svg");
  this->PixmapMap[PipelineModelIconType::INSITU_EXTRACT].load(":/pqWidgets/Icons/pqLinkIn16.png");
  this->PixmapMap[PipelineModelIconType::INSITU_EXTRACT_GRAY].load(
    ":/pqWidgets/Icons/pqLinkIn16d.png");
  this->PixmapMap[PipelineModelIconType::INSITU_SERVER_RUNNING].load(
    ":/pqWidgets/Icons/pqInsituServerRunning16.png");
  this->PixmapMap[PipelineModelIconType::INSITU_SERVER_PAUSED].load(
    ":/pqWidgets/Icons/pqInsituServerPaused16.png");
  this->PixmapMap[PipelineModelIconType::INSITU_BREAKPOINT].load(
    ":/pqWidgets/Icons/pqInsituBreakpoint16.png");
  this->PixmapMap[PipelineModelIconType::DATA_EXTRACTOR].load(":/pqWidgets/Icons/pqSave.svg");
  this->PixmapMap[PipelineModelIconType::IMAGE_EXTRACTOR].load(
    ":/pqWidgets/Icons/pqCaptureScreenshot.svg");
  this->PixmapMap[PipelineModelIconType::CINEMA_MARK].load(":/pqWidgets/Icons/pqCinemaScience.svg");
}

//-----------------------------------------------------------------------------
pqPipelineModel::pqPipelineModel(QObject* p)
  : QAbstractItemModel(p)
  , FilterAnnotationMatching(true)
  , LinkCallback(nullptr)
{
  this->constructor();
}

//-----------------------------------------------------------------------------
pqPipelineModel::pqPipelineModel(const pqPipelineModel& other, QObject* parentObject)
  : QAbstractItemModel(parentObject)
  , FilterAnnotationMatching(true)
  , LinkCallback(nullptr)
{
  this->constructor();
  this->Internal->Root = other.Internal->Root;
  this->Internal->Root.updateLinks();
}

//-----------------------------------------------------------------------------
pqPipelineModel::pqPipelineModel(const pqServerManagerModel& other, QObject* parentObject)
  : QAbstractItemModel(parentObject)
  , FilterAnnotationMatching(true)
  , LinkCallback(nullptr)
{
  this->constructor();

  // Build a pipeline model from the current server manager model.
  QList<pqPipelineSource*> sources;
  QList<pqPipelineSource*>::Iterator source;
  QList<pqExtractor*> extractors;
  QList<pqExtractor*>::Iterator extractor;
  QList<pqServer*> servers = other.findItems<pqServer*>();
  QList<pqServer*>::Iterator server = servers.begin();
  for (; server != servers.end(); ++server)
  {
    // Add the server to the model.
    this->addServer(*server);

    // Add the sources for the server.
    sources = other.findItems<pqPipelineSource*>(*server);
    for (source = sources.begin(); source != sources.end(); ++source)
    {
      this->addSource(*source);
    }

    // Set up the pipeline connections.
    for (source = sources.begin(); source != sources.end(); ++source)
    {
      int numOutputPorts = (*source)->getNumberOfOutputPorts();
      for (int port = 0; port < numOutputPorts; port++)
      {
        int numConsumers = (*source)->getNumberOfConsumers(port);
        for (int i = 0; i < numConsumers; ++i)
        {
          this->addConnection(*source, (*source)->getConsumer(port, i), port);
        }
      }
    }

    // Set up extractors
    extractors = other.findItems<pqExtractor*>(*server);
    for (extractor = extractors.begin(); extractor != extractors.end(); ++extractor)
    {
      this->addExtractor(*extractor);
      auto port = qobject_cast<pqOutputPort*>((*extractor)->producer());
      if (port && (*extractor)->isDataExtractor())
      {
        this->addConnection(port->getSource(), *extractor);
      }
    }
  }
}

//-----------------------------------------------------------------------------
pqPipelineModel::~pqPipelineModel()
{
  // setting this->Internal to nullptr keeps the ~pqPipelineModelDataItem() from
  // trying to update the link connections.
  pqPipelineModelInternal* internal = this->Internal;
  this->Internal = nullptr;
  delete internal;

  if (this->LinkCallback)
  {
    this->LinkCallback->Delete();
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::onInsituConnectionInitiated(pqServer* displaySession)
{
  pqLiveInsituVisualizationManager* manager =
    pqLiveInsituManager::instance()->managerFromDisplay(displaySession);
  vtkSMLiveInsituLinkProxy* proxy = pqLiveInsituManager::linkProxy(manager->insituSession());
  if (manager && proxy)
  {
    if (this->LinkCallback)
    {
      this->LinkCallback->Delete();
    }
    this->LinkCallback = new ModifiedLiveInsituLink(manager->insituSession(), this);
    proxy->AddObserver(vtkCommand::ModifiedEvent, this->LinkCallback);
    QObject::connect(pqLiveInsituManager::instance(), SIGNAL(breakpointAdded(pqServer*)), this,
      SLOT(updateDataServer(pqServer*)));
    QObject::connect(pqLiveInsituManager::instance(), SIGNAL(breakpointRemoved(pqServer*)), this,
      SLOT(updateDataServer(pqServer*)));
  }
}

//-----------------------------------------------------------------------------
pqPipelineModel::ItemType pqPipelineModel::getTypeFor(const QModelIndex& idx) const
{
  if (idx.isValid() && idx.model() == this)
  {
    pqPipelineModelDataItem* item =
      reinterpret_cast<pqPipelineModelDataItem*>(idx.internalPointer());
    return item->getType();
  }

  return pqPipelineModel::Invalid;
}

//-----------------------------------------------------------------------------
void pqPipelineModel::setSelectable(const QModelIndex& idx, bool selectable)
{
  if (idx.isValid() && idx.model() == this)
  {
    pqPipelineModelDataItem* item =
      reinterpret_cast<pqPipelineModelDataItem*>(idx.internalPointer());
    item->Selectable = selectable;
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::setSubtreeSelectable(pqServerManagerModelItem* smitem, bool selectable)
{
  pqOutputPort* port = qobject_cast<pqOutputPort*>(smitem);
  if (port && port->getSource())
  {
    smitem = port->getSource();
  }

  pqPipelineModelDataItem* item;
  pqServer* server = qobject_cast<pqServer*>(smitem);
  if (server)
  {
    item = this->getDataItem(smitem, &this->Internal->Root, pqPipelineModel::Server);
  }
  else
  {
    item = this->getDataItem(smitem, &this->Internal->Root, pqPipelineModel::Proxy);
  }
  this->setSubtreeSelectable(item, selectable);
}

//-----------------------------------------------------------------------------
void pqPipelineModel::setSubtreeSelectable(pqPipelineModelDataItem* item, bool selectable)
{
  if (item)
  {
    item->Selectable = selectable;
    Q_FOREACH (pqPipelineModelDataItem* link, item->Links)
    {
      link->Selectable = selectable;
    }
    Q_FOREACH (pqPipelineModelDataItem* child, item->Children)
    {
      this->setSubtreeSelectable(child, selectable);
    }
  }
}

//-----------------------------------------------------------------------------
bool pqPipelineModel::isSelectable(const QModelIndex& idx) const
{
  if (idx.isValid() && idx.model() == this)
  {
    pqPipelineModelDataItem* item =
      reinterpret_cast<pqPipelineModelDataItem*>(idx.internalPointer());
    return item->Selectable;
  }

  return false;
}
//-----------------------------------------------------------------------------
QModelIndex pqPipelineModel::getNextIndex(const QModelIndex idx, const QModelIndex& root) const
{
  // If the index has children, return the first child.
  if (this->rowCount(idx) > 0)
  {
    return this->index(0, 0, idx);
  }

  // Search up the parent chain for an index with more children.
  QModelIndex current = idx;
  while (current.isValid() && current != root)
  {
    QModelIndex parentIndex = current.parent();
    if (current.row() < this->rowCount(parentIndex) - 1)
    {
      return this->index(current.row() + 1, 0, parentIndex);
    }

    current = parentIndex;
  }

  return QModelIndex();
}

//-----------------------------------------------------------------------------
int pqPipelineModel::rowCount(const QModelIndex& parentIndex) const
{
  if (parentIndex.isValid() && parentIndex.model() == this)
  {
    pqPipelineModelDataItem* item =
      reinterpret_cast<pqPipelineModelDataItem*>(parentIndex.internalPointer());
    return item->Children.size();
  }
  return this->Internal->Root.Children.size();
}

//-----------------------------------------------------------------------------
int pqPipelineModel::columnCount(const QModelIndex&) const
{
  return 2;
}

//-----------------------------------------------------------------------------
bool pqPipelineModel::hasChildren(const QModelIndex& parentIndex) const
{
  return this->rowCount(parentIndex) > 0;
}

//-----------------------------------------------------------------------------
QModelIndex pqPipelineModel::index(int row, int column, const QModelIndex& parentIndex) const
{
  // Make sure the row and column number is within range.
  int rows = this->rowCount(parentIndex);
  int columns = this->columnCount(parentIndex);
  if (row < 0 || row >= rows || column < 0 || column >= columns)
  {
    return QModelIndex();
  }

  pqPipelineModelDataItem* parentItem = nullptr;
  if (parentIndex.isValid())
  {
    parentItem = reinterpret_cast<pqPipelineModelDataItem*>(parentIndex.internalPointer());
  }
  else
  {
    // The parent refers to the model root.
    parentItem = &this->Internal->Root;
  }

  return this->createIndex(row, column, parentItem->Children[row]);
}

//-----------------------------------------------------------------------------
QModelIndex pqPipelineModel::parent(const QModelIndex& idx) const
{
  if (idx.isValid() && idx.model() == this)
  {
    pqPipelineModelDataItem* item =
      reinterpret_cast<pqPipelineModelDataItem*>(idx.internalPointer());

    pqPipelineModelDataItem* _parent = item->Parent;
    return this->getIndex(_parent);
  }

  return QModelIndex();
}

//-----------------------------------------------------------------------------
QVariant pqPipelineModel::data(const QModelIndex& idx, int role) const
{
  if (!idx.isValid() || idx.model() != this)
  {
    return QVariant();
  }

  pqPipelineModelDataItem* item = reinterpret_cast<pqPipelineModelDataItem*>(idx.internalPointer());

  pqServer* server = qobject_cast<pqServer*>(item->Object);
  pqOutputPort* port = qobject_cast<pqOutputPort*>(item->Object);
  pqProxy* proxy = port ? port->getSource() : qobject_cast<pqProxy*>(item->Object);
  switch (role)
  {
    case Qt::ToolTipRole:
      if (proxy && proxy->getProxy()->HasAnnotation("tooltip"))
      {
        return QVariant(proxy->getProxy()->GetAnnotation("tooltip"));
      }
      [[fallthrough]];
    case Qt::DisplayRole:
      if (idx.column() == 1)
      {
        return QIcon(this->PixmapMap[item->VisibilityIcon]);
      }
      [[fallthrough]];
    // *** don't break.
    case Qt::EditRole:
      if (idx.column() == 0)
      {
        if (server)
        {
          const pqServerResource& resource = server->getResource();
          const bool is_configuration_default = resource.configuration().isNameDefault();
          const auto name = is_configuration_default ? resource.schemeHostsPorts().toURI()
                                                     : resource.configuration().name();
          int time = server->getRemainingLifeTime();
          QString timeLeft =
            time > -1 ? QString(" (%1min left)").arg(QString::number(time)) : QString();
          return is_configuration_default
            ? QString("%1 %3").arg(name).arg(timeLeft)
            : QString("%1 (%2)%3").arg(name).arg(resource.configuration().URI()).arg(timeLeft);
        }
        else if (port)
        {
          return port->getPortName();
        }
        else if (proxy)
        {
          return QVariant(proxy->getSMName());
        }
        else
        {
          qDebug() << "Cannot decide type.";
        }
      }
      break;

    case Qt::ForegroundRole:
    {
      if (idx.column() == 0 && server && server->getRemainingLifeTime() > -1 &&
        server->getRemainingLifeTime() <= 5)
      {
        return QVariant::fromValue<QColor>(QColor(Qt::red));
      }
      break;
    }

    case Qt::DecorationRole:
      if (idx.column() == 0)
      {
        if (item && item->getType() != pqPipelineModel::Invalid)
        {
          return QVariant(this->PixmapMap[item->getIconType()]);
        }
      }
      break;

    case Qt::FontRole:
    {
      if (idx.column() == 0 && item->isModified())
      {
        return QVariant::fromValue<QFont>(this->Internal->ModifiedFont);
      }
      break;
    }
    case pqPipelineModel::AnnotationFilterRole:
    {
      if (!this->FilterRoleAnnotationKey.isEmpty() && proxy)
      {
        bool hasAnnotation =
          proxy->getProxy()->HasAnnotation(this->FilterRoleAnnotationKey.toUtf8().data());
        return (this->FilterAnnotationMatching ? hasAnnotation : !hasAnnotation);
      }
      return QVariant(true);
    }
    case pqPipelineModel::SessionFilterRole:
    {
      if (this->FilterRoleSession && server)
      {
        // We just want to make sure we are pointing to the same session
        return ((void*)server->session() == (void*)this->FilterRoleSession);
      }
      return QVariant(true);
    }
  }

  return QVariant();
}

//-----------------------------------------------------------------------------
bool pqPipelineModel::setData(const QModelIndex& idx, const QVariant& value, int)
{
  const QString name = value.toString();
  if (name.isEmpty())
  {
    return false;
  }
  auto proxy = qobject_cast<pqProxy*>(this->getItemFor(idx));
  if (proxy && proxy->getSMName() != name)
  {
    BEGIN_UNDO_SET(tr("Rename %1 to %2").arg(proxy->getSMName()).arg(name));
    proxy->rename(name);
    END_UNDO_SET();
    return true;
  }

  return false;
}

//-----------------------------------------------------------------------------
Qt::ItemFlags pqPipelineModel::flags(const QModelIndex& idx) const
{
  Qt::ItemFlags _flags = Qt::ItemIsEnabled;

  if (idx.column() == 0)
  {
    pqPipelineModelDataItem* item =
      reinterpret_cast<pqPipelineModelDataItem*>(idx.internalPointer());
    if (item->Selectable)
    {
      _flags |= Qt::ItemIsSelectable;
    }

    if (this->Editable &&
      (item->Type == pqPipelineModel::Proxy || item->Type == pqPipelineModel::Extractor))
    {
      _flags |= Qt::ItemIsEditable;
    }
  }

  return _flags;
}

//-----------------------------------------------------------------------------
pqServerManagerModelItem* pqPipelineModel::getItemFor(const QModelIndex& idx) const
{
  if (idx.isValid() && idx.model() == this)
  {
    pqPipelineModelDataItem* item =
      reinterpret_cast<pqPipelineModelDataItem*>(idx.internalPointer());
    return item->Object;
  }
  return nullptr;
}

//-----------------------------------------------------------------------------
QModelIndex pqPipelineModel::getIndexFor(pqServerManagerModelItem* item) const
{
  pqPipelineModelDataItem* dataItem = this->getDataItem(item, &this->Internal->Root);
  if (!dataItem)
  {
    pqOutputPort* port = qobject_cast<pqOutputPort*>(item);
    if (port && port->getSource()->getNumberOfOutputPorts() == 1)
    {
      return this->getIndexFor(port->getSource());
    }
  }
  return this->getIndex(dataItem);
}

//-----------------------------------------------------------------------------
pqPipelineModelDataItem* pqPipelineModel::getDataItem(
  pqServerManagerModelItem* item, pqPipelineModelDataItem* _parent = nullptr,
  pqPipelineModel::ItemType type /*= pqPipelineModel::Invalid*/
) const
{
  if (_parent == nullptr)
  {
    _parent = &this->Internal->Root;
  }

  if (!item)
  {
    return nullptr;
  }

  if (_parent->Object == item && (type == pqPipelineModel::Invalid || type == _parent->Type))
  {
    return _parent;
  }

  Q_FOREACH (pqPipelineModelDataItem* child, _parent->Children)
  {
    pqPipelineModelDataItem* retVal = this->getDataItem(item, child, type);
    if (retVal && (type == pqPipelineModel::Invalid || type == retVal->Type))
    {
      return retVal;
    }
  }
  return nullptr;
}

//-----------------------------------------------------------------------------
QModelIndex pqPipelineModel::getIndex(pqPipelineModelDataItem* dataItem) const
{
  if (dataItem && dataItem->Parent)
  {
    int rowNo = dataItem->getIndexInParent();
    if (rowNo != -1)
    {
      return this->createIndex(rowNo, 0, dataItem);
    }
  }

  // QModelIndex() implies the ROOT.
  return QModelIndex();
}

//-----------------------------------------------------------------------------
void pqPipelineModel::addChild(pqPipelineModelDataItem* _parent, pqPipelineModelDataItem* child)
{
  if (!_parent || !child)
  {
    qDebug() << "addChild cannot have null arguments.";
    return;
  }

  QModelIndex parentIndex = this->getIndex(_parent);
  int row = _parent->Children.size();

  this->beginInsertRows(parentIndex, row, row);
  _parent->addChild(child);
  this->endInsertRows();

  // Make sure a pipeline icon exists for this item
  if (!this->checkAndLoadPipelinePixmap(child->getIconType()))
  {
    qWarning() << "Could not find icon pixmap for" << child->getIconType();
  }

  if (row == 0)
  {
    Q_EMIT this->firstChildAdded(parentIndex);
  }
  // This is a special case where an added child has children already. This can occur
  // when a change of input is triggered on a source that has children.
  if (!child->Children.empty())
  {
    Q_EMIT this->childWithChildrenAdded(this->getIndex(child));
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::removeChildFromParent(pqPipelineModelDataItem* child)
{
  if (!child)
  {
    qDebug() << "removeChild cannot have null arguments.";
    return;
  }

  pqPipelineModelDataItem* _parent = child->Parent;
  if (!_parent)
  {
    qDebug() << "cannot remove ROOT.";
    return;
  }

  QModelIndex parentIndex = this->getIndex(_parent);
  int row = child->getIndexInParent();

  this->beginRemoveRows(parentIndex, row, row);
  _parent->removeChild(child);
  this->endRemoveRows();
}

//-----------------------------------------------------------------------------
void pqPipelineModel::serverDataChanged()
{
  // TODO: we should determine which server data actually changed
  // and invalidate only that one. FOr now, just invalidate all.

  int max = this->Internal->Root.Children.size() - 1;
  if (max >= 0)
  {
    QModelIndex minIndex = this->getIndex(this->Internal->Root.Children[0]);
    QModelIndex maxIndex = this->getIndex(this->Internal->Root.Children[max]);
    Q_EMIT this->dataChanged(minIndex, maxIndex);
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::itemDataChanged(pqPipelineModelDataItem* item)
{
  QModelIndex idx = this->getIndex(item);
  Q_EMIT this->dataChanged(idx, idx);
}

//-----------------------------------------------------------------------------
void pqPipelineModel::addServer(pqServer* server)
{
  if (!server)
  {
    return;
  }

  pqPipelineModelDataItem* item =
    new pqPipelineModelDataItem(this, server, pqPipelineModel::Server, this);
  this->addChild(&this->Internal->Root, item);
  QObject::connect(server, SIGNAL(nameChanged(pqServerManagerModelItem*)), this,
    SLOT(updateData(pqServerManagerModelItem*)));
}

//-----------------------------------------------------------------------------
void pqPipelineModel::removeServer(pqServer* server)
{
  pqPipelineModelDataItem* item =
    this->getDataItem(server, &this->Internal->Root, pqPipelineModel::Server);

  if (!item)
  {
    qDebug() << "Requesting to remove a non-added server.";
    return;
  }

  this->removeChildFromParent(item);

  delete item;
}

//-----------------------------------------------------------------------------
void pqPipelineModel::addSource(pqPipelineSource* source)
{
  pqServer* server = source->getServer();
  pqPipelineModelDataItem* _parent =
    this->getDataItem(server, &this->Internal->Root, pqPipelineModel::Server);

  if (!_parent)
  {
    qDebug() << "Could not locate server on which the source is being added.";
    return;
  }

  pqPipelineModelDataItem* item =
    new pqPipelineModelDataItem(this, source, pqPipelineModel::Proxy, this);
  item->Object = source;
  item->Type = pqPipelineModel::Proxy; // source->getType();

  // Add the 'source' to the server.
  this->addChild(_parent, item);

  int numOutputPorts = source->getNumberOfOutputPorts();
  if (numOutputPorts > 1)
  {
    // add output-ports for this source.
    for (int cc = 0; cc < numOutputPorts; cc++)
    {
      pqPipelineModelDataItem* opport =
        new pqPipelineModelDataItem(this, source->getOutputPort(cc), pqPipelineModel::Port, this);
      this->addChild(item, opport);
    }
  }

  QObject::connect(source, SIGNAL(visibilityChanged(pqPipelineSource*, pqDataRepresentation*)),
    this, SLOT(delayedUpdateVisibility(pqPipelineSource*)));

  QObject::connect(source, SIGNAL(nameChanged(pqServerManagerModelItem*)), this,
    SLOT(updateData(pqServerManagerModelItem*)));
  QObject::connect(source, SIGNAL(modifiedStateChanged(pqServerManagerModelItem*)), this,
    SLOT(updateData(pqServerManagerModelItem*)));
}

//-----------------------------------------------------------------------------
void pqPipelineModel::removeSource(pqPipelineSource* source)
{
  pqPipelineModelDataItem* item =
    this->getDataItem(source, &this->Internal->Root, pqPipelineModel::Proxy);

  if (!item)
  {
    // qDebug() << "Requesting to remove a non-added source.";
    return;
  }

  while (!item->Links.empty())
  {
    pqPipelineModelDataItem* link = item->Links.last();
    this->removeChildFromParent(link);
    delete link;
  }

  this->removeChildFromParent(item);
  if (!item->Children.empty())
  {
    // Move the children to the server.
    pqServer* server = source->getServer();
    pqPipelineModelDataItem* _parent =
      this->getDataItem(server, &this->Internal->Root, pqPipelineModel::Server);
    if (!_parent)
    {
      _parent = &this->Internal->Root;
    }

    QList<pqPipelineModelDataItem*> childrenToMove;
    Q_FOREACH (pqPipelineModelDataItem* child, item->Children)
    {
      if (child->Type == pqPipelineModel::Port)
      {
        childrenToMove.append(child->Children);
      }
      else
      {
        childrenToMove.push_back(child);
      }
    }

    Q_FOREACH (pqPipelineModelDataItem* child, childrenToMove)
    {
      child->Parent = nullptr;
      this->addChild(_parent, child);
    }
  }

  delete item;
}

//-----------------------------------------------------------------------------
void pqPipelineModel::addConnection(
  pqPipelineSource* source, pqPipelineSource* sink, int sourceOutputPort)
{
  if (!source || !sink)
  {
    qDebug() << "Cannot connect a null source or sink.";
    return;
  }

  // Note: this slot is invoked after the connection has been set.

  // If fanIn == 1, take the sink form the server list
  // and put it under the source.
  // If fanIn == 2, take the sink from  the under the source that it was put
  //  when fanIn == 1, replace that with a link. Add a new link for the new
  //  connection and put the source under the server list.
  // If fanIn > 2, just create a link for this new connection. We have already
  //  flagged it as a link when fanIn == 2.

  pqPipelineFilter* filter = qobject_cast<pqPipelineFilter*>(sink);
  if (!filter)
  {
    qDebug() << "Sink has to be a filter.";
    return;
  }

  pqPipelineModelDataItem* srcItem =
    this->getDataItem(source, &this->Internal->Root, pqPipelineModel::Proxy);
  pqPipelineModelDataItem* sinkItem =
    this->getDataItem(sink, &this->Internal->Root, pqPipelineModel::Proxy);
  if (!srcItem || !sinkItem)
  {
    qDebug() << "Connection involves a non-added source. Ignoring.";
    return;
  }

  if (source->getNumberOfOutputPorts() > 1)
  {
    srcItem = srcItem->Children[sourceOutputPort];
  }

  // NOTE-TO-SELF: Never use actual values of current connections from the
  // sources/filters -- think of change input dialog to know why.

  pqPipelineModelDataItem* currentParent = sinkItem->Parent;
  if (currentParent->Type == pqPipelineModel::Server && !sinkItem->Links.empty())
  {
    // sink has previously been identified as a "fan-in". We simply, create a
    // new link object for it.
    pqPipelineModelDataItem* link =
      new pqPipelineModelDataItem(this, sink, pqPipelineModel::Link, this);
    this->addChild(srcItem, link);
    return;
  }

  if (currentParent->Type == pqPipelineModel::Proxy || currentParent->Type == pqPipelineModel::Port)
  {

    // this filter had just 1 input previously, now it has 2. So we need to
    // upgrade it to a fan-in.
    pqPipelineModelDataItem* linkOld =
      new pqPipelineModelDataItem(this, sink, pqPipelineModel::Link, this);
    this->addChild(currentParent, linkOld);

    pqPipelineModelDataItem* link =
      new pqPipelineModelDataItem(this, sink, pqPipelineModel::Link, this);
    this->addChild(srcItem, link);

    pqServer* server = sink->getServer();
    pqPipelineModelDataItem* serverItem =
      this->getDataItem(server, &this->Internal->Root, pqPipelineModel::Server);

    this->removeChildFromParent(sinkItem);
    this->addChild(serverItem, sinkItem);
    return;
  }

  // Adding the first input connection for this sink.
  // Remove the sink from where ever.
  this->removeChildFromParent(sinkItem);

  // Add to the children of the source.
  this->addChild(srcItem, sinkItem);
}

//-----------------------------------------------------------------------------
void pqPipelineModel::removeConnection(
  pqPipelineSource* source, pqPipelineSource* sink, int sourceOutputPort)
{
  if (!source || !sink)
  {
    qDebug() << "Cannot disconnect a null source or sink.";
    return;
  }

  pqPipelineModelDataItem* sinkItem =
    this->getDataItem(sink, &this->Internal->Root, pqPipelineModel::Proxy);
  pqPipelineModelDataItem* srcItem =
    this->getDataItem(source, &this->Internal->Root, pqPipelineModel::Proxy);

  if (!sinkItem || !srcItem)
  {
    // qDebug() << "Connection involves a non-added source. Ignoring.";
    return;
  }

  // Note: this slot is invoked after the connection has been broken.

  if (sinkItem->Links.empty())
  {
    // Simplest case, sink had just 1 input.
    pqServer* server = sink->getServer();
    pqPipelineModelDataItem* serverItem =
      this->getDataItem(server, &this->Internal->Root, pqPipelineModel::Server);
    if (!serverItem)
    {
      qDebug() << "Failed to locate data item for server.";
      return;
    }

    this->removeChildFromParent(sinkItem);
    this->addChild(serverItem, sinkItem);
    return;
  }

  if (source->getNumberOfOutputPorts() > 1)
  {
    srcItem = srcItem->Children[sourceOutputPort];
  }

  // Has a fan-in for sure.
  // Remove the link item under the source.
  pqPipelineModelDataItem* linkItem = this->getDataItem(sink, srcItem, pqPipelineModel::Link);
  assert(linkItem != 0);
  this->removeChildFromParent(linkItem);
  delete linkItem;

  // locate all links for sink. If number of links == 1, remove the link and
  // move the sink to where the link was.
  if (sinkItem->Links.size() == 1)
  {
    linkItem = sinkItem->Links[0];
    pqPipelineModelDataItem* parentItem = linkItem->Parent;
    this->removeChildFromParent(linkItem);
    delete linkItem;

    this->removeChildFromParent(sinkItem);

    this->addChild(parentItem, sinkItem);
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::addExtractor(pqExtractor* egenerator)
{
  pqServer* server = egenerator->getServer();
  pqPipelineModelDataItem* parentItem =
    this->getDataItem(server, &this->Internal->Root, pqPipelineModel::Server);
  if (!parentItem)
  {
    qDebug() << "Could not locate server on which the extract-generator is being added.";
    return;
  }

  auto item = new pqPipelineModelDataItem(this, egenerator, pqPipelineModel::Extractor, this);

  // add to the 'server'.
  this->addChild(parentItem, item);

  // monitor common signals.
  this->connect(egenerator, SIGNAL(nameChanged(pqServerManagerModelItem*)),
    SLOT(updateData(pqServerManagerModelItem*)));
  this->connect(egenerator, SIGNAL(modifiedStateChanged(pqServerManagerModelItem*)),
    SLOT(updateData(pqServerManagerModelItem*)));

  // ensure the "enabled" icon is updated when enabled-state changes.
  QPointer<pqPipelineModelDataItem> itemPtr = item;
  QPointer<pqPipelineModel> self = this;
  QObject::connect(egenerator, &pqExtractor::enabledStateChanged,
    [itemPtr, self]()
    {
      if (itemPtr && self)
      {
        itemPtr->updateVisibilityIcon(self->View, false);
      }
    });
}

//-----------------------------------------------------------------------------
void pqPipelineModel::removeExtractor(pqExtractor* egenerator)
{
  if (auto item = this->getDataItem(egenerator, &this->Internal->Root, pqPipelineModel::Extractor))
  {
    this->removeChildFromParent(item);
    delete item;
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::addConnection(pqServerManagerModelItem* source, pqExtractor* sink)
{
  if (source == nullptr || sink == nullptr)
  {
    return;
  }

  if (sink->isImageExtractor())
  {
    return;
  }

  auto pqsource = qobject_cast<pqPipelineSource*>(source);
  auto port = qobject_cast<pqOutputPort*>(source);
  if (port)
  {
    pqsource = port->getSource();
  }
  if (!pqsource)
  {
    return;
  }

  auto& internals = (*this->Internal);
  auto srcItem = this->getDataItem(pqsource, &internals.Root, pqPipelineModel::Proxy);
  auto sinkItem = this->getDataItem(sink, &internals.Root, pqPipelineModel::Extractor);
  if (!srcItem || !sinkItem)
  {
    qDebug("Connection involves unknown items. Ignoring.");
    return;
  }

  if (port && pqsource->getNumberOfOutputPorts() > 1)
  {
    srcItem = srcItem->Children[port->getPortNumber()];
  }

  if (sinkItem->Parent != srcItem)
  {
    this->removeChildFromParent(sinkItem);
    this->addChild(srcItem, sinkItem);
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::removeConnection(pqServerManagerModelItem*, pqExtractor* sink)
{
  if (sink == nullptr || sink->isImageExtractor())
  {
    return;
  }

  auto& internals = (*this->Internal);
  auto sinkItem = this->getDataItem(sink, &internals.Root, pqPipelineModel::Extractor);

  pqServer* server = sink->getServer();
  auto serverItem = this->getDataItem(server, &internals.Root, pqPipelineModel::Server);
  if (serverItem && sinkItem)
  {
    this->removeChildFromParent(sinkItem);
    this->addChild(serverItem, sinkItem);
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::setView(pqView* newview)
{
  if (this->View == newview)
  {
    return;
  }
  this->View = newview;
  // update all VisibilityIcons.
  this->Internal->Root.updateVisibilityIcon(newview, true);
}

//-----------------------------------------------------------------------------
void pqPipelineModel::delayedUpdateVisibility(pqPipelineSource* source)
{
  this->Internal->DelayedUpdateVisibilityItems.removeAll(source);
  this->Internal->DelayedUpdateVisibilityItems.push_front(source);
  this->Internal->DelayedUpdateVisibilityTimer.start(0);
}

//-----------------------------------------------------------------------------
void pqPipelineModel::delayedUpdateVisibilityTimeout()
{
  Q_FOREACH (pqPipelineSource* source, this->Internal->DelayedUpdateVisibilityItems)
  {
    if (source)
    {
      this->updateVisibility(source);
    }
  }
  this->Internal->DelayedUpdateVisibilityItems.clear();
}

//-----------------------------------------------------------------------------
void pqPipelineModel::updateVisibility(pqPipelineSource* source, ItemType type /* = Proxy */)
{
  pqPipelineModelDataItem* item = this->getDataItem(source, &this->Internal->Root, type);
  if (item)
  {
    item->updateVisibilityIcon(this->View, false);
    Q_FOREACH (pqPipelineModelDataItem* child, item->Children)
    {
      if (child->Type == Port)
      {
        child->updateVisibilityIcon(this->View, false);
      }
    }

    Q_FOREACH (pqPipelineModelDataItem* link, item->Links)
    {
      link->updateVisibilityIcon(this->View, false);
    }
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::updateData(pqServerManagerModelItem* source, ItemType type /* = Proxy */)
{
  pqPipelineModelDataItem* item = this->getDataItem(source, &this->Internal->Root, type);
  if (item)
  {
    item->updateVisibilityIcon(this->View, false);
    this->itemDataChanged(item);
    Q_FOREACH (pqPipelineModelDataItem* link, item->Links)
    {
      item->updateVisibilityIcon(this->View, false);
      this->itemDataChanged(link);
    }
  }
  else // source is null, so update everything
  {
    Q_FOREACH (pqPipelineModelDataItem* child, this->Internal->Root.Children)
    {
      this->itemDataChanged(child);
    }
  }
}

//-----------------------------------------------------------------------------
void pqPipelineModel::updateDataServer(pqServer* server)
{
  updateData(server, Invalid);
}

//-----------------------------------------------------------------------------
void pqPipelineModel::setModifiedFont(const QFont& font)
{
  this->Internal->ModifiedFont = font;
}

//-----------------------------------------------------------------------------
void pqPipelineModel::setAnnotationFilterMatching(bool matching)
{
  this->FilterAnnotationMatching = matching;
}

//-----------------------------------------------------------------------------
void pqPipelineModel::enableFilterAnnotationKey(const QString& expectedAnnotation)
{
  this->FilterRoleAnnotationKey = expectedAnnotation;
}

//-----------------------------------------------------------------------------
void pqPipelineModel::disableFilterAnnotationKey()
{
  this->FilterRoleAnnotationKey.clear();
}

//-----------------------------------------------------------------------------
void pqPipelineModel::enableFilterSession(vtkPVSession* session)
{
  this->FilterRoleSession = session;
}

//-----------------------------------------------------------------------------
void pqPipelineModel::disableFilterSession()
{
  this->FilterRoleSession = nullptr;
}

//-----------------------------------------------------------------------------
bool pqPipelineModel::checkAndLoadPipelinePixmap(const QString& iconType)
{
  auto it = this->PixmapMap.find(iconType);
  if (it != this->PixmapMap.end())
  {
    return true;
  }

  return this->PixmapMap[iconType].load(iconType);
}
