// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-FileCopyrightText: Copyright (c) Sandia Corporation
// SPDX-License-Identifier: BSD-3-Clause

/// \file pqCustomFilterDefinitionWizard.cxx
/// \date 6/19/2006

#include "pqCustomFilterDefinitionWizard.h"
#include "ui_pqCustomFilterDefinitionWizard.h"

#include "pqCustomFilterDefinitionModel.h"
#include "pqCustomFilterManagerModel.h"
#include "pqFlatTreeView.h"
#include "pqOutputPort.h"
#include "pqPipelineSource.h"
#include "pqWidgetUtilities.h"

#include <QHeaderView>
#include <QMessageBox>
#include <QModelIndex>
#include <QStringList>
#include <QTreeWidget>
#include <QTreeWidgetItem>

#include "vtkPVXMLElement.h"
#include "vtkSMCompoundSourceProxy.h"
#include "vtkSMInputProperty.h"
#include "vtkSMPropertyIterator.h"
#include "vtkSMProxy.h"
#include "vtkSMProxyManager.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSmartPointer.h"

#include <algorithm>

class pqCustomFilterDefinitionWizardForm : public Ui::pqCustomFilterDefinitionWizard
{
public:
  QStringList ExposedPropertyNames; ///< Used to ensure all exposed
                                    ///  property names are unique.

  QStringList ExposedPortNames; ///< Used to ensure all exposed
                                ///  port names are unique.
  QStringList ToExposeNames;    ///< Used to ensure that same thing
                                /// (either property or port) is not exposed twice.

  // Maps a proxy name with a map of <propertyLabel, propertyName>
  // pairs for each of its properties. This assumes that labels of
  // a proxy's properties are unique.
  QMap<QString, QMap<QString, QString>> LabelToNamePropertyMap;
};

//-----------------------------------------------------------------------------
pqCustomFilterDefinitionWizard::pqCustomFilterDefinitionWizard(
  pqCustomFilterDefinitionModel* model, QWidget* widgetParent)
  : QDialog(widgetParent)
{
  this->CurrentPage = 0;
  this->OverwriteOK = false;
  this->Filter = nullptr;
  this->Model = model;
  this->Form = new pqCustomFilterDefinitionWizardForm();
  this->Form->setupUi(this);
  pqWidgetUtilities::formatChildTooltips(this);

  // hide the Context Help item (it's a "?" in the Title Bar for Windows, a menu item for Linux)
  this->setWindowFlags(this->windowFlags().setFlag(Qt::WindowContextHelpButtonHint, false));

  // Initialize the form.
  this->Form->TitleFrame->setBackgroundRole(QPalette::Base);
  this->Form->TitleFrame->setAutoFillBackground(true);
  this->Form->TitleStack->setCurrentIndex(this->CurrentPage);
  this->Form->PageStack->setCurrentIndex(this->CurrentPage);
  this->Form->BackButton->setEnabled(false);
  this->Form->RemoveInputButton->setEnabled(false);
  this->Form->InputUpButton->setEnabled(false);
  this->Form->InputDownButton->setEnabled(false);
  this->Form->RemoveOutputButton->setEnabled(false);
  this->Form->OutputUpButton->setEnabled(false);
  this->Form->OutputDownButton->setEnabled(false);
  this->Form->RemovePropertyButton->setEnabled(false);
  this->Form->PropertyUpButton->setEnabled(false);
  this->Form->PropertyDownButton->setEnabled(false);

  this->Form->InputPipeline->getHeader()->hide();
  this->Form->OutputPipeline->getHeader()->hide();
  this->Form->PropertyPipeline->getHeader()->hide();
  this->Form->InputPipeline->setModel(this->Model);
  this->Form->OutputPipeline->setModel(this->Model);
  this->Form->PropertyPipeline->setModel(this->Model);

  this->setupDefaultInputOutput();

  // Listen to the button click events.
  QObject::connect(this->Form->CancelButton, SIGNAL(clicked()), this, SLOT(reject()));
  QObject::connect(this->Form->BackButton, SIGNAL(clicked()), this, SLOT(navigateBack()));
  QObject::connect(this->Form->NextButton, SIGNAL(clicked()), this, SLOT(navigateNext()));
  QObject::connect(this->Form->FinishButton, SIGNAL(clicked()), this, SLOT(finishWizard()));

  // Listen to the selection events.
  QObject::connect(this->Form->InputPipeline->getSelectionModel(),
    SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this,
    SLOT(updateInputForm(const QModelIndex&, const QModelIndex&)));
  QObject::connect(this->Form->OutputPipeline->getSelectionModel(),
    SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this,
    SLOT(updateOutputForm(const QModelIndex&, const QModelIndex&)));
  QObject::connect(this->Form->PropertyPipeline->getSelectionModel(),
    SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this,
    SLOT(updatePropertyForm(const QModelIndex&, const QModelIndex&)));
  QObject::connect(this->Form->InputPorts->selectionModel(),
    SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this,
    SLOT(updateInputButtons(const QModelIndex&, const QModelIndex&)));
  QObject::connect(this->Form->OutputPorts->selectionModel(),
    SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this,
    SLOT(updateOutputButtons(const QModelIndex&, const QModelIndex&)));
  QObject::connect(this->Form->PropertyList->selectionModel(),
    SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this,
    SLOT(updatePropertyButtons(const QModelIndex&, const QModelIndex&)));

  // Listen to the list editing buttons.
  QObject::connect(this->Form->AddInputButton, SIGNAL(clicked()), this, SLOT(addInput()));
  QObject::connect(this->Form->RemoveInputButton, SIGNAL(clicked()), this, SLOT(removeInput()));
  QObject::connect(this->Form->InputUpButton, SIGNAL(clicked()), this, SLOT(moveInputUp()));
  QObject::connect(this->Form->InputDownButton, SIGNAL(clicked()), this, SLOT(moveInputDown()));
  QObject::connect(this->Form->AddOutputButton, SIGNAL(clicked()), this, SLOT(addOutput()));
  QObject::connect(this->Form->RemoveOutputButton, SIGNAL(clicked()), this, SLOT(removeOutput()));
  QObject::connect(this->Form->OutputUpButton, SIGNAL(clicked()), this, SLOT(moveOutputUp()));
  QObject::connect(this->Form->OutputDownButton, SIGNAL(clicked()), this, SLOT(moveOutputDown()));
  QObject::connect(this->Form->AddPropertyButton, SIGNAL(clicked()), this, SLOT(addProperty()));
  QObject::connect(
    this->Form->RemovePropertyButton, SIGNAL(clicked()), this, SLOT(removeProperty()));
  QObject::connect(this->Form->PropertyUpButton, SIGNAL(clicked()), this, SLOT(movePropertyUp()));
  QObject::connect(
    this->Form->PropertyDownButton, SIGNAL(clicked()), this, SLOT(movePropertyDown()));

  // Listen for name changes.
  QObject::connect(this->Form->CustomFilterName, SIGNAL(textEdited(const QString&)), this,
    SLOT(clearNameOverwrite(const QString&)));

  // When combo selection changes, update new label to be same as old.
  QObject::connect(this->Form->PropertyCombo, &QComboBox::currentTextChanged,
    this->Form->PropertyName, &QLineEdit::setText);
}

//-----------------------------------------------------------------------------
pqCustomFilterDefinitionWizard::~pqCustomFilterDefinitionWizard()
{
  delete this->Form;

  if (this->Filter)
  {
    // Release the reference to the compound proxy.
    this->Filter->Delete();
  }
}

//-----------------------------------------------------------------------------
QString pqCustomFilterDefinitionWizard::getCustomFilterName() const
{
  if (this->Filter)
  {
    return this->Form->CustomFilterName->text();
  }

  return QString();
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::createCustomFilter()
{
  if (this->Filter != nullptr || this->Form->CustomFilterName->text().isEmpty())
  {
    return;
  }

  // Create the compound proxy. Add all the proxies to it.
  pqPipelineSource* source = nullptr;
  this->Filter = vtkSMCompoundSourceProxy::New();
  bool first = true;
  QModelIndex index = this->Model->getNextIndex(QModelIndex());
  while (index.isValid())
  {
    source = this->Model->getSourceFor(index);
    if (source)
    {
      if (first)
      {
        this->Filter->SetSession(source->getProxy()->GetSession());
        this->Filter->SetLocation(source->getProxy()->GetLocation());
        first = false;
      }
      this->Filter->AddProxy(source->getSMName().toUtf8().data(), source->getProxy());
    }

    index = this->Model->getNextIndex(index);
  }

  // Expose the input properties.
  int i = 0;
  QTreeWidgetItem* item = nullptr;
  int numInputs = this->Form->InputPorts->topLevelItemCount();
  for (; i < numInputs; i++)
  {
    item = this->Form->InputPorts->topLevelItem(i);
    this->Filter->ExposeProperty(item->text(0).toUtf8().data(),
      this->Form->LabelToNamePropertyMap[item->text(0)][item->text(1)].toUtf8().data(),
      item->text(2).toUtf8().data());
  }

  // Set the output proxies.
  int total = this->Form->OutputPorts->topLevelItemCount();
  for (i = 0; i < total; i++)
  {
    item = this->Form->OutputPorts->topLevelItem(i);
    pqOutputPort* port = qobject_cast<pqOutputPort*>(item->data(0, Qt::UserRole).value<QObject*>());
    if (port)
    {
      this->Filter->ExposeOutputPort(port->getSource()->getSMName().toUtf8().data(),
        port->getPortNumber(), item->text(1).toUtf8().data());
    }
  }

  // Expose the properties.
  total = this->Form->PropertyList->topLevelItemCount();
  for (i = 0; i < total; i++)
  {
    item = this->Form->PropertyList->topLevelItem(i);
    this->Filter->ExposeProperty(item->text(0).toUtf8().data(),
      this->Form->LabelToNamePropertyMap[item->text(0)][item->text(1)].toUtf8().data(),
      item->text(2).toUtf8().data());
  }

  // Include any internal proxies.
  this->addAutoIncludedProxies();

  // Register the compound proxy definition with the server manager.
  vtkPVXMLElement* root = this->Filter->SaveDefinition(nullptr);
  vtkSMSessionProxyManager* proxyManager = source->proxyManager();
  if (numInputs > 0)
  {
    proxyManager->RegisterCustomProxyDefinition(
      "filters", this->Form->CustomFilterName->text().toUtf8().data(), root);
  }
  else
  {
    proxyManager->RegisterCustomProxyDefinition(
      "sources", this->Form->CustomFilterName->text().toUtf8().data(), root);
  }
  root->Delete();
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::addAutoIncludedProxies()
{
  unsigned int num_of_proxies = this->Filter->GetNumberOfProxies();
  vtkSMSessionProxyManager* pxm =
    vtkSMProxyManager::GetProxyManager()->GetActiveSessionProxyManager();

  QSet<vtkSMProxy*> autoIncludeSet;

  for (unsigned int cc = 0; cc < num_of_proxies; cc++)
  {
    vtkSMProxy* subProxy = this->Filter->GetProxy(cc);
    vtkSmartPointer<vtkSMPropertyIterator> iter;
    iter.TakeReference(subProxy->NewPropertyIterator());

    for (iter->Begin(); !iter->IsAtEnd(); iter->Next())
    {
      vtkSMProxyProperty* pp = vtkSMProxyProperty::SafeDownCast(iter->GetProperty());
      if (!pp)
      {
        continue;
      }
      unsigned int proxy_count = pp->GetNumberOfProxies();
      for (unsigned int i = 0; i < proxy_count; i++)
      {
        vtkSMProxy* proxy = pp->GetProxy(i);
        if (!proxy || pxm->GetProxyName("sources", proxy) != nullptr)
        {
          continue;
        }
        autoIncludeSet.insert(proxy);
      }
    }
  }
  Q_FOREACH (vtkSMProxy* proxy, autoIncludeSet)
  {
    QString name = "auto_";
    name += proxy->GetGlobalIDAsString();
    this->Filter->AddProxy(name.toUtf8().data(), proxy);
  }
}

//-----------------------------------------------------------------------------
bool pqCustomFilterDefinitionWizard::validateCustomFilterName()
{
  // Make sure the user has entered a name for the custom filter.
  QString filterName = this->Form->CustomFilterName->text();
  if (filterName.isEmpty())
  {
    QMessageBox::warning(this, "No Name",
      "The custom filter name field is empty.\n"
      "Please enter a unique name for the custom filter.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->CustomFilterName->setFocus();
    return false;
  }

  // Make sure the name is unique.
  vtkSMSessionProxyManager* proxyManager =
    vtkSMProxyManager::GetProxyManager()->GetActiveSessionProxyManager();
  if (!this->OverwriteOK)
  {
    if (proxyManager->GetProxyDefinition("filters", filterName.toUtf8().data()) ||
      proxyManager->GetProxyDefinition("sources", filterName.toUtf8().data()))
    {
      QMessageBox::warning(this, "Duplicate Name",
        "This filter name already exists.\n"
        "Please enter a different name.",
        QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
      return false;
    }
  }

  return true;
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::setupDefaultInputOutput()
{
  if (this->Model->rowCount() == 1)
  {
    // Set up the default input property.
    QModelIndex index = this->Model->index(0, 0);
    pqPipelineSource* source = this->Model->getSourceFor(index);
    if (source)
    {
      vtkSMProxy* proxy = source->getProxy();
      if (proxy)
      {
        QStringList inputNames;
        vtkSMInputProperty* input = nullptr;
        vtkSMPropertyIterator* iter = proxy->NewPropertyIterator();
        for (iter->Begin(); !iter->IsAtEnd(); iter->Next())
        {
          input = vtkSMInputProperty::SafeDownCast(iter->GetProperty());
          if (input)
          {
            inputNames.append(QString(iter->GetKey()));
          }
        }

        iter->Delete();
        if (!inputNames.empty())
        {
          // Add the "Input" property if it exists. Otherwise, use the
          // first property.
          QString propertyName = "Input";
          if (!inputNames.contains("Input"))
          {
            propertyName = inputNames[0];
          }

          QStringList list;
          list.append(source->getSMName());
          list.append(propertyName);
          list.append("Input");
          QTreeWidgetItem* item = new QTreeWidgetItem(this->Form->InputPorts, list);
          this->Form->LabelToNamePropertyMap[source->getSMName()][propertyName] = propertyName;
          this->Form->InputPorts->setCurrentItem(item);
          this->Form->ExposedPropertyNames.append("Input");
          this->Form->ToExposeNames.append(
            QString("INPUT:%1.%2").arg(item->text(0)).arg(item->text(1)));
        }
      }
    }

    // Set up the default output property.
    while (this->Model->hasChildren(index))
    {
      if (this->Model->rowCount(index) > 0)
      {
        index = this->Model->index(0, 0, index);
      }
      else
      {
        index = QModelIndex();
        break;
      }
    }

    pqPipelineSource* output = this->Model->getSourceFor(index);
    if (output)
    {
      this->addOutputInternal(output->getOutputPort(0), "Output");
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::navigateBack()
{
  if (this->CurrentPage > 0)
  {
    // Decrement the page number and set the stacks to the appropriate
    // page. Update the button state as needed.
    this->CurrentPage--;
    this->Form->TitleStack->setCurrentIndex(this->CurrentPage);
    this->Form->PageStack->setCurrentIndex(this->CurrentPage);
    if (this->CurrentPage == 0)
    {
      this->Form->BackButton->setEnabled(false);
    }
    else if (this->CurrentPage == 2)
    {
      this->Form->NextButton->setEnabled(true);
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::navigateNext()
{
  if (this->CurrentPage < 3)
  {
    if (this->CurrentPage == 0 && !this->validateCustomFilterName())
    {
      return;
    }

    // Increment the page number and set the stacks to the appropriate
    // page. Update the button state as needed.
    this->CurrentPage++;
    this->Form->TitleStack->setCurrentIndex(this->CurrentPage);
    this->Form->PageStack->setCurrentIndex(this->CurrentPage);
    if (this->CurrentPage == 1)
    {
      this->Form->BackButton->setEnabled(true);
    }
    else if (this->CurrentPage == 3)
    {
      this->Form->NextButton->setEnabled(false);
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::finishWizard()
{
  // Make sure the name has been entered and is unique.
  if (this->validateCustomFilterName())
  {
    this->accept();
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::clearNameOverwrite(const QString&)
{
  this->OverwriteOK = false;
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::updateInputForm(const QModelIndex& current, const QModelIndex&)
{
  // Clear the form fields.
  this->Form->InputName->setText("");
  this->Form->InputCombo->clear();

  // Get the proxy from the index. Use the proxy to find all the input
  // properties to put in the combo box.
  pqPipelineSource* source = this->Model->getSourceFor(current);
  if (source)
  {
    vtkSMProxy* proxy = source->getProxy();
    if (proxy)
    {
      vtkSMInputProperty* input = nullptr;
      vtkSMPropertyIterator* iter = proxy->NewPropertyIterator();
      for (iter->Begin(); !iter->IsAtEnd(); iter->Next())
      {
        input = vtkSMInputProperty::SafeDownCast(iter->GetProperty());
        if (input)
        {
          const char* name = iter->GetProperty()->GetXMLLabel();
          if (!name)
          {
            name = iter->GetKey();
          }
          this->Form->LabelToNamePropertyMap[source->getSMName()][name] = iter->GetKey();
          this->Form->InputCombo->addItem(tr(name));
        }
      }

      iter->Delete();
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::updateOutputForm(
  const QModelIndex& current, const QModelIndex&)
{
  // Clear the output name field.
  this->Form->OutputName->setText("");
  this->Form->OutputCombo->clear();

  // * Get the proxy from the index.
  // * Find the names for all output ports for that proxy.
  // * Populate the OutputCombo with available options.
  pqPipelineSource* source = this->Model->getSourceFor(current);
  if (source)
  {
    vtkSMSourceProxy* proxy = vtkSMSourceProxy::SafeDownCast(source->getProxy());
    if (proxy)
    {
      unsigned int numPorts = proxy->GetNumberOfOutputPorts();
      for (unsigned int cc = 0; cc < numPorts; cc++)
      {
        this->Form->OutputCombo->addItem(proxy->GetOutputPortName(cc));
      }
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::updatePropertyForm(
  const QModelIndex& current, const QModelIndex&)
{
  // Clear the form fields.
  this->Form->PropertyName->setText("");
  this->Form->PropertyCombo->clear();
  // this->Form->LabelToNamePropertyMap.clear();

  // Get the proxy from the index. Use the proxy to find all the
  // properties to put in the combo box. Don't display the input
  // properties.
  pqPipelineSource* source = this->Model->getSourceFor(current);
  if (source)
  {
    vtkSMProxy* proxy = source->getProxy();
    if (proxy)
    {
      vtkSMInputProperty* input = nullptr;
      vtkSMPropertyIterator* iter = proxy->NewPropertyIterator();
      for (iter->Begin(); !iter->IsAtEnd(); iter->Next())
      {
        input = vtkSMInputProperty::SafeDownCast(iter->GetProperty());
        if (!input)
        {
          const char* name = iter->GetProperty()->GetXMLLabel();
          if (!name)
          {
            name = iter->GetKey();
          }
          this->Form->LabelToNamePropertyMap[source->getSMName()][name] = iter->GetKey();
          this->Form->PropertyCombo->addItem(tr(name));
        }
      }

      iter->Delete();
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::addInput()
{
  // Validate the entry. Make sure there is an object and a property
  // selected. Make sure the name is unique.
  pqPipelineSource* source =
    this->Model->getSourceFor(this->Form->InputPipeline->getSelectionModel()->currentIndex());
  if (!source)
  {
    QMessageBox::warning(this, "No Object Selected",
      "No pipeline object is selected.\n"
      "Please select a pipeline object from the list on the left.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    return;
  }

  int propertyIndex = this->Form->InputCombo->currentIndex();
  if (propertyIndex == -1)
  {
    QMessageBox::warning(this, "No Input Properties",
      "The selected pipeline object does not have any inputs.\n"
      "Please select another pipeline object from the list on the left.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    return;
  }

  QString name = this->Form->InputName->text();
  if (name.isEmpty())
  {
    QMessageBox::warning(this, "No Name",
      "The input name field is empty.\n"
      "Please enter a unique name for the input.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->InputName->setFocus();
    return;
  }

  if (this->Form->ExposedPropertyNames.contains(name))
  {
    QMessageBox::warning(this, "Duplicate Name",
      "Another input already has the name entered.\n"
      "Please enter a unique name for the input.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->InputName->setFocus();
    this->Form->InputName->selectAll();
    return;
  }

  QString key = QString("INPUT:%1.%2")
                  .arg(source->getSMName())
                  .arg(this->Form->InputCombo->itemText(propertyIndex));
  if (this->Form->ToExposeNames.contains(key))
  {
    QMessageBox::warning(this, "Duplicate Input", "The selected Input has already been added.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    return;
  }

  // Add the exposed input port to the list.
  QStringList list;
  list.append(source->getSMName());
  list.append(this->Form->InputCombo->itemText(propertyIndex));
  list.append(name);
  QTreeWidgetItem* item = new QTreeWidgetItem(this->Form->InputPorts, list);
  this->Form->InputPorts->setCurrentItem(item);
  this->Form->ExposedPropertyNames.append(name);
  this->Form->ToExposeNames.append(key);
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::removeInput()
{
  // Remove the selected row from the list.
  QTreeWidgetItem* item = this->Form->InputPorts->currentItem();
  if (item)
  {
    int row = this->Form->InputPorts->indexOfTopLevelItem(item) - 1;
    this->Form->ExposedPropertyNames.removeAll(item->text(2));
    QString key = QString("INPUT:%1.%2").arg(item->text(0)).arg(item->text(1));
    this->Form->ToExposeNames.removeAll(key);
    delete item;
    row = std::max(row, 0);

    item = this->Form->InputPorts->topLevelItem(row);
    if (item)
    {
      this->Form->InputPorts->setCurrentItem(item);
    }
    else
    {
      this->updateInputButtons(QModelIndex(), QModelIndex());
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::moveInputUp()
{
  // Move the selected row up one if possible.
  QTreeWidgetItem* item = this->Form->InputPorts->currentItem();
  if (item)
  {
    int row = this->Form->InputPorts->indexOfTopLevelItem(item);
    if (row > 0)
    {
      this->Form->InputPorts->takeTopLevelItem(row--);
      this->Form->InputPorts->insertTopLevelItem(row, item);
      this->Form->InputPorts->setCurrentItem(item);
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::moveInputDown()
{
  // Move the selected row down one if possible.
  QTreeWidgetItem* item = this->Form->InputPorts->currentItem();
  if (item)
  {
    int row = this->Form->InputPorts->indexOfTopLevelItem(item);
    if (row < this->Form->InputPorts->topLevelItemCount() - 1)
    {
      this->Form->InputPorts->takeTopLevelItem(row++);
      this->Form->InputPorts->insertTopLevelItem(row, item);
      this->Form->InputPorts->setCurrentItem(item);
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::addOutput()
{
  // Validate the entry. Make sure there is an object and a property
  // selected. Make sure the name is unique.
  pqPipelineSource* source =
    this->Model->getSourceFor(this->Form->OutputPipeline->getSelectionModel()->currentIndex());
  if (!source)
  {
    QMessageBox::warning(this, "No Object Selected",
      "No pipeline object is selected.\n"
      "Please select a pipeline object from the list on the left.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    return;
  }

  QString name = this->Form->OutputName->text();
  if (name.isEmpty())
  {
    QMessageBox::warning(this, "No Name",
      "The output name field is empty.\n"
      "Please enter a unique name for the output.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->OutputName->setFocus();
    return;
  }

  if (this->Form->ExposedPortNames.contains(name))
  {
    QMessageBox::warning(this, "Duplicate Name",
      "Another output already has the name entered.\n"
      "Please enter a unique name for the output.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->OutputName->setFocus();
    this->Form->OutputName->selectAll();
    return;
  }

  // Validate port name.
  QString portname = this->Form->OutputCombo->currentText();
  pqOutputPort* port = source->getOutputPort(portname);
  if (!port)
  {
    QMessageBox::warning(this, "No Output Port Selected",
      "No output port was selected or selected output port does not exist.\n"
      "Please select a output port from the \"Output Port\" combo box.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->OutputCombo->setFocus();
    return;
  }

  // Verify that the selected port hasn't already been exposed.
  // We don't want to export the same output port twice.
  QString key = QString("OUTPUT:%1 (%2)").arg(source->getSMName()).arg(port->getPortNumber());
  if (this->Form->ToExposeNames.contains(key))
  {
    QMessageBox::warning(this, "Duplicate Output", "Selected output port has already been exposed.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->OutputCombo->setFocus();
    return;
  }

  this->addOutputInternal(port, name);
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::addOutputInternal(pqOutputPort* port, const QString& name)
{
  pqPipelineSource* source = port->getSource();
  QString key = QString("OUTPUT:%1 (%2)").arg(source->getSMName()).arg(port->getPortNumber());
  // Add the exposed output port to the list.
  QStringList list;
  if (source->getNumberOfOutputPorts() > 1)
  {
    list.append(QString("%1 (%2)").arg(source->getSMName()).arg(port->getPortNumber()));
  }
  else
  {
    list.append(source->getSMName());
  }
  list.append(name);
  QTreeWidgetItem* item = new QTreeWidgetItem(this->Form->OutputPorts, list);
  item->setData(0, Qt::UserRole, QVariant::fromValue<QObject*>(port));
  this->Form->OutputPorts->setCurrentItem(item);
  this->Form->ExposedPortNames.append(name);
  this->Form->ToExposeNames.append(key);
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::removeOutput()
{
  // Remove the selected row from the list.
  QTreeWidgetItem* item = this->Form->OutputPorts->currentItem();
  if (item)
  {
    int row = this->Form->OutputPorts->indexOfTopLevelItem(item) - 1;
    this->Form->ExposedPortNames.removeAll(item->text(1));
    pqOutputPort* port = qobject_cast<pqOutputPort*>(item->data(0, Qt::UserRole).value<QObject*>());
    pqPipelineSource* source = port->getSource();
    QString key = QString("OUTPUT:%1 (%2)").arg(source->getSMName()).arg(port->getPortNumber());
    this->Form->ToExposeNames.removeAll(key);
    delete item;
    row = std::max(row, 0);

    item = this->Form->OutputPorts->topLevelItem(row);
    if (item)
    {
      this->Form->OutputPorts->setCurrentItem(item);
    }
    else
    {
      this->updateOutputButtons(QModelIndex(), QModelIndex());
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::moveOutputUp()
{
  // Move the selected row up one if possible.
  QTreeWidgetItem* item = this->Form->OutputPorts->currentItem();
  if (item)
  {
    int row = this->Form->OutputPorts->indexOfTopLevelItem(item);
    if (row > 0)
    {
      this->Form->OutputPorts->takeTopLevelItem(row--);
      this->Form->OutputPorts->insertTopLevelItem(row, item);
      this->Form->OutputPorts->setCurrentItem(item);
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::moveOutputDown()
{
  // Move the selected row down one if possible.
  QTreeWidgetItem* item = this->Form->OutputPorts->currentItem();
  if (item)
  {
    int row = this->Form->OutputPorts->indexOfTopLevelItem(item);
    if (row < this->Form->OutputPorts->topLevelItemCount() - 1)
    {
      this->Form->OutputPorts->takeTopLevelItem(row++);
      this->Form->OutputPorts->insertTopLevelItem(row, item);
      this->Form->OutputPorts->setCurrentItem(item);
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::addProperty()
{
  // Validate the entry. Make sure there is an object and a property
  // selected. Make sure the name is unique.
  pqPipelineSource* source =
    this->Model->getSourceFor(this->Form->PropertyPipeline->getSelectionModel()->currentIndex());
  if (!source)
  {
    QMessageBox::warning(this, "No Object Selected",
      "No pipeline object is selected.\n"
      "Please select a pipeline object from the list on the left.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    return;
  }

  int propertyIndex = this->Form->PropertyCombo->currentIndex();
  if (propertyIndex == -1)
  {
    QMessageBox::warning(this, "No Properties",
      "The selected pipeline object does not have any properties.\n"
      "Please select another pipeline object from the list on the left.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    return;
  }

  QString name = this->Form->PropertyName->text();
  if (name.isEmpty())
  {
    QMessageBox::warning(this, "No Name",
      "The property name field is empty.\n"
      "Please enter a unique name for the property.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->PropertyName->setFocus();
    return;
  }

  if (this->Form->ExposedPropertyNames.contains(name))
  {
    QMessageBox::warning(this, "Duplicate Name",
      "Another property already has the name entered.\n"
      "Please enter a unique name for the property.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->PropertyName->setFocus();
    this->Form->PropertyName->selectAll();
    return;
  }

  QString key = QString("INPUT:%1.%2")
                  .arg(source->getSMName())
                  .arg(this->Form->PropertyCombo->itemText(propertyIndex));
  if (this->Form->ToExposeNames.contains(key))
  {
    QMessageBox::warning(this, "Duplicate Property",
      "The selected property have already been exposed.\n"
      "Please select another property.",
      QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton);
    this->Form->PropertyCombo->setFocus();
    return;
  }

  // Add the exposed property to the list.
  QStringList list;
  list.append(source->getSMName());
  list.append(this->Form->PropertyCombo->itemText(propertyIndex));
  list.append(name);
  QTreeWidgetItem* item = new QTreeWidgetItem(this->Form->PropertyList, list);
  this->Form->PropertyList->setCurrentItem(item);
  this->Form->ExposedPropertyNames.append(name);
  this->Form->ToExposeNames.append(key);
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::removeProperty()
{
  // Remove the selected row from the list.
  QTreeWidgetItem* item = this->Form->PropertyList->currentItem();
  if (item)
  {
    int row = this->Form->PropertyList->indexOfTopLevelItem(item) - 1;
    this->Form->ExposedPropertyNames.removeAll(item->text(2));
    QString key = QString("INPUT:%1.%2").arg(item->text(0)).arg(item->text(1));
    this->Form->ToExposeNames.removeAll(key);
    delete item;
    row = std::max(row, 0);

    item = this->Form->PropertyList->topLevelItem(row);
    if (item)
    {
      this->Form->PropertyList->setCurrentItem(item);
    }
    else
    {
      this->updatePropertyButtons(QModelIndex(), QModelIndex());
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::movePropertyUp()
{
  // Move the selected row up one if possible.
  QTreeWidgetItem* item = this->Form->PropertyList->currentItem();
  if (item)
  {
    int row = this->Form->PropertyList->indexOfTopLevelItem(item);
    if (row > 0)
    {
      this->Form->PropertyList->takeTopLevelItem(row--);
      this->Form->PropertyList->insertTopLevelItem(row, item);
      this->Form->PropertyList->setCurrentItem(item);
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::movePropertyDown()
{
  // Move the selected row down one if possible.
  QTreeWidgetItem* item = this->Form->PropertyList->currentItem();
  if (item)
  {
    int row = this->Form->PropertyList->indexOfTopLevelItem(item);
    if (row < this->Form->PropertyList->topLevelItemCount() - 1)
    {
      this->Form->PropertyList->takeTopLevelItem(row++);
      this->Form->PropertyList->insertTopLevelItem(row, item);
      this->Form->PropertyList->setCurrentItem(item);
    }
  }
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::updateInputButtons(
  const QModelIndex& current, const QModelIndex&)
{
  bool indexIsValid = current.isValid();
  this->Form->RemoveInputButton->setEnabled(indexIsValid);
  this->Form->InputUpButton->setEnabled(indexIsValid);
  this->Form->InputDownButton->setEnabled(indexIsValid);
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::updateOutputButtons(
  const QModelIndex& current, const QModelIndex&)
{
  bool indexIsValid = current.isValid();
  this->Form->RemoveOutputButton->setEnabled(indexIsValid);
  this->Form->OutputUpButton->setEnabled(indexIsValid);
  this->Form->OutputDownButton->setEnabled(indexIsValid);
}

//-----------------------------------------------------------------------------
void pqCustomFilterDefinitionWizard::updatePropertyButtons(
  const QModelIndex& current, const QModelIndex&)
{
  bool indexIsValid = current.isValid();
  this->Form->RemovePropertyButton->setEnabled(indexIsValid);
  this->Form->PropertyUpButton->setEnabled(indexIsValid);
  this->Form->PropertyDownButton->setEnabled(indexIsValid);
}
