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

   Program: ParaView
   Module:  pqColorAnnotationsWidget.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 "pqColorAnnotationsWidget.h"
#include "ui_pqColorAnnotationsWidget.h"
#include "ui_pqSavePresetOptions.h"

#include "pqActiveObjects.h"
#include "pqChooseColorPresetReaction.h"
#include "pqCoreUtilities.h"
#include "pqDataRepresentation.h"
#include "pqDoubleRangeDialog.h"
#include "pqDoubleRangeWidget.h"
#include "pqHeaderView.h"
#include "pqPropertiesPanel.h"
#include "pqScalarBarVisibilityReaction.h"
#include "pqTreeViewSelectionHelper.h"

#include "vtkAbstractArray.h"
#include "vtkCollection.h"
#include "vtkNew.h"
#include "vtkPVArrayInformation.h"
#include "vtkPVProminentValuesInformation.h"
#include "vtkSMPVRepresentationProxy.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxy.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMTransferFunctionPresets.h"
#include "vtkSMTransferFunctionProxy.h"
#include "vtkSmartPointer.h"
#include "vtkVariant.h"
#include "vtkVariantArray.h"

#include <vtk_jsoncpp.h>

#include <QColorDialog>
#include <QItemSelection>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QSortFilterProxyModel>
#include <QString>
#include <QTextStream>

#include <cassert>
#include <set>
#include <sstream>
#include <vector>

static const std::string INDEXED_COLORS = "IndexedColors";
static const std::string ANNOTATIONS = "Annotations";

namespace
{

//-----------------------------------------------------------------------------
// Given a list of existing annotations and a list of potentially new
// annotations, merge the lists. The candidate annotations are first
// selected to fill in empty annotation values in the existing
// annotations list, then they are added to the end.
// Arguments are vectors of pairs where the pair.first is the annotated value
// while the pair.second is the label/text for the value.
std::vector<std::pair<QString, QString>> MergeAnnotations(
  const std::vector<std::pair<QString, QString>>& current_pairs,
  const std::vector<std::pair<QString, QString>>& new_pairs)
{
  QMap<QString, QString> old_values;
  for (const auto& pair : current_pairs)
  {
    old_values[pair.first] = pair.second;
  }

  // Subset candidate annotations to only those not in existing annotations.
  // At the same time, update old_values map to have better annotation
  // text/labels, if present in the new values.
  std::vector<std::pair<QString, QString>> real_new_pairs;
  for (const auto& pair : new_pairs)
  {
    auto iter = old_values.find(pair.first);
    if (iter == old_values.end())
    {
      real_new_pairs.push_back(pair);
    }
    else if (iter.value().isEmpty())
    {
      // update old values with text from new pairs if it's `better`
      iter.value() = pair.second;
    }
  }

  // Iterate over existing annotations, backfilling annotation texts/labels
  // from the new_pairs, if old texts were empty.
  std::vector<std::pair<QString, QString>> merged_pairs;
  merged_pairs.reserve(current_pairs.size() + real_new_pairs.size());
  for (const auto& pair : current_pairs)
  {
    // using old_values to get updated annotation texts, if any.
    merged_pairs.push_back(std::pair<QString, QString>(pair.first, old_values[pair.first]));
  }
  for (const auto& pair : real_new_pairs)
  {
    merged_pairs.push_back(pair);
  }

  return merged_pairs;
}

//-----------------------------------------------------------------------------
// Custom proxy model used to apply global modifications (when editing the data from
// the header of the table) to the sorted items only.
class ColorAnnotationsFilterProxyModel : public QSortFilterProxyModel
{
public:
  explicit ColorAnnotationsFilterProxyModel(QObject* parent = nullptr)
    : QSortFilterProxyModel(parent)
  {
  }

  // Overriden to update the global checkbox based on filtered elements only.
  QVariant headerData(int section, Qt::Orientation orientation, int role) const override
  {
    pqAnnotationsModel* sourceModel = qobject_cast<pqAnnotationsModel*>(this->sourceModel());
    if (!sourceModel)
    {
      return false;
    }

    // Return the global visibility value based on the filtered element values only
    if (orientation == Qt::Horizontal && role == Qt::CheckStateRole &&
      section == pqAnnotationsModel::VISIBILITY)
    {
      // No items
      if (this->rowCount() == 0)
      {
        return Qt::Unchecked;
      }

      int currentCheckState = this->data(this->index(0, section), role).toInt();
      for (int row = 1; row < this->rowCount(); row++)
      {
        if (this->data(this->index(row, section), role).toInt() != currentCheckState)
        {
          currentCheckState = Qt::PartiallyChecked;
          break;
        }
      }
      return static_cast<Qt::CheckState>(currentCheckState);
    }

    // Forward to the source model in all other cases
    return sourceModel->headerData(section, orientation, role);
  }

  // Overriden in order to set the visibility / opacity values on the filtered elements only.
  bool setHeaderData(
    int section, Qt::Orientation orientation, const QVariant& value, int role) override
  {
    pqAnnotationsModel* sourceModel = qobject_cast<pqAnnotationsModel*>(this->sourceModel());
    if (!sourceModel)
    {
      return false;
    }

    if (orientation == Qt::Horizontal)
    {
      // Change the visibility values on filtered items only
      if (role == Qt::CheckStateRole && section == pqAnnotationsModel::VISIBILITY)
      {
        for (int row = 0; row < this->rowCount(); row++)
        {
          this->setData(this->index(row, section), value, role);
        }
        Q_EMIT this->headerDataChanged(orientation, section, section);
        return true;
      }
      // Change the opacity values on filtered items only
      else if (role == Qt::EditRole && section == pqAnnotationsModel::OPACITY)
      {
        sourceModel->setGlobalOpacity(value.toDouble());

        for (int row = 0; row < this->rowCount(); row++)
        {
          this->setData(this->index(row, section), value, role);
        }
        Q_EMIT this->headerDataChanged(orientation, section, section);
        return true;
      }
    }

    // Forward the call to the source model in all other cases
    return sourceModel->setHeaderData(section, orientation, value, role);
  }

  bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override
  {
    QModelIndex domainIndex =
      sourceModel()->index(sourceRow, pqAnnotationsModel::VISIBILITY, sourceParent);
    return sourceModel()->data(domainIndex, Qt::UserRole).toBool() &&
      QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
  }
};

//-----------------------------------------------------------------------------
// Dialog to set opacity value(s)
class pqOpacityRangeDialog : public QDialog
{
  // Enable the use of the tr() method in a class without Q_OBJECT macro
  Q_DECLARE_TR_FUNCTIONS(pqOpacityRangeDialog)
public:
  pqOpacityRangeDialog(double initialValue = 1.0, QWidget* parent = nullptr)
    : QDialog(parent)
  {
    this->OpacityWidget = new pqDoubleRangeWidget(this);
    this->OpacityWidget->setMinimum(0.0);
    this->OpacityWidget->setMaximum(1.0);
    this->OpacityWidget->setValue(initialValue);

    QDialogButtonBox* buttonBox =
      new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
    connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
    connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));

    QHBoxLayout* widgetLayout = new QHBoxLayout;
    widgetLayout->addWidget(new QLabel(tr("Opacity value :"), this));
    widgetLayout->addWidget(this->OpacityWidget);
    QVBoxLayout* layout_ = new QVBoxLayout;
    layout_->addLayout(widgetLayout);
    layout_->addWidget(buttonBox);
    this->setLayout(layout_);

    // 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));
  }

  ~pqOpacityRangeDialog() override = default;

  double opacity() const { return this->OpacityWidget->value(); }

private:
  pqDoubleRangeWidget* OpacityWidget;
};

//-----------------------------------------------------------------------------
// Custom pqTreeViewHelper used to set opacity values on selected items.
class pqColorAnnotationsSelectionHelper : public pqTreeViewSelectionHelper
{
  // Enable the use of the tr() method in a class without Q_OBJECT macro
  Q_DECLARE_TR_FUNCTIONS(pqColorAnnotationsSelectionHelper)
public:
  explicit pqColorAnnotationsSelectionHelper(QAbstractItemView* view)
    : pqTreeViewSelectionHelper(view, false)
  {
  }

protected:
  void execOpacityDialog()
  {
    pqOpacityRangeDialog dialog(1.0, nullptr);
    dialog.setWindowTitle(tr("Set opacity of highlited items"));
    dialog.move(QCursor::pos());
    if (dialog.exec() == QDialog::Accepted)
    {
      // Set the opacity on selected elements
      QAbstractItemModel* model = this->TreeView->model();
      QItemSelectionModel* select = this->TreeView->selectionModel();
      for (QModelIndex idx : select->selectedRows(pqAnnotationsModel::OPACITY))
      {
        model->setData(idx, dialog.opacity(), Qt::EditRole);
      }
    }
  }

  void buildupMenu(QMenu& menu, int section, const QPoint& pos) override
  {
    pqTreeViewSelectionHelper::buildupMenu(menu, section, pos);

    if (section == pqAnnotationsModel::OPACITY)
    {
      // Add a menu section to set the opacity of the selected rows
      if (!menu.isEmpty())
      {
        menu.addSeparator();
      }
      if (auto actn = menu.addAction(tr("Set opacity of highlighted items")))
      {
        QObject::connect(actn, &QAction::triggered, [this](bool) { this->execOpacityDialog(); });
      }
    }
  }
};
}

//=============================================================================
class pqColorAnnotationsWidget::pqInternals
{
public:
  Ui::ColorAnnotationsWidget Ui;
  pqAnnotationsModel* Model;
  QScopedPointer<QAction> TempAction;
  QScopedPointer<pqChooseColorPresetReaction> ChoosePresetReaction;
  vtkSmartPointer<vtkSMTransferFunctionProxy> LookupTableProxy;
  std::string CurrentPresetName;

  void SetCurrentPresetName(std::string newName) { this->CurrentPresetName = newName; }

  pqInternals(pqColorAnnotationsWidget* self)
    : TempAction(new QAction(self))
    , ChoosePresetReaction(new pqChooseColorPresetReaction(this->TempAction.data(), false))
  {
    this->SetCurrentPresetName("");
    this->Ui.setupUi(self);
    this->Ui.gridLayout->setContentsMargins(pqPropertiesPanel::suggestedMargin(),
      pqPropertiesPanel::suggestedMargin(), pqPropertiesPanel::suggestedMargin(),
      pqPropertiesPanel::suggestedMargin());
    this->Ui.gridLayout->setVerticalSpacing(pqPropertiesPanel::suggestedVerticalSpacing());
    this->Ui.gridLayout->setHorizontalSpacing(pqPropertiesPanel::suggestedHorizontalSpacing());
    this->Ui.verticalLayout->setContentsMargins(pqPropertiesPanel::suggestedMargin(),
      pqPropertiesPanel::suggestedMargin(), pqPropertiesPanel::suggestedMargin(),
      pqPropertiesPanel::suggestedMargin());
    this->Ui.verticalLayout->setSpacing(pqPropertiesPanel::suggestedVerticalSpacing());

    this->Model = new pqAnnotationsModel(self);
    this->Ui.AnnotationsTable->setModel(this->Model);

    pqHeaderView* myheader = new pqHeaderView(Qt::Horizontal, this->Ui.AnnotationsTable);
    myheader->setToggleCheckStateOnSectionClick(true);
    this->Ui.AnnotationsTable->setHorizontalHeader(myheader);

    this->Ui.AnnotationsTable->horizontalHeader()->setHighlightSections(false);
    this->Ui.AnnotationsTable->horizontalHeader()->setSectionResizeMode(
      QHeaderView::ResizeToContents);
    this->Ui.AnnotationsTable->horizontalHeader()->setStretchLastSection(true);

    QObject::connect(this->ChoosePresetReaction.data(), SIGNAL(presetApplied(const QString&)), self,
      SLOT(onPresetApplied(const QString&)));
  }

  // updates annotations model with values provided. If extend is true, existing
  // values will be extended otherwise old values will be replaced.
  // Returns true if annotations changed otherwise returns false.
  bool updateAnnotations(vtkAbstractArray* values, bool extend = true);
};

//-----------------------------------------------------------------------------
bool pqColorAnnotationsWidget::pqInternals::updateAnnotations(vtkAbstractArray* values, bool extend)
{
  std::vector<std::pair<QString, QString>> candidate_tuples;
  for (vtkIdType idx = 0; idx < values->GetNumberOfTuples(); idx++)
  {
    const auto val = values->GetVariantValue(idx);
    if (val.IsDouble())
    {
      candidate_tuples.push_back(std::make_pair(
        pqCoreUtilities::number(val.ToDouble()), pqCoreUtilities::formatNumber(val.ToDouble())));
    }
    else if (val.IsFloat())
    {
      candidate_tuples.push_back(std::make_pair(
        pqCoreUtilities::number(val.ToFloat()), pqCoreUtilities::formatNumber(val.ToFloat())));
    }
    else
    {
      auto str = val.ToString();
      candidate_tuples.push_back(std::pair<QString, QString>(str.c_str(), str.c_str()));
    }
  }

  // Combined annotation values (old and new)
  const auto& current_value_annotation_tuples = this->Model->annotations();
  auto merged_value_annotation_tuples =
    extend ? MergeAnnotations(current_value_annotation_tuples, candidate_tuples) : candidate_tuples;
  if (current_value_annotation_tuples != merged_value_annotation_tuples)
  {
    this->Model->setAnnotations(merged_value_annotation_tuples);
    return true;
  }

  return false;
}

//=============================================================================
pqColorAnnotationsWidget::pqColorAnnotationsWidget(QWidget* parentObject)
  : Superclass(parentObject)
  , Internals()
{
  this->Internals = new pqInternals(this);

  // Hookup UI buttons.
  Ui::ColorAnnotationsWidget& ui = this->Internals->Ui;
  QObject::connect(ui.Add, SIGNAL(clicked()), this, SLOT(addAnnotation()));
  QObject::connect(ui.AddActive, SIGNAL(clicked()), this, SLOT(addActiveAnnotations()));
  QObject::connect(ui.AddActiveFromVisible, SIGNAL(clicked()), this,
    SLOT(addActiveAnnotationsFromVisibleSources()));
  QObject::connect(ui.Remove, SIGNAL(clicked()), this, SLOT(removeAnnotation()));
  QObject::connect(ui.DeleteAll, SIGNAL(clicked()), this, SLOT(removeAllAnnotations()));
  QObject::connect(ui.ChoosePreset, &QToolButton::clicked, this,
    [&]() { choosePreset(this->Internals->CurrentPresetName.c_str()); });
  QObject::connect(
    ui.SaveAsNewPreset, &QToolButton::clicked, this, [&]() { this->saveAsNewPreset(); });
  QObject::connect(ui.SaveAsPreset, &QToolButton::clicked, this,
    [&]() { this->saveAsPreset(this->Internals->CurrentPresetName.c_str(), false, true); });

  QObject::connect(this->Internals->Model,
    SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this,
    SLOT(onDataChanged(const QModelIndex&, const QModelIndex&)));
  QObject::connect(ui.AnnotationsTable, SIGNAL(doubleClicked(const QModelIndex&)), this,
    SLOT(onDoubleClicked(const QModelIndex&)));
  QObject::connect(ui.AnnotationsTable->horizontalHeader(), SIGNAL(sectionDoubleClicked(int)), this,
    SLOT(onHeaderDoubleClicked(int)));
  QObject::connect(ui.AnnotationsTable, SIGNAL(editPastLastRow()), this, SLOT(editPastLastRow()));
  ui.AnnotationsTable->setContextMenuPolicy(Qt::CustomContextMenu);

  this->connect(ui.AnnotationsTable->selectionModel(),
    SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this,
    SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)));

  // Opacity mapping enable/disable mechanism
  this->connect(
    ui.EnableOpacityMapping, SIGNAL(stateChanged(int)), SLOT(updateOpacityColumnState()));
  this->connect(ui.EnableOpacityMapping, SIGNAL(clicked()), SIGNAL(opacityMappingChanged()));

  this->updateOpacityColumnState();

  this->setSupportsReorder(false);

  ColorAnnotationsFilterProxyModel* proxyModel = new ColorAnnotationsFilterProxyModel(this);
  proxyModel->setSourceModel(this->Internals->Model);
  ui.AnnotationsTable->setModel(proxyModel);
  ui.AnnotationsTable->setSortingEnabled(false);

  new pqColorAnnotationsSelectionHelper(ui.AnnotationsTable);
}

//-----------------------------------------------------------------------------
pqColorAnnotationsWidget::~pqColorAnnotationsWidget()
{
  delete this->Internals;
  this->Internals = nullptr;
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setLookupTableProxy(vtkSMProxy* proxy)
{
  this->Internals->LookupTableProxy = vtkSMTransferFunctionProxy::SafeDownCast(proxy);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::applyPreset(const char* presetName)
{
  auto presets = vtkSMTransferFunctionPresets::GetInstance();
  const Json::Value& preset = presets->GetFirstPresetWithName(presetName);
  const Json::Value& indexedColors = preset[INDEXED_COLORS];
  if (indexedColors.isNull() || !indexedColors.isArray() || (indexedColors.size() % 3) != 0 ||
    indexedColors.empty())
  {
    QString warningMessage = "Could not use " + QString(presetName) + " (size " +
      QString::number(indexedColors.size()) + ")" +
      " preset as default preset. Preset may not be valid. Please "
      "validate this preset";
    qWarning("%s", warningMessage.toStdString().c_str());
  }
  else
  {
    this->Internals->SetCurrentPresetName(presetName);
    QList<QVariant> defaultPresetVariant;
    for (Json::ArrayIndex cc = 0; cc < indexedColors.size(); cc++)
    {
      defaultPresetVariant.append(indexedColors[cc].asDouble());
    }

    this->setIndexedColors(defaultPresetVariant);
  }
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::indexedLookupStateUpdated(bool indexed)
{
  this->setColumnVisibility(pqAnnotationsModel::COLOR, indexed);
  this->Internals->Ui.ChoosePreset->setVisible(indexed);
  this->Internals->Ui.SaveAsPreset->setVisible(indexed);
  this->Internals->Ui.SaveAsNewPreset->setVisible(indexed);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::updateOpacityColumnState()
{
  Ui::ColorAnnotationsWidget& ui = this->Internals->Ui;
  this->setColumnVisibility(pqAnnotationsModel::OPACITY, ui.EnableOpacityMapping->isChecked());
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::onDataChanged(
  const QModelIndex& topleft, const QModelIndex& btmright)
{
  if (topleft.column() <= pqAnnotationsModel::VISIBILITY)
  {
    Q_EMIT this->visibilitiesChanged();
  }
  if (topleft.column() == pqAnnotationsModel::COLOR)
  {
    Q_EMIT this->indexedColorsChanged();
  }
  if (topleft.column() == pqAnnotationsModel::OPACITY)
  {
    Q_EMIT this->indexedOpacitiesChanged();
  }
  if (btmright.column() >= pqAnnotationsModel::VALUE)
  {
    Q_EMIT this->annotationsChanged();
  }
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::onHeaderDoubleClicked(int index)
{
  if (index == pqAnnotationsModel::OPACITY)
  {
    this->execGlobalOpacityDialog();
  }
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::execGlobalOpacityDialog()
{
  QAbstractItemModel* model = this->Internals->Ui.AnnotationsTable->model();
  if (!model)
  {
    return;
  }
  double globalOpacity =
    model->headerData(pqAnnotationsModel::OPACITY, Qt::Horizontal, Qt::EditRole).toDouble();
  pqOpacityRangeDialog dialog(globalOpacity, this);
  dialog.setWindowTitle(tr("Set global opacity"));
  dialog.move(QCursor::pos());
  if (dialog.exec() == QDialog::Accepted)
  {
    // Set the global opacity
    model->setHeaderData(
      pqAnnotationsModel::OPACITY, Qt::Horizontal, dialog.opacity(), Qt::EditRole);
  }
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::onDoubleClicked(const QModelIndex& index)
{
  auto model = this->Internals->Ui.AnnotationsTable->model();
  auto proxyModel = dynamic_cast<QSortFilterProxyModel*>(model);
  auto idx = proxyModel ? proxyModel->mapToSource(index) : index;

  if (idx.column() == pqAnnotationsModel::COLOR)
  {
    QColor color = this->Internals->Model->data(idx, Qt::EditRole).value<QColor>();
    color = QColorDialog::getColor(
      color, this, tr("Choose Annotation Color"), QColorDialog::DontUseNativeDialog);
    if (color.isValid())
    {
      this->Internals->Model->setData(idx, color);
    }
  }
  if (idx.column() == pqAnnotationsModel::OPACITY)
  {
    double opacity = this->Internals->Model->data(idx, Qt::EditRole).toDouble();
    pqDoubleRangeDialog dialog(tr("Opacity:"), 0.0, 1.0, this);
    dialog.setWindowTitle(tr("Select Opacity"));
    dialog.move(QCursor::pos());
    dialog.setValue(opacity);
    if (dialog.exec() == QDialog::Accepted)
    {
      this->Internals->Model->setData(idx, dialog.value());
    }
  }
}

//-----------------------------------------------------------------------------
QList<QVariant> pqColorAnnotationsWidget::annotations() const
{
  const auto& values = this->Internals->Model->annotations();

  QList<QVariant> reply;
  for (const auto& val : values)
  {
    reply.push_back(val.first);
    reply.push_back(val.second);
  }
  return reply;
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setAnnotations(const QList<QVariant>& value)
{
  std::vector<std::pair<QString, QString>> annotationsData;
  annotationsData.reserve(value.size() / 2);

  for (int cc = 0; (cc + 1) < value.size(); cc += 2)
  {
    annotationsData.push_back(
      std::pair<QString, QString>(value[cc].toString(), value[cc + 1].toString()));
  }
  this->Internals->Model->setAnnotations(annotationsData);

  Q_EMIT this->annotationsChanged();
}

//-----------------------------------------------------------------------------
QList<QVariant> pqColorAnnotationsWidget::indexedColors() const
{
  QList<QVariant> reply;
  std::vector<QColor> colors = this->Internals->Model->indexedColors();
  for (const QColor& color : colors)
  {
    reply.push_back(color.redF());
    reply.push_back(color.greenF());
    reply.push_back(color.blueF());
  }
  return reply;
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setIndexedColors(const QList<QVariant>& value)
{
  int nbEntry = value.size() / 3;
  std::vector<QColor> colors;
  colors.resize(nbEntry);

  for (int cc = 0; cc < nbEntry; cc++)
  {
    QColor color;
    color.setRgbF(
      value[cc * 3].toDouble(), value[cc * 3 + 1].toDouble(), value[cc * 3 + 2].toDouble());
    colors[cc] = color;
  }

  this->Internals->Model->setIndexedColors(colors);
  Q_EMIT this->indexedColorsChanged();
}

//-----------------------------------------------------------------------------
QList<QVariant> pqColorAnnotationsWidget::visibilities() const
{
  QList<QVariant> reply;
  auto visibilities = this->Internals->Model->visibilities();
  std::sort(visibilities.begin(), visibilities.end(),
    [](std::pair<QString, int> a, std::pair<QString, int> b) {
      return a.first.compare(b.first, Qt::CaseInsensitive) < 0;
    });
  for (const auto& vis : visibilities)
  {
    reply.push_back(vis.first);
    reply.push_back(vis.second == Qt::Checked ? "1" : "0");
  }
  return reply;
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setVisibilities(const QList<QVariant>& values)
{
  std::vector<std::pair<QString, int>> visibilities;

  for (int cc = 0; (cc + 1) < values.size(); cc += 2)
  {
    visibilities.push_back(std::make_pair(
      values[cc].toString(), values[cc + 1].toString() == "1" ? Qt::Checked : Qt::Unchecked));
  }

  this->Internals->Model->setVisibilities(visibilities);
  Q_EMIT this->visibilitiesChanged();
}

//-----------------------------------------------------------------------------
QList<QVariant> pqColorAnnotationsWidget::indexedOpacities() const
{
  QList<QVariant> reply;
  std::vector<double> opacities = this->Internals->Model->indexedOpacities();
  for (const auto& opac : opacities)
  {
    reply.push_back(opac);
  }
  return reply;
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setIndexedOpacities(const QList<QVariant>& values)
{
  std::vector<double> opacities;
  opacities.reserve(values.size());
  for (const auto& val : values)
  {
    opacities.push_back(val.toDouble());
  }
  this->Internals->Model->setIndexedOpacities(opacities);
  Q_EMIT this->indexedOpacitiesChanged();
}

//-----------------------------------------------------------------------------
QVariant pqColorAnnotationsWidget::opacityMapping() const
{
  return this->Internals->Ui.EnableOpacityMapping->isChecked() ? 1 : 0;
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setOpacityMapping(const QVariant& value)
{
  this->Internals->Ui.EnableOpacityMapping->setChecked(value.toInt() == 1);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::addAnnotation()
{
  auto& internals = (*this->Internals);
  QModelIndex idx = internals.Model->addAnnotation(internals.Ui.AnnotationsTable->currentIndex());
  // now select the newly added item.
  internals.Ui.AnnotationsTable->selectionModel()->setCurrentIndex(
    idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
  Q_EMIT this->annotationsChanged();
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::editPastLastRow()
{
  this->Internals->Model->addAnnotation(this->Internals->Ui.AnnotationsTable->currentIndex());
  Q_EMIT this->annotationsChanged();
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::removeAnnotation()
{
  auto& internals = (*this->Internals);
  QModelIndexList indexes = internals.Ui.AnnotationsTable->selectionModel()->selectedIndexes();
  if (indexes.empty())
  {
    // Nothing selected. Nothing to remove
    return;
  }
  QModelIndex idx = internals.Model->removeAnnotations(indexes);
  internals.Ui.AnnotationsTable->selectionModel()->setCurrentIndex(
    idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
  Q_EMIT this->annotationsChanged();
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::addActiveAnnotations()
{
  if (!this->addActiveAnnotations(false))
  {
    QString warningTitle(tr("Could not determine discrete values"));
    QString warningMessage;
    QTextStream qs(&warningMessage);
    qs << tr("Could not automatically determine annotation values. Usually this means "
             "too many discrete values (more than %1) "
             "are available in the data produced by the "
             "current source/filter.")
            .arg(vtkAbstractArray::MAX_DISCRETE_VALUES)
       << tr(" This can happen if the data array type is floating "
             "point. Please add annotations manually or force generation. Forcing the "
             "generation will automatically hide the Scalar Bar.");
    QMessageBox* box = new QMessageBox(this);
    box->setWindowTitle(warningTitle);
    box->setText(warningMessage);
    box->addButton(tr("Force"), QMessageBox::AcceptRole);
    box->addButton(QMessageBox::Cancel);
    if (box->exec() != QMessageBox::Cancel)
    {
      QAction action(nullptr);
      pqScalarBarVisibilityReaction reaction(&action);
      reaction.setScalarBarVisibility(false);

      if (!this->addActiveAnnotations(true))
      {
        QMessageBox::warning(this, warningTitle,
          tr("Could not force generation of discrete values using the data "
             "produced by the current source/filter. Please add annotations "
             "manually."),
          QMessageBox::Ok);
      }
    }
  }
}

//-----------------------------------------------------------------------------
bool pqColorAnnotationsWidget::addActiveAnnotations(bool force)
{
  // obtain prominent values from the server and add them
  pqDataRepresentation* repr = pqActiveObjects::instance().activeRepresentation();
  if (!repr)
  {
    return false;
  }

  vtkPVProminentValuesInformation* info =
    vtkSMPVRepresentationProxy::GetProminentValuesInformationForColorArray(
      repr->getProxy(), 1e-3, 1e-6, force);
  if (!info || !info->GetValid())
  {
    return false;
  }

  int component_no = -1;
  if (QString("Component") ==
    vtkSMPropertyHelper(this->Internals->LookupTableProxy, "VectorMode", true).GetAsString())
  {
    component_no =
      vtkSMPropertyHelper(this->Internals->LookupTableProxy, "VectorComponent").GetAsInt();
  }
  if (component_no == -1 && info->GetNumberOfComponents() == 1)
  {
    component_no = 0;
  }

  vtkSmartPointer<vtkAbstractArray> uniqueValues;
  uniqueValues.TakeReference(info->GetProminentComponentValues(component_no));
  if (uniqueValues == nullptr)
  {
    return false;
  }

  // Set the merged annotations
  auto& internals = (*this->Internals);
  if (internals.updateAnnotations(uniqueValues, true))
  {
    Q_EMIT this->annotationsChanged();
  }
  return true;
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::addActiveAnnotationsFromVisibleSources()
{
  if (!this->addActiveAnnotationsFromVisibleSources(false))
  {
    QString warningTitle(tr("Could not determine discrete values"));
    QString warningMessage;
    QTextStream qs(&warningMessage);
    qs << tr("Could not automatically determine annotation values. Usually this means "
             "too many discrete values (more than %1) "
             "are available in the data produced by the "
             "current source/filter.")
            .arg(vtkAbstractArray::MAX_DISCRETE_VALUES)
       << tr(" This can happen if the data array type is floating "
             "point. Please add annotations manually or force generation. Forcing the "
             "generation will automatically hide the Scalar Bar.");

    QMessageBox* box = new QMessageBox(this);
    box->setWindowTitle(warningTitle);
    box->setText(warningMessage);
    box->addButton(tr("Force"), QMessageBox::AcceptRole);
    box->addButton(QMessageBox::Cancel);
    if (box->exec() != QMessageBox::Cancel)
    {
      QAction action(nullptr);
      pqScalarBarVisibilityReaction reaction(&action);
      reaction.setScalarBarVisibility(false);

      if (!this->addActiveAnnotationsFromVisibleSources(true))
      {
        QMessageBox::warning(this, warningTitle,
          tr("Could not force generation of discrete values using the data "
             "produced by the current source/filter. Please add annotations "
             "manually."),
          QMessageBox::Ok);
      }
    }
  }
}

//-----------------------------------------------------------------------------
bool pqColorAnnotationsWidget::addActiveAnnotationsFromVisibleSources(bool force)
{
  pqServer* server = pqActiveObjects::instance().activeServer();
  if (!server)
  {
    return false;
  }

  // obtain prominent values from all visible sources colored by the same
  // array name as the name of color array for the active representation.
  pqDataRepresentation* repr = pqActiveObjects::instance().activeRepresentation();
  if (!repr)
  {
    return false;
  }

  vtkSMPVRepresentationProxy* activeRepresentationProxy =
    vtkSMPVRepresentationProxy::SafeDownCast(repr->getProxy());
  if (!activeRepresentationProxy)
  {
    return false;
  }

  vtkPVArrayInformation* activeArrayInfo =
    vtkSMPVRepresentationProxy::GetArrayInformationForColorArray(activeRepresentationProxy);

  vtkSMSessionProxyManager* pxm = server->proxyManager();

  // Iterate over representations, collecting prominent values from each.
  std::set<vtkVariant> uniqueAnnotations;
  vtkSmartPointer<vtkCollection> collection = vtkSmartPointer<vtkCollection>::New();
  pxm->GetProxies("representations", collection);
  bool missingValues = false;
  for (int i = 0; i < collection->GetNumberOfItems(); ++i)
  {
    vtkSMProxy* representationProxy = vtkSMProxy::SafeDownCast(collection->GetItemAsObject(i));
    if (!representationProxy || !vtkSMPropertyHelper(representationProxy, "Visibility").GetAsInt())
    {
      continue;
    }

    vtkPVArrayInformation* currentArrayInfo =
      vtkSMPVRepresentationProxy::GetArrayInformationForColorArray(representationProxy);
    if (!activeArrayInfo || !activeArrayInfo->GetName() || !currentArrayInfo ||
      !currentArrayInfo->GetName() ||
      strcmp(activeArrayInfo->GetName(), currentArrayInfo->GetName()) != 0)
    {
      continue;
    }

    vtkPVProminentValuesInformation* info =
      vtkSMPVRepresentationProxy::GetProminentValuesInformationForColorArray(
        representationProxy, 1e-3, 1e-6, force);
    if (!info)
    {
      continue;
    }

    int component_no = -1;
    if (QString("Component") ==
      vtkSMPropertyHelper(this->Internals->LookupTableProxy, "VectorMode", true).GetAsString())
    {
      component_no =
        vtkSMPropertyHelper(this->Internals->LookupTableProxy, "VectorComponent").GetAsInt();
    }
    if (component_no == -1 && info->GetNumberOfComponents() == 1)
    {
      component_no = 0;
    }

    vtkSmartPointer<vtkAbstractArray> uniqueValues;
    uniqueValues.TakeReference(info->GetProminentComponentValues(component_no));
    if (uniqueValues == nullptr)
    {
      missingValues = true;
      continue;
    }
    for (vtkIdType idx = 0, max = uniqueValues->GetNumberOfTuples(); idx < max; ++idx)
    {
      uniqueAnnotations.insert(uniqueValues->GetVariantValue(idx));
    }
  }

  vtkNew<vtkVariantArray> uniqueAnnotationsArray;
  uniqueAnnotationsArray->Allocate(static_cast<vtkIdType>(uniqueAnnotations.size()));
  for (const vtkVariant& val : uniqueAnnotations)
  {
    uniqueAnnotationsArray->InsertNextValue(val);
  }

  auto& internals = (*this->Internals);
  if (internals.updateAnnotations(uniqueAnnotationsArray, true))
  {
    Q_EMIT this->annotationsChanged();
  }

  return !missingValues;
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::removeAllAnnotations()
{
  this->Internals->Model->removeAllAnnotations();
  this->Internals->Model->setHeaderData(
    pqAnnotationsModel::OPACITY, Qt::Horizontal, 1.0, Qt::EditRole);
  Q_EMIT this->annotationsChanged();
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::choosePreset(const char* presetName)
{
  this->Internals->ChoosePresetReaction->setTransferFunction(this->Internals->LookupTableProxy);
  this->Internals->ChoosePresetReaction->choosePreset(presetName);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::saveAsNewPreset()
{
  QDialog dialog(this);
  Ui::SavePresetOptions ui;
  ui.setupUi(&dialog);
  ui.saveOpacities->setVisible(false);
  auto name =
    this->Internals->Model->headerData(pqAnnotationsModel::LABEL, Qt::Horizontal, Qt::DisplayRole)
      .toString();
  ui.saveAnnotations->setText(QString(tr("Save %1s")).arg(name));

  // For now, let's not provide an option to not save colors. We'll need to fix
  // the pqPresetToPixmap to support rendering only opacities.
  ui.saveColors->setChecked(true);
  ui.saveColors->setEnabled(false);
  ui.saveColors->hide();
  ui.presetName->setText(tr("Preset"));

  if (dialog.exec() != QDialog::Accepted)
  {
    return;
  }

  this->saveAsPreset(
    ui.presetName->text().toUtf8().data(), !ui.saveAnnotations->isChecked(), false);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::saveAsPreset(
  const char* defaultName, bool removeAnnotations, bool allowOverride)
{
  Json::Value cpreset =
    vtkSMTransferFunctionProxy::GetStateAsPreset(this->Internals->LookupTableProxy);

  if (cpreset[INDEXED_COLORS].empty())
  {
    qWarning("Cannot save an empty preset");
    return;
  }

  // Sanity check
  Json::Value nullValue;
  Json::Value colors = cpreset.get(INDEXED_COLORS, nullValue);
  Json::Value annotations = cpreset.get(ANNOTATIONS, nullValue);
  if (annotations.size() / 2 != colors.size() / 3)
  {
    qWarning("Preset has unexpected size");
    return;
  }

  // Keep only rows visible in the widget
  Json::Value cleanAnnotations;
  Json::Value cleanColors;
  auto table = this->Internals->Ui.AnnotationsTable;
  Json::ArrayIndex idxAnno, idxColor;
  for (idxAnno = idxColor = 0; idxAnno < annotations.size(); idxAnno += 2, idxColor += 3)
  {
    std::string serieName = annotations[idxAnno].asString();
    for (int j = 0; j < table->model()->rowCount(); j++)
    {
      auto idx = table->model()->index(j, pqAnnotationsModel::VALUE);
      QString annotation = table->model()->data(idx).toString();
      if (serieName == annotation.toStdString() &&
        table->model()
          ->data(table->model()->index(j, pqAnnotationsModel::VISIBILITY), Qt::UserRole)
          .toBool())
      {
        cleanColors.append(colors.get(idxColor, nullValue));
        cleanColors.append(colors.get(idxColor + 1, nullValue));
        cleanColors.append(colors.get(idxColor + 2, nullValue));

        if (!removeAnnotations)
        {
          cleanAnnotations.append(annotations.get(idxAnno, nullValue));
          cleanAnnotations.append(annotations.get(idxAnno + 1, nullValue));
        }
        break;
      }
    }
  }

  cpreset[INDEXED_COLORS] = cleanColors;
  if (!removeAnnotations)
  {
    cpreset[ANNOTATIONS] = cleanAnnotations;
  }
  else
  {
    cpreset.removeMember(ANNOTATIONS);
  }

  std::string presetName = defaultName;
  {
    // This scoping is necessary to ensure that the vtkSMTransferFunctionPresets
    // saves the new preset to the "settings" before the choosePreset dialog is
    // shown.
    auto presets = vtkSMTransferFunctionPresets::GetInstance();
    if (!allowOverride || !presets->SetPreset(presetName.c_str(), cpreset))
    {
      presetName = presets->AddUniquePreset(cpreset, presetName.c_str());
    }
  }

  this->applyPreset(presetName.c_str());
  this->onPresetApplied(QString(presetName.c_str()));
  this->choosePreset(presetName.c_str());
}

//-----------------------------------------------------------------------------
QModelIndex pqColorAnnotationsWidget::currentIndex()
{
  auto table = this->Internals->Ui.AnnotationsTable;
  auto idx = table->currentIndex();

  const auto proxyModel = qobject_cast<const QSortFilterProxyModel*>(this->Internals->Model);
  idx = proxyModel ? proxyModel->mapToSource(idx) : idx;

  return idx.isValid() && table->selectionModel()->isRowSelected(idx.row(), idx.parent())
    ? idx
    : QModelIndex();
}

//-----------------------------------------------------------------------------
QString pqColorAnnotationsWidget::currentAnnotationValue()
{
  auto idx = this->currentIndex();
  QModelIndex valueIdx = idx.sibling(idx.row(), pqAnnotationsModel::VALUE);
  auto value = valueIdx.data();
  return value.toString();
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setSelectedAnnotations(const QStringList& annotations)
{
  auto table = this->Internals->Ui.AnnotationsTable;
  auto prevSelection = table->selectionModel()->selection();
  table->selectionModel()->clearSelection();

  for (int i = 0; i < table->model()->rowCount(); i++)
  {
    auto idx = table->model()->index(i, pqAnnotationsModel::VALUE);
    if (annotations.contains(idx.data().toString()))
    {
      table->selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::Select);
      table->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Rows);
    }
  }

  auto currentSelection = table->selectionModel()->selection();
  Q_EMIT this->selectionChanged(currentSelection, prevSelection);
}

//-----------------------------------------------------------------------------
QStringList pqColorAnnotationsWidget::selectedAnnotations()
{
  auto selection = this->selectedIndexes();
  QStringList selectedAnnotations;
  for (auto idx : selection)
  {
    QModelIndex valueIdx = idx.sibling(idx.row(), pqAnnotationsModel::VALUE);
    selectedAnnotations << valueIdx.data().toString();
  }

  selectedAnnotations.removeDuplicates();

  return selectedAnnotations;
}

//-----------------------------------------------------------------------------
QModelIndexList pqColorAnnotationsWidget::selectedIndexes()
{
  return this->Internals->Ui.AnnotationsTable->selectionModel()->selectedIndexes();
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setAnnotationsModel(pqAnnotationsModel* model)
{
  delete this->Internals->Model;

  auto previousModel = this->Internals->Ui.AnnotationsTable->model();
  auto proxyModel = dynamic_cast<QSortFilterProxyModel*>(previousModel);
  this->Internals->Model = model;
  if (proxyModel)
  {
    proxyModel->setSourceModel(this->Internals->Model);
  }
  else
  {
    this->Internals->Ui.AnnotationsTable->setModel(this->Internals->Model);
  }

  QObject::connect(this->Internals->Model,
    SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this,
    SLOT(onDataChanged(const QModelIndex&, const QModelIndex&)));

  this->connect(this->Internals->Ui.AnnotationsTable->selectionModel(),
    SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this,
    SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)));
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setSupportsReorder(bool reorder)
{
  if (!reorder)
  {
    this->Internals->Ui.AnnotationsTable->sortByColumn(
      pqAnnotationsModel::VALUE, Qt::AscendingOrder);
  }

  this->Internals->Model->setSupportsReorder(reorder);

  this->Internals->Ui.AnnotationsTable->setDragEnabled(reorder);
  this->Internals->Ui.AnnotationsTable->setDragDropMode(
    reorder ? QAbstractItemView::InternalMove : QAbstractItemView::NoDragDrop);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::sort(int column, Qt::SortOrder order)
{
  this->Internals->Ui.AnnotationsTable->model()->sort(column, order);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::allowsRegexpMatching(bool allow)
{
  this->Internals->ChoosePresetReaction->setAllowsRegexpMatching(allow);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::allowsUserDefinedValues(bool allow)
{
  Ui::ColorAnnotationsWidget& ui = this->Internals->Ui;
  ui.Add->setVisible(allow);
  ui.Remove->setVisible(allow);
  ui.AddActive->setVisible(allow);
  ui.AddActiveFromVisible->setVisible(allow);
  ui.DeleteAll->setVisible(allow);
  if (!allow)
  {
    QObject::disconnect(
      ui.AnnotationsTable, SIGNAL(editPastLastRow()), this, SLOT(editPastLastRow()));
  }
  else
  {
    QObject::connect(ui.AnnotationsTable, SIGNAL(editPastLastRow()), this, SLOT(editPastLastRow()));
  }
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::enablePresets(bool enable)
{
  Ui::ColorAnnotationsWidget& ui = this->Internals->Ui;
  ui.SaveAsPreset->setVisible(enable);
  ui.SaveAsNewPreset->setVisible(enable);
  ui.ChoosePreset->setVisible(enable);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::supportsOpacityMapping(bool val)
{
  Ui::ColorAnnotationsWidget& ui = this->Internals->Ui;
  ui.EnableOpacityMapping->setVisible(val);
  ui.EnableOpacityMapping->setChecked(val);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::onPresetApplied(const QString& name)
{
  this->Internals->SetCurrentPresetName(name.toStdString());
  Q_EMIT this->presetChanged(name);
}

//-----------------------------------------------------------------------------
const char* pqColorAnnotationsWidget::currentPresetName()
{
  return this->Internals->CurrentPresetName.c_str();
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setCurrentPresetName(const char* name)
{
  this->Internals->SetCurrentPresetName(name);
}

//-----------------------------------------------------------------------------
void pqColorAnnotationsWidget::setColumnVisibility(
  pqAnnotationsModel::ColumnRoles col, bool visible)
{
  this->Internals->Ui.AnnotationsTable->setColumnHidden(col, !visible);
}

//-----------------------------------------------------------------------------
bool pqColorAnnotationsWidget::presetLoadAnnotations()
{
  return this->Internals->ChoosePresetReaction->loadAnnotations();
}

//-----------------------------------------------------------------------------
QRegularExpression pqColorAnnotationsWidget::presetRegularExpression()
{
  return this->Internals->ChoosePresetReaction->regularExpression();
}
