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

  Program:   ParaView
  Module:    pqMultiBlockInspectorPanel.h

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "pqMultiBlockInspectorPanel.h"

#include "pqActiveObjects.h"
#include "pqDoubleRangeDialog.h"
#include "pqOutputPort.h"
#include "pqSelectionManager.h"
#include "pqTreeWidgetSelectionHelper.h"
#include "pqUndoStack.h"

#include "vtkEventQtSlotConnect.h"
#include "vtkDiscretizableColorTransferFunction.h"
#include "vtkNew.h"
#include "vtkPiecewiseFunction.h"
#include "vtkPVCompositeDataInformation.h"
#include "vtkPVDataInformation.h"
#include "vtkSMDoubleMapProperty.h"
#include "vtkSMDoubleMapPropertyIterator.h"
#include "vtkSMIntVectorProperty.h"
#include "vtkSMProperty.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxy.h"
#include "vtkSMProxyManager.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMStringVectorProperty.h"
#include "vtkSMTransferFunctionManager.h"
#include "vtkSelection.h"

#include <QColorDialog>
#include <QHeaderView>
#include <QIcon>
#include <QMenu>
#include <QPainter>
#include <QPixmap>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>

#define FLAT_INDEX_ROLE Qt::UserRole
#define LEAF_INDEX_ROLE (Qt::UserRole+1)


namespace
{
enum ValueType
{
  SYSTEM_VALUE,
  USER_VALUE
};

enum Column
{
  NAME_COLUMN,
  COLOR_COLUMN,
  OPACITY_COLUMN,
  COLUMN_COUNT
};

const int ICON_SIZE = 16;
const int BETWEEN_SIZE = 3;
void drawColorIcon(QPainter& painter, QColor& color,
                   ValueType valueType = SYSTEM_VALUE)
{
  painter.setPen(QPen(Qt::NoPen));
  if (valueType == USER_VALUE)
    {
    painter.setBrush(Qt::black);
    painter.drawRect(0, 0, ICON_SIZE, ICON_SIZE);
    }
  painter.setBrush(color);
  painter.drawEllipse(BETWEEN_SIZE,
                      BETWEEN_SIZE,
                      ICON_SIZE - 2*BETWEEN_SIZE,
                      ICON_SIZE - 2*BETWEEN_SIZE);
}

void drawNullIcon(QPainter& painter)
{
  painter.setPen(Qt::black);
  painter.setBrush(Qt::white);
  painter.drawEllipse(BETWEEN_SIZE, BETWEEN_SIZE, 
                      ICON_SIZE - 2*BETWEEN_SIZE, ICON_SIZE - 2*BETWEEN_SIZE);
  painter.drawLine(
    ICON_SIZE - BETWEEN_SIZE - 1, BETWEEN_SIZE, 
    BETWEEN_SIZE, ICON_SIZE - BETWEEN_SIZE - 1);
}


void drawOpacityIcon(QPainter& painter, double opacity,
                     ValueType valueType = SYSTEM_VALUE)
{
  if (valueType == USER_VALUE)
    {
    painter.setPen(Qt::black);
    painter.setBrush(Qt::black);
    painter.drawRect(0, 0, ICON_SIZE, ICON_SIZE);
    }
  painter.setPen(Qt::black);
  int angle = 5760 * opacity;
  painter.setBrush(Qt::white);
  painter.drawEllipse(BETWEEN_SIZE, BETWEEN_SIZE, 
                      ICON_SIZE - 2*BETWEEN_SIZE, ICON_SIZE - 2*BETWEEN_SIZE);
  painter.setBrush(Qt::lightGray);
  painter.drawPie(BETWEEN_SIZE, BETWEEN_SIZE, 
                  ICON_SIZE - 2*BETWEEN_SIZE,
                  ICON_SIZE - 2*BETWEEN_SIZE, 0, angle);
}


};

pqMultiBlockInspectorPanel::pqMultiBlockInspectorPanel(QWidget *parent_)
  : QWidget(parent_), ColorTransferProxy(NULL),
    ColorTransferFunction(NULL), OpacityTransferFunction(NULL),
    BlockColorsDistinctValues(7), CompositeWrap(0)
{
  // setup tree widget
  this->TreeWidget = new QTreeWidget(this);
  this->TreeWidget->setColumnCount(COLUMN_COUNT);
  this->TreeWidget->setExpandsOnDoubleClick(false);
  this->TreeWidget->header()->close();

  // create tree widget selection helper
  new pqTreeWidgetSelectionHelper(this->TreeWidget);

  // disconnect from selection helper's context menu so that
  // we can use our own menu with multi-block support
  this->TreeWidget->disconnect(
    SIGNAL(customContextMenuRequested(const QPoint&)), 0, 0);

  this->connect(this->TreeWidget, SIGNAL(itemSelectionChanged()),
                this, SLOT(onItemSelectionChanged()));

  this->PropertyListener = vtkEventQtSlotConnect::New();

  QVBoxLayout *layout_ = new QVBoxLayout;
  layout_->addWidget(this->TreeWidget);
  setLayout(layout_);

  // listen to active object changes
  pqActiveObjects *activeObjects = &pqActiveObjects::instance();
  this->connect(activeObjects, SIGNAL(portChanged(pqOutputPort*)),
                this, SLOT(onPortChanged(pqOutputPort*)));
  this->connect(activeObjects, SIGNAL(representationChanged(pqRepresentation*)),
                this, SLOT(onRepresentationChanged(pqRepresentation*)));

  // listen to selection changes
  pqSelectionManager *selectionManager =
    qobject_cast<pqSelectionManager*>(
      pqApplicationCore::instance()->manager("SelectionManager"));
  if(selectionManager)
    {
    this->connect(selectionManager, SIGNAL(selectionChanged(pqOutputPort*)),
                  this, SLOT(onSelectionChanged(pqOutputPort*)));
    }

  // connect to right-click signals in the tree widget
  this->TreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
  this->connect(this->TreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
                this, SLOT(onCustomContextMenuRequested(QPoint)));
  this->connect(this->TreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)),
                this, SLOT(onItemChanged(QTreeWidgetItem*, int)));

  // setup double-click for colors and opacity
  this->connect(this->TreeWidget, 
                SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)),
                this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int)));

  //  UpdateUITimer helps use collapse updates to the UI whenever the SM
  //  properties change.
  this->UpdateUITimer.setSingleShot(true);
  this->UpdateUITimer.setInterval(0);
  this->connect(&this->UpdateUITimer, SIGNAL(timeout()),
                this, SLOT(updateTree()));
}

pqMultiBlockInspectorPanel::~pqMultiBlockInspectorPanel()
{
  this->PropertyListener->Delete();
}

void pqMultiBlockInspectorPanel::onPortChanged(pqOutputPort *port)
{
  if(this->OutputPort == port)
    {
    return;
    }

  if(this->OutputPort)
    {
    QObject::disconnect(this->OutputPort->getSource(),
                        SIGNAL(dataUpdated(pqPipelineSource*)),
                        this,
                        SLOT(onDataUpdated()));
    }

  this->OutputPort = port;

  if(this->OutputPort)
    {
    QObject::connect(this->OutputPort->getSource(),
                     SIGNAL(dataUpdated(pqPipelineSource*)),
                     this,
                     SLOT(onDataUpdated()));
    }

  this->onDataUpdated();
}

pqOutputPort* pqMultiBlockInspectorPanel::getOutputPort() const
{
  return this->OutputPort.data();
}

void pqMultiBlockInspectorPanel::onRepresentationChanged(
  pqRepresentation *representation)
{
  if(this->Representation == representation)
    {
    return;
    }

  // disconnect from previous representation
  this->PropertyListener->Disconnect();

  this->Representation = representation;

  if(this->Representation)
    {
    // update properties
    this->onDataUpdated();

    // listen to property changes
    vtkSMProxy *proxy = this->Representation->getProxy();
    vtkSMProperty *visibilityProperty = proxy->GetProperty("BlockVisibility");
    if(visibilityProperty)
      {
      this->PropertyListener->Connect(visibilityProperty,
                                      vtkCommand::ModifiedEvent,
                                      &this->UpdateUITimer,
                                      SLOT(start()));
      }

    vtkSMProperty *colorProperty = proxy->GetProperty("BlockColors");
    if(colorProperty)
      {
      this->PropertyListener->Connect(colorProperty,
                                      vtkCommand::ModifiedEvent,
                                      &this->UpdateUITimer,
                                      SLOT(start()));
      }
    vtkSMProperty* colorArrayNameProperty = 
      proxy->GetProperty("ColorArrayName");
    if(colorArrayNameProperty)
      {
      this->PropertyListener->Connect(colorArrayNameProperty,
                                      vtkCommand::ModifiedEvent,
                                      this,
                                      SLOT(onColorArrayNameModified()));
      }
    vtkSMProperty* compositeWrapDistinctValuesProperty = 
      proxy->GetProperty("BlockColorsDistinctValues");
    if(compositeWrapDistinctValuesProperty)
      {
      this->PropertyListener->Connect(compositeWrapDistinctValuesProperty,
                                      vtkCommand::ModifiedEvent,
                                      &this->UpdateUITimer,
                                      SLOT(start()));
      }
    onColorArrayNameModified();
    }
  else
    {
    this->BlockVisibilites.clear();
    this->BlockColors.clear();
    this->TreeWidget->blockSignals(true);
    this->TreeWidget->clear();
    this->TreeWidget->blockSignals(false);
    }
}

void pqMultiBlockInspectorPanel::buildTree(vtkPVCompositeDataInformation *info,
                                           QTreeWidgetItem *parent_,
                                           int& flatIndex,
                                           int& leafIndex)
{
  parent_->setTextAlignment(COLOR_COLUMN, Qt::AlignHCenter | Qt::AlignVCenter);
  parent_->setTextAlignment(
    OPACITY_COLUMN, Qt::AlignHCenter | Qt::AlignVCenter);
  for(unsigned int i = 0; i < info->GetNumberOfChildren(); i++)
    {
    vtkPVDataInformation *childInfo = info->GetDataInformation(i);
    const char *childName = info->GetName(i);

    QString text;
    if(childName && childName[0])
      {
      text = childName;
      }
    else
      {
      text = QString("Block #%2").arg(flatIndex);
      }

    QTreeWidgetItem *item = new QTreeWidgetItem(parent_, QStringList() << text);
    item->setData(NAME_COLUMN, FLAT_INDEX_ROLE, flatIndex);
    item->setData(NAME_COLUMN, Qt::CheckStateRole, Qt::Checked);

    flatIndex++;

    if (childInfo)
      {
      vtkPVCompositeDataInformation *compositeChildInfo =
        childInfo->GetCompositeDataInformation();

      // recurse down through child blocks only if the child block
      // is composite and is not a multi-piece data set
      if(compositeChildInfo->GetDataIsComposite() &&
         !compositeChildInfo->GetDataIsMultiPiece())
        {
        this->buildTree(compositeChildInfo, item, flatIndex, leafIndex);
        }
      if (!compositeChildInfo->GetDataIsComposite())
        {
        item->setData(NAME_COLUMN, LEAF_INDEX_ROLE, leafIndex);
        leafIndex++;
        }
      }
    else
      {
      item->setDisabled(true);
      leafIndex++;
      }
    }
}

void pqMultiBlockInspectorPanel::onDataUpdated()
{
  // clear previous information
  this->TreeWidget->blockSignals(true);
  this->TreeWidget->clear();
  this->TreeWidget->blockSignals(false);

  if(!this->OutputPort)
    {
    return;
    }

  // update information
  pqPipelineSource *source = this->OutputPort->getSource();
  vtkPVDataInformation *info = this->OutputPort->getDataInformation();

  if(!source || !info)
    {
    return;
    }

  vtkPVCompositeDataInformation *compositeInfo =
    info->GetCompositeDataInformation();

  if(compositeInfo->GetDataIsComposite())
    {
    this->TreeWidget->blockSignals(true);
    int flatIndex = 0;
    int leafIndex = 0;

    // create root item
    QString rootLabel = source->getSMName();
    QTreeWidgetItem *rootItem =
      new QTreeWidgetItem(this->TreeWidget->invisibleRootItem(),
                          QStringList() << rootLabel);
    rootItem->setData(NAME_COLUMN, FLAT_INDEX_ROLE, flatIndex++);
    rootItem->setData(NAME_COLUMN, Qt::CheckStateRole, Qt::Checked);

    // build the rest of the tree
    this->buildTree(compositeInfo, rootItem, flatIndex, leafIndex);
    this->TreeWidget->expandAll();
    this->TreeWidget->resizeColumnToContents(NAME_COLUMN);


    // expand root item
    this->TreeWidget->expandItem(rootItem);

    // update visibilities
    this->updateTree();
    this->TreeWidget->resizeColumnToContents(COLOR_COLUMN);
    this->TreeWidget->resizeColumnToContents(OPACITY_COLUMN);

    this->TreeWidget->blockSignals(false);
    }
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::setBlockVisibility(
  unsigned int index, bool visible)
{
  QList<unsigned int> indices;
  indices.push_back(index);
  this->setBlockVisibility(indices, visible);
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::clearBlockVisibility(unsigned int index)
{
  QList<unsigned int> indices;
  indices.push_back(index);
  this->clearBlockVisibility(indices);
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::setBlockVisibility(
  const QList<unsigned int>& indices, bool visible)
{
  foreach(const unsigned int &index, indices)
    {
    this->BlockVisibilites[index] = visible;
    }

  this->updateBlockVisibilities();
  this->Representation->renderViewEventually();
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::clearBlockVisibility(
  const QList<unsigned int>& indices)
{
  foreach(const unsigned int &index, indices)
    {
    this->BlockVisibilites.remove(index);
    }

  this->updateBlockVisibilities();
  this->Representation->renderViewEventually();
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::setBlockColor(
  unsigned int index, const QColor &color)
{
  QList<unsigned int> indices;
  indices.push_back(index);
  this->setBlockColor(indices, color);
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::setBlockColor(
  const QList<unsigned int>& indices, const QColor &color)
{
  foreach(const unsigned int &index, indices)
    {
    this->BlockColors[index] = color;
    }

  this->updateBlockColors();
  this->Representation->renderViewEventually();
}


//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::clearBlockColor(unsigned int index)
{
  QList<unsigned int> indices;
  indices.push_back(index);
  this->clearBlockColor(indices);
}


//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::clearBlockColor(
  const QList<unsigned int>& indices)
{
  foreach(const unsigned int &index, indices)
    {
    this->BlockColors.remove(index);
    }
  this->updateBlockColors();
  this->Representation->renderViewEventually();
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::setBlockOpacity(
  unsigned int index, double opacity)
{
  QList<unsigned int> indices;
  indices.push_back(index);
  this->setBlockOpacity(indices, opacity);
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::setBlockOpacity(
  const QList<unsigned int>& indices, double opacity)
{
  foreach(const unsigned int &index, indices)
    {
    this->BlockOpacities[index] = opacity;
    }
  this->updateBlockOpacities();
  this->Representation->renderViewEventually();
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::clearBlockOpacity(unsigned int index)
{
  QList<unsigned int> indices;
  indices.push_back(index);
  this->clearBlockOpacity(indices);
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::clearBlockOpacity(
  const QList<unsigned int>& indices)
{
  foreach(const unsigned int &index, indices)
    {
    this->BlockOpacities.remove(index);
    }

  this->updateBlockOpacities();
  this->Representation->renderViewEventually();
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::promptAndSetBlockOpacity(unsigned int index)
{
  QList<unsigned int> list;
  list.append(index);
  this->promptAndSetBlockOpacity(list);
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::promptAndSetBlockOpacity(
  const QList<unsigned int> &indices)
{
  if(indices.isEmpty())
    {
    return;
    }

  double current_opacity = this->BlockOpacities.value(indices[0], 1.0);
  pqDoubleRangeDialog dialog("Opacity:", 0.0, 1.0, this);
  dialog.setValue(current_opacity);
  bool ok = dialog.exec();
  if(ok)
    {
    this->setBlockOpacity(indices, dialog.value());
    }
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::showOnlyBlock(unsigned int index)
{
  QList<unsigned int> indices;
  indices.push_back(index);
  this->showOnlyBlocks(indices);
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::showAllBlocks()
{
  this->BlockVisibilites.clear();
  this->BlockVisibilites[0] = true; // show root block
  this->updateBlockVisibilities();
  this->Representation->renderViewEventually();
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::showOnlyBlocks(
  const QList<unsigned int>& indices)
{
  this->BlockVisibilites.clear();
  this->BlockVisibilites[0] = false; // hide root block
  foreach (const unsigned int &index, indices)
    {
    this->BlockVisibilites[index] = true;
    }
  this->updateBlockVisibilities();
  this->Representation->renderViewEventually();
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::updateBlockVisibilities()
{
  std::vector<int> vector;

  for(
    QMap<unsigned int, bool>::const_iterator i = this->BlockVisibilites.begin();
    i != this->BlockVisibilites.end(); i++)
    {
    vector.push_back(static_cast<int>(i.key()));
    vector.push_back(static_cast<int>(i.value()));
    }

  // update vtk property
  vtkSMProxy *proxy = this->Representation->getProxy();
  vtkSMProperty *property_ = proxy->GetProperty("BlockVisibility");

  if(property_)
    {
    BEGIN_UNDO_SET("Change Block Visibilities");
    vtkSMIntVectorProperty *ivp =
      vtkSMIntVectorProperty::SafeDownCast(property_);
    if(!vector.empty())
      {
      // if property changes, ModifiedEvent will be fired and
      // this->UpdateUITimer will be started.
      ivp->SetElements(&vector[0], static_cast<unsigned int>(vector.size()));
      }
    proxy->UpdateVTKObjects();
    END_UNDO_SET();
    }
}

//-----------------------------------------------------------------------------
void pqMultiBlockInspectorPanel::updateBlockColors()
{
  // update vtk property
  vtkSMProxy *proxy = this->Representation->getProxy();
  vtkSMProperty *property_ = proxy->GetProperty("BlockColor");

  if(property_)
    {
    BEGIN_UNDO_SET("Change Block Colors");
    vtkSMDoubleMapProperty *dmp =
      vtkSMDoubleMapProperty::SafeDownCast(property_);

    // if property changes, ModifiedEvent will be fired and
    // this->UpdateUITimer will be started.
    dmp->ClearElements();

    QMap<unsigned int, QColor>::const_iterator iter;
    for(iter = this->BlockColors.begin();
        iter != this->BlockColors.end();
        iter++)
      {
      QColor qcolor = iter.value();
      double color[] = { qcolor.redF(), qcolor.greenF(), qcolor.blueF() };
      dmp->SetElements(iter.key(), color);
      }

    proxy->UpdateVTKObjects();
    END_UNDO_SET();
    }

  this->updateTree();
}

void pqMultiBlockInspectorPanel::updateBlockOpacities()
{
  // update vtk property
  vtkSMProxy *proxy = this->Representation->getProxy();
  vtkSMProperty *property_ = proxy->GetProperty("BlockOpacity");

  if(property_)
    {
    BEGIN_UNDO_SET("Change Block Opacities");
    vtkSMDoubleMapProperty *dmp =
      vtkSMDoubleMapProperty::SafeDownCast(property_);

    // if property changes, ModifiedEvent will be fired and
    // this->UpdateUITimer will be started.
    dmp->ClearElements();

    QMap<unsigned int, double>::const_iterator iter;
    for(iter = this->BlockOpacities.begin();
        iter != this->BlockOpacities.end();
        iter++)
      {
      dmp->SetElement(iter.key(), iter.value());
      }

    proxy->UpdateVTKObjects();
    END_UNDO_SET();
    }

  this->updateTree();
}

void pqMultiBlockInspectorPanel::updateTree()
{
  if(!this->Representation || !this->OutputPort)
    {
    return;
    }

  // get the array name we color by
  vtkSMProxy *representationProxy = this->Representation->getProxy();
  vtkSMPropertyHelper colorArrayHelper(representationProxy, "ColorArrayName",
                                       true);
  const char* arrayName = colorArrayHelper.GetInputArrayNameToProcess();
  if (! arrayName)
    {
    return;
    }
  this->ColorTransferFunction = NULL;
  this->OpacityTransferFunction = NULL;
  // field name setup in vtkPVGeometryFilter to contain the current block index
  bool isComposite = ! strcmp(arrayName, "vtkCompositeIndex");
  bool isCompositeWrap = ! strcmp(arrayName, "vtkBlockColors");
  this->CompositeWrap = isCompositeWrap;
  if (isComposite || isCompositeWrap)
    {
    // get color/opacity transfer function
    vtkSMSessionProxyManager* activeSessionProxyManager = 
      vtkSMProxyManager::GetProxyManager()->GetActiveSessionProxyManager();
    vtkNew<vtkSMTransferFunctionManager> transferFunctionManager;
    vtkSMProxy *colorTransferProxy = 
      transferFunctionManager->GetColorTransferFunction(
        arrayName, activeSessionProxyManager);
    this->ColorTransferFunction =
      vtkDiscretizableColorTransferFunction::SafeDownCast(
        colorTransferProxy->GetClientSideObject());
    if (this->ColorTransferFunction->GetEnableOpacityMapping())
      {
      this->OpacityTransferFunction = 
        this->ColorTransferFunction->GetScalarOpacityFunction();
      }
    }

  // update BlockVisibility map from vtk property
  vtkSMProperty *blockVisibilityProperty = 
    representationProxy->GetProperty("BlockVisibility");
  vtkSMIntVectorProperty *ivp = 
    vtkSMIntVectorProperty::SafeDownCast(blockVisibilityProperty);
  this->BlockVisibilites.clear();

  if(ivp)
    {
    vtkIdType nbElems = static_cast<vtkIdType>(ivp->GetNumberOfElements());
    for(vtkIdType i = 0; i + 1 < nbElems; i += 2)
      {
      this->BlockVisibilites[ivp->GetElement(i)] = ivp->GetElement(i+1);
      }
    }

  // update BlockColor map from vtk property
  vtkSMProperty *blockColorProperty = 
    representationProxy->GetProperty("BlockColor");
  vtkSMDoubleMapProperty *dmp = 
    vtkSMDoubleMapProperty::SafeDownCast(blockColorProperty);
  this->BlockColors.clear();

  // update BlockColorsDistinctValues
  vtkSMProperty *distinctValuesProperty = 
    representationProxy->GetProperty("BlockColorsDistinctValues");
  ivp = vtkSMIntVectorProperty::SafeDownCast(distinctValuesProperty);
  this->BlockColorsDistinctValues = ivp ? ivp->GetElement(0) : 2;

  if(dmp)
    {
    vtkSMDoubleMapPropertyIterator *iter = dmp->NewIterator();
    for(iter->Begin(); !iter->IsAtEnd(); iter->Next())
      {
      QColor color;
      color.setRedF(iter->GetElementComponent(0));
      color.setGreenF(iter->GetElementComponent(1));
      color.setBlueF(iter->GetElementComponent(2));
      this->BlockColors[iter->GetKey()] = color;
      }
    iter->Delete();
    }

  // update BlockOpacity map from vtk property
  vtkSMProperty *blockOpacityProperty = 
    representationProxy->GetProperty("BlockOpacity");
  dmp = vtkSMDoubleMapProperty::SafeDownCast(blockOpacityProperty);
  this->BlockOpacities.clear();

  if(dmp)
    {
    vtkSMDoubleMapPropertyIterator *iter = dmp->NewIterator();
    for(iter->Begin(); !iter->IsAtEnd(); iter->Next())
      {
      this->BlockOpacities[iter->GetKey()] = iter->GetElementComponent(0);
      }
    iter->Delete();
    }

  // update ui
  pqPipelineSource *source = this->OutputPort->getSource();
  vtkPVDataInformation *info = this->OutputPort->getDataInformation();

  if(!source || !info)
    {
    return;
    }

  vtkPVCompositeDataInformation *compositeInfo =
    info->GetCompositeDataInformation();

  if(compositeInfo->GetDataIsComposite())
    {
    this->TreeWidget->blockSignals(true);
    bool root_visibility = this->BlockVisibilites.value(0, true);

    // get root item and set its visibility
    QTreeWidgetItem *rootItem =
      this->TreeWidget->invisibleRootItem()->child(0);
    rootItem->setData(NAME_COLUMN,
                      Qt::CheckStateRole,
                      root_visibility ? Qt::Checked : Qt::Unchecked);
    rootItem->setData(COLOR_COLUMN, Qt::DecorationRole,
                      makeColorIcon(0, INTERNAL_NODE, -1));
    rootItem->setData(OPACITY_COLUMN, Qt::DecorationRole,
                      makeOpacityIcon(0, INTERNAL_NODE, -1));
    
    // recurse down the tree updating child visibilities
    int flatIndex = 0;
    this->updateTree(compositeInfo, rootItem, 
                     flatIndex, root_visibility,
                     this->BlockColors.contains(flatIndex) ? flatIndex : -1,
                     this->BlockOpacities.contains(flatIndex) ? flatIndex : -1);
    this->TreeWidget->blockSignals(false);
    }
}

void pqMultiBlockInspectorPanel::onColorArrayNameModified()
{
  // get the array name we color by
  vtkSMProxy *representationProxy = this->Representation->getProxy();
  vtkSMPropertyHelper colorArrayHelper(representationProxy, "ColorArrayName",
                                       true);
  const char* arrayName = colorArrayHelper.GetInputArrayNameToProcess();
  if (! arrayName)
    {
    return;
    }

  // field name setup in vtkPVGeometryFilter to contain the current block index
  bool isComposite = ! strcmp(arrayName, "vtkCompositeIndex");
  bool isCompositeWrap = ! strcmp(arrayName, "vtkBlockColors");
  if (isComposite || isCompositeWrap)
    {
    // get color/opacity transfer function
    vtkSMSessionProxyManager* activeSessionProxyManager = 
      vtkSMProxyManager::GetProxyManager()->GetActiveSessionProxyManager();
    vtkNew<vtkSMTransferFunctionManager> transferFunctionManager;
    vtkSMProxy* colorTransferProxy = 
      transferFunctionManager->GetColorTransferFunction(
        arrayName, activeSessionProxyManager);
    if (this->ColorTransferProxy)
      {
      this->PropertyListener->Disconnect(colorTransferProxy);
      }
    this->PropertyListener->Connect(colorTransferProxy,
                                    vtkCommand::ModifiedEvent,
                                    &this->UpdateUITimer,
                                    SLOT(start()));
    this->ColorTransferProxy = colorTransferProxy;
    }
  updateTree();
}



void pqMultiBlockInspectorPanel::updateTree(
  vtkPVCompositeDataInformation *info, QTreeWidgetItem *parent_,
  int& flatIndex, bool parentVisibility, int inheritedColorIndex,
  int inheritedOpacityIndex)
{
  for(unsigned int i = 0; i < info->GetNumberOfChildren(); i++)
    {
    QTreeWidgetItem *item = parent_->child(i);
    flatIndex++;

    bool visibility = parentVisibility;
    if(this->BlockVisibilites.contains(flatIndex))
      {
      visibility = this->BlockVisibilites[flatIndex];
      }

    vtkPVDataInformation *childInfo = info->GetDataInformation(i);
    vtkPVCompositeDataInformation *compositeChildInfo = NULL;
    NodeType nodeType = LEAF_NODE;
    if(childInfo)
      {
      compositeChildInfo = childInfo->GetCompositeDataInformation();

      // recurse down through child blocks only if the child block
      // is composite and is not a multi-piece data set
      if(compositeChildInfo->GetDataIsComposite() &&
         !compositeChildInfo->GetDataIsMultiPiece())
        {
        nodeType = INTERNAL_NODE;
        }
      }
    QVariant leafIndex = item->data(NAME_COLUMN, LEAF_INDEX_ROLE);
    item->setData(NAME_COLUMN, Qt::CheckStateRole, visibility ? 
                  Qt::Checked : Qt::Unchecked);
    item->setData(COLOR_COLUMN, Qt::DecorationRole, 
                  (item->isDisabled() ?
                   makeNullIcon() :
                   makeColorIcon(flatIndex, nodeType,
                                 inheritedColorIndex,
                                 leafIndex.isValid()? leafIndex.toInt() : -1)));
    item->setData(
      OPACITY_COLUMN, Qt::DecorationRole, 
      (item->isDisabled()? makeNullIcon() :
       makeOpacityIcon(flatIndex, nodeType, inheritedOpacityIndex)));


    if(nodeType == INTERNAL_NODE)
      {
      this->updateTree(
        compositeChildInfo, item, flatIndex, visibility,
        inheritedColorIndex != -1 ? inheritedColorIndex :
        (this->BlockColors.contains(flatIndex) ? flatIndex : -1),
        inheritedOpacityIndex != -1 ? inheritedOpacityIndex :
        (this->BlockOpacities.contains(flatIndex) ? flatIndex : -1));
      }
    }
}

void pqMultiBlockInspectorPanel::onCustomContextMenuRequested(
  const QPoint &)
{
  // selected items
  QList<QTreeWidgetItem*> items = this->TreeWidget->selectedItems();
  if(items.isEmpty())
    {
    return;
    }

  bool hasOverriddenVisibilities = false;
  bool hasOverridenColor = false;
  bool hasOverriddenOpacity = false;
  int hiddenItemCount = 0;
  foreach(const QTreeWidgetItem *item, items)
    {
    if(item->data(NAME_COLUMN, Qt::CheckStateRole).toBool() == false)
      {
      hiddenItemCount++;
      }

    unsigned int flat_index =
      item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();
    hasOverriddenVisibilities = hasOverriddenVisibilities || this->BlockVisibilites.contains(flat_index);
    hasOverridenColor = hasOverridenColor || this->BlockColors.contains(flat_index);
    hasOverriddenOpacity = hasOverriddenOpacity || this->BlockOpacities.contains(flat_index);
    }
  int visibleItemCount = items.size() - hiddenItemCount;

  QMenu menu;

  QAction *hideAction = 0;
  if(visibleItemCount > 0)
    {
    QString label = (visibleItemCount > 1)?
      QString("Hide %1 Blocks").arg(visibleItemCount) : QString("Hide Block");
    hideAction = menu.addAction(label);
    }
  QAction *showAction = 0;
  if(hiddenItemCount > 0)
    {
    QString label = (hiddenItemCount > 1)?
      QString("Show %1 Blocks").arg(hiddenItemCount) : QString("Show Block");
    showAction = menu.addAction(label);
    }

  QAction *unsetVisibilityAction = menu.addAction("Unset Visibility");
  unsetVisibilityAction->setEnabled(hasOverriddenVisibilities);

  menu.addSeparator();
  QAction *setColorAction = menu.addAction("Set Color...");
  QAction *unsetColorAction = menu.addAction("Unset Color");
  unsetColorAction->setEnabled(hasOverridenColor);

  menu.addSeparator();
  QAction *setOpacityAction = menu.addAction("Set Opacity...");
  QAction *unsetOpacityAction = menu.addAction("Unset Opacity");
  unsetOpacityAction->setEnabled(hasOverriddenOpacity);

  // show menu
  QAction *action = menu.exec(QCursor::pos());

  if(!action)
    {
    return;
    }
  else if(action == hideAction)
    {
    foreach(QTreeWidgetItem *item, items)
      {
      unsigned int flat_index =
        item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();

      // first unset any child item visibilites
      this->unsetChildVisibilities(item);

      this->setBlockVisibility(flat_index, false);
      item->setData(NAME_COLUMN, Qt::CheckStateRole, Qt::Unchecked);
      }
    }
  else if(action == showAction)
    {
    foreach(QTreeWidgetItem *item, items)
      {
      unsigned int flat_index =
        item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();

      // first unset any child item visibilites
      this->unsetChildVisibilities(item);

      this->setBlockVisibility(flat_index, true);
      item->setData(NAME_COLUMN, Qt::CheckStateRole, Qt::Checked);
      }
    }
  else if(action == unsetVisibilityAction)
    {
    foreach(QTreeWidgetItem *item, items)
      {
      unsigned int flat_index =
        item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();

      this->clearBlockVisibility(flat_index);
      item->setData(
        NAME_COLUMN, Qt::CheckStateRole,
        item->parent()?
        item->parent()->data(NAME_COLUMN, Qt::CheckStateRole):
        Qt::Checked);
      }
    }
  else if(action == setColorAction)
    {
    QColor color = QColorDialog::getColor(QColor(), this, "Select Color",
      QColorDialog::DontUseNativeDialog);
    if(color.isValid())
      {
      foreach(QTreeWidgetItem *item, items)
        {
        unsigned int flat_index =
          item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();
        this->setBlockColor(flat_index, color);
        }
      }
    }
  else if(action == unsetColorAction)
    {
    foreach(QTreeWidgetItem *item, items)
      {
      unsigned int flat_index =
        item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();
      this->clearBlockColor(flat_index);
      }
    }
  else if(action == setOpacityAction)
    {
    QList<unsigned int> indices;
    foreach(QTreeWidgetItem *item, items)
      {
      indices.append(
        item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>());
      }
    this->promptAndSetBlockOpacity(indices);
    }
  else if(action == unsetOpacityAction)
    {
    foreach(QTreeWidgetItem *item, items)
      {
      unsigned int flat_index =
        item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();

      this->clearBlockOpacity(flat_index);
      }
    }
}

void pqMultiBlockInspectorPanel::onItemDoubleClicked(
  QTreeWidgetItem * item, int column)
{
  unsigned int flatIndex =
    item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();
  // if (column == NAME_COLUMN)
  //   {
  //   QModelIndex mi(flatIndex, column);
  //   this->TreeWidget->isExpanded(mi) ? 
  //     this->TreeWidget->collapse(mi) : this->TreeWidget->expand(mi);
  //   }
  if (column == COLOR_COLUMN)
    {
    QColor color = QColorDialog::getColor(QColor(), this, "Select Color",
                                          QColorDialog::DontUseNativeDialog);
    if(color.isValid())
      {
      this->setBlockColor(flatIndex, color);
      }
    }
  else if (column == OPACITY_COLUMN)
    {
    this->promptAndSetBlockOpacity(flatIndex);
    }
}


void pqMultiBlockInspectorPanel::onItemChanged(
  QTreeWidgetItem *item, int column)
{
  Q_UNUSED(column);

  unsigned int flat_index = item->data(
    NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();
  bool visible = item->data(NAME_COLUMN, Qt::CheckStateRole).toBool();

  // first unset any child item visibilites
  this->unsetChildVisibilities(item);

  // set block visibility
  this->setBlockVisibility(flat_index, visible);
}


void pqMultiBlockInspectorPanel::unsetChildVisibilities(
  QTreeWidgetItem *parent_)
{
  for(int i = 0; i < parent_->childCount(); i++)
    {
    QTreeWidgetItem *child = parent_->child(i);
    unsigned int flatIndex = 
      child->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();
    this->BlockVisibilites.remove(flatIndex);
    unsetChildVisibilities(child);
    }
}

void pqMultiBlockInspectorPanel::onSelectionChanged(pqOutputPort *port)
{
  // find selected block ids
  std::vector<vtkIdType> block_ids;

  if(port)
    {
    vtkSMSourceProxy *activeSelection = port->getSelectionInput();
    if(activeSelection &&
       strcmp(activeSelection->GetXMLName(), "BlockSelectionSource") == 0)
      {
      vtkSMPropertyHelper blocksProp(activeSelection, "Blocks");
      block_ids.resize(blocksProp.GetNumberOfElements());
      if (block_ids.size() > 0)
        {
        blocksProp.Get(&block_ids[0], blocksProp.GetNumberOfElements());
        }
      }
    }

  // sort block ids so we can use binary_search
  std::sort(block_ids.begin(), block_ids.end());

  // update visibilities in the tree widget
  this->TreeWidget->blockSignals(true);

  foreach(QTreeWidgetItem *item,
          this->TreeWidget->findItems(
            "", Qt::MatchContains | Qt::MatchRecursive))
    {
    vtkIdType flatIndex =
      item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<vtkIdType>();

    item->setSelected(
      std::binary_search(block_ids.begin(), block_ids.end(), flatIndex));
    }

  this->TreeWidget->blockSignals(false);
}

void pqMultiBlockInspectorPanel::onItemSelectionChanged()
{
  // create vector of selected block ids
  std::vector<vtkIdType> blockIds;
  foreach(const QTreeWidgetItem *item, this->TreeWidget->selectedItems())
    {
    unsigned int flatIndex =
      item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();

    blockIds.push_back(flatIndex);
    }

  // create block selection source proxy
  vtkSMSessionProxyManager *proxyManager =
    vtkSMProxyManager::GetProxyManager()->GetActiveSessionProxyManager();

  vtkSMProxy* selectionSource =
    proxyManager->NewProxy("sources", "BlockSelectionSource");

  // set selected blocks
  if (blockIds.size() > 0)
    {
    vtkSMPropertyHelper(selectionSource, "Blocks")
      .Set(&blockIds[0], static_cast<unsigned int>(blockIds.size()));
    }
  else
    {
    vtkSMPropertyHelper(selectionSource, "Blocks").SetNumberOfElements(0);
    }
  selectionSource->UpdateVTKObjects();

  vtkSMSourceProxy *selectionSourceProxy =
    vtkSMSourceProxy::SafeDownCast(selectionSource);

  // set the selection
  if(this->OutputPort)
    {
    this->OutputPort->setSelectionInput(selectionSourceProxy, 0);
    }

  // update the selection manager
  pqSelectionManager *selectionManager =
    qobject_cast<pqSelectionManager*>(
      pqApplicationCore::instance()->manager("SelectionManager"));
  if(selectionManager)
    {
    selectionManager->select(this->OutputPort);
    }

  // delete the selection source
  selectionSourceProxy->Delete();

  // update the views
  if(this->OutputPort)
    {
    this->OutputPort->renderAllViews();
    }
}

QString pqMultiBlockInspectorPanel::lookupBlockName(
  unsigned int flatIndex) const
{
  foreach(
    QTreeWidgetItem *item,
    this->TreeWidget->findItems("", Qt::MatchContains | Qt::MatchRecursive))
    {
    unsigned int itemFlatIndex =
      item->data(NAME_COLUMN, FLAT_INDEX_ROLE).value<unsigned int>();

    if(itemFlatIndex == flatIndex)
      {
      return item->text(0);
      }
    }

  return QString();
}

QIcon pqMultiBlockInspectorPanel::makeNullIcon() const
{
  QPixmap pixmap(ICON_SIZE, ICON_SIZE);
  pixmap.fill(Qt::transparent);
  QPainter painter(&pixmap);
  drawNullIcon(painter);
  painter.end();
  QIcon icon(pixmap);
  return icon;
}

QIcon pqMultiBlockInspectorPanel::makeColorIcon(
  int flatIndex, NodeType nodeType, int inheritedColorIndex, int leafIndex/*=-1*/) const
{
  QPixmap pixmap(ICON_SIZE, ICON_SIZE);
  pixmap.fill(Qt::transparent);
  QPainter painter(&pixmap);

  int index = (inheritedColorIndex != -1) ? inheritedColorIndex : flatIndex;
  if(this->BlockColors.contains(index))
    {
    QColor color = this->BlockColors[index];
    drawColorIcon(painter, color, 
                  (index == flatIndex) ? USER_VALUE : SYSTEM_VALUE);
    }
  else if (this->ColorTransferFunction && nodeType == LEAF_NODE)
    {
    unsigned char* rgb = this->ColorTransferFunction->MapValue(
      CompositeWrap ? 
      (leafIndex % this->BlockColorsDistinctValues) : leafIndex);
    QColor color (rgb[0], rgb[1], rgb[2]);
    drawColorIcon(painter, color);
    }
  else
    {
    drawNullIcon(painter);
    }

  painter.end();
  QIcon icon(pixmap);
  return icon;
}

QIcon pqMultiBlockInspectorPanel::makeOpacityIcon(
  int flatIndex, NodeType nodeType, int inheritedOpacityIndex) const
{
  QPixmap pixmap(ICON_SIZE, ICON_SIZE);
  pixmap.fill(Qt::transparent);
  QPainter painter(&pixmap);

  int index = (inheritedOpacityIndex != -1) ? inheritedOpacityIndex : flatIndex;
  if(this->BlockOpacities.contains(index))
    {
    double opacity = this->BlockOpacities[index];
    drawOpacityIcon(painter, opacity, 
                    (index == flatIndex) ? USER_VALUE : SYSTEM_VALUE);
    }
  else if (this->OpacityTransferFunction && nodeType == LEAF_NODE)
    {
    double opacity = this->OpacityTransferFunction->GetValue(
      CompositeWrap ? 
      (flatIndex % this->BlockColorsDistinctValues) : flatIndex);
    drawOpacityIcon(painter, opacity);
    }
  else
    {
    drawNullIcon(painter);
    }

  painter.end();

  QIcon icon(pixmap);

  return icon;
}
