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

  Program: 3D Slicer

  Copyright (c) Laboratory for Percutaneous Surgery (PerkLab)
  Queen's University, Kingston, ON, Canada. All Rights Reserved.

  See COPYRIGHT.txt
  or http://www.slicer.org/copyright/copyright.txt for details.

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  This file was originally developed by Csaba Pinter, PerkLab, Queen's University
  and was supported through the Applied Cancer Research Unit program of Cancer Care
  Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care

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

// Terminologies includes
#include "qSlicerTerminologyNavigatorWidget.h"
#include "qMRMLSimpleColorTableView.h"
#include "qMRMLSortFilterColorProxyModel.h"

#include "ui_qSlicerTerminologyNavigatorWidget.h"

#include "vtkSlicerTerminologiesModuleLogic.h"

// Slicer includes
#include <qSlicerApplication.h>
#include <qSlicerModuleManager.h>
#include <qSlicerAbstractCoreModule.h>

// MRML includes
#include <vtkMRMLColorNode.h>
#include <vtkMRMLScene.h>

// MRMLWidgets includes
#include <qMRMLColorModel.h>

// VTK includes
#include <vtkSmartPointer.h>

// Qt includes
#include <QColor>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QItemSelection>
#include <QMessageBox>
#include <QSettings>
#include <QTableWidgetItem>
#include <QTimer>

//-----------------------------------------------------------------------------
// TerminologyInfoBundle methods

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::TerminologyInfoBundle()
{
  this->TerminologyEntry = vtkSlicerTerminologyEntry::New();
}

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::TerminologyInfoBundle(vtkSlicerTerminologyEntry* entry,
                                                                                QString name,
                                                                                bool nameAutoGenerated,
                                                                                QColor color,
                                                                                bool colorAutoGenerated,
                                                                                QColor generatedColor)
  : Name(name)
  , NameAutoGenerated(nameAutoGenerated)
  , Color(color)
  , ColorAutoGenerated(colorAutoGenerated)
  , GeneratedColor(generatedColor)
{
  this->TerminologyEntry = vtkSlicerTerminologyEntry::New();
  if (entry)
  {
    this->TerminologyEntry->Copy(entry);
  }
}

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::~TerminologyInfoBundle()
{
  if (this->TerminologyEntry)
  {
    this->TerminologyEntry->Delete();
    this->TerminologyEntry = nullptr;
  }
}

//-----------------------------------------------------------------------------
const qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle& qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::operator=(
  const qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle& other)
{
  this->TerminologyEntry->Copy(other.TerminologyEntry);
  this->Name = other.Name;
  this->NameAutoGenerated = other.NameAutoGenerated;
  this->Color = other.Color;
  this->ColorAutoGenerated = other.ColorAutoGenerated;
  this->GeneratedColor = other.GeneratedColor;
  return *this;
}

//-----------------------------------------------------------------------------
vtkSlicerTerminologyEntry* qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::GetTerminologyEntry()
{
  return this->TerminologyEntry;
}

//-----------------------------------------------------------------------------
// qSlicerTerminologyNavigatorWidgetPrivate methods

//-----------------------------------------------------------------------------
class qSlicerTerminologyNavigatorWidgetPrivate : public Ui_qSlicerTerminologyNavigatorWidget
{
  Q_DECLARE_PUBLIC(qSlicerTerminologyNavigatorWidget);

protected:
  qSlicerTerminologyNavigatorWidget* const q_ptr;

public:
  qSlicerTerminologyNavigatorWidgetPrivate(qSlicerTerminologyNavigatorWidget& object);
  ~qSlicerTerminologyNavigatorWidgetPrivate();
  void init();

  /// Get terminology module logic
  static vtkSlicerTerminologiesModuleLogic* terminologyLogic();

  /// Set name from current selection and set it to name text box
  void setNameFromCurrentTerminology();

  /// Set recommended color from current selection to color picker
  void setRecommendedColorFromCurrentTerminology();
  /// Get recommended color stored in terminology.
  /// Return invalid color if type is not selected or the selected type has modifiers but no modifier is selected.
  QColor terminologyRecommendedColor();
  /// Get recommended color for given type.
  /// If the type does not have a color but has modifiers, then the first valid color from the modifier is returned.
  /// If no color is found then an invalid color object is returned
  QColor recommendedColorForType(std::string terminologyName, vtkSlicerTerminologyCategory* category, vtkSlicerTerminologyType* type);

  /// Find item in category table widget corresponding to a given category
  QTableWidgetItem* findTableWidgetItemForCategory(vtkSlicerTerminologyCategory* category);
  /// Find item in (type or region) table widget corresponding to a given type
  QTableWidgetItem* findTableWidgetItemForType(QTableWidget* tableWidget, vtkSlicerTerminologyType* type);
  /// Find index in (type or region) modifier combobox corresponding to a given modifier
  /// \return -1 if not found
  int findComboBoxIndexForModifier(ctkComboBox* comboBox, vtkSlicerTerminologyType* modifier);

  /// Get last terminology context selected by the user that contains the currently set terminology entry
  bool findTerminology(vtkSlicerTerminologyEntry* entry, QString& terminologyName, QString& colorNodeId, int& colorIndexInColorTable);

  /// Set current terminology to widget
  void setCurrentTerminology(QString terminologyName, QString colorNodeID);
  /// Set current category to widget.
  /// Only used when setting the category from a given entry to the widget!
  /// \return Flag indicating whether the given category was found in the category table
  bool setCurrentCategory(vtkSlicerTerminologyCategory* category);
  /// Set current type to widget
  /// \return Flag indicating whether the given type was found in the type table
  bool setCurrentType(vtkSlicerTerminologyType* type);
  /// Set current type modifier to widget
  /// \return Flag indicating whether the given modifier was found in the combobox
  bool setCurrentTypeModifier(vtkSlicerTerminologyType* modifier);
  /// Set current region context to widget
  void setCurrentRegionContext(QString contextName);
  /// Set current region to widget
  /// \return Flag indicating whether the given region was found in the region table
  bool setCurrentRegion(vtkSlicerTerminologyType* region);
  /// Set current region modifier to widget
  /// \return Flag indicating whether the given modifier was found in the combobox
  bool setCurrentRegionModifier(vtkSlicerTerminologyType* modifier);

  /// Update widget UI based on the current category selection
  void updateWidgetFromCurrentCategory();

  /// Populate terminology combobox based on current selection
  void populateTerminologyComboBox();
  /// Populate category table based on selected terminology and category search term
  void populateCategoryTable();
  /// Populate type table based on selected category and type search term
  void populateTypeTable();
  /// Populate type modifier combobox based on current selection
  void populateTypeModifierComboBox();

  /// Populate region context combobox based on current selection
  void populateRegionContextComboBox();
  /// Populate region table based on selected region context and type search term
  void populateRegionTable();
  /// Populate region modifier combobox based on current selection
  void populateRegionModifierComboBox();

  /// Copy terminology or region context file to user folder
  void copyContextToUserDirectory(QString filePath);

public:
  /// Name (SegmentationCategoryTypeContextName) of the current terminology. Color node name if the current terminology is a color node.
  QString CurrentTerminologyName;
  /// Color node ID of the current terminology. Empty if the current terminology is not a color node.
  QString CurrentColorNodeID;

  /// Object containing the details of the current category.
  /// This is the category containing the current type. It does not reflect UI selection
  vtkSmartPointer<vtkSlicerTerminologyCategory> CurrentCategoryObject;
  /// Category objects of the currently selected categories in the UI
  /// Used for populating the type list when categories are selected
  QList<vtkSmartPointer<vtkSlicerTerminologyCategory>> SelectedCategoryObjects;
  /// Object containing the details of the current type
  vtkSmartPointer<vtkSlicerTerminologyType> CurrentTypeObject;
  /// Object containing the details of the current type modifier if any
  vtkSmartPointer<vtkSlicerTerminologyType> CurrentTypeModifierObject;

  /// Name (RegionContextName) of the current region context
  QString CurrentRegionContextName;

  /// Object containing the details of the current region
  vtkSmartPointer<vtkSlicerTerminologyType> CurrentRegionObject;
  /// Object containing the details of the current region modifier if any
  vtkSmartPointer<vtkSlicerTerminologyType> CurrentRegionModifierObject;

  /// Name of the "no selected type" item
  QString NoneItemName;

  /// Flag indicating whether name was automatically generated
  bool NameAutoGenerated{ true };
  /// Flag indicating whether color is the recommended color from the selected terminology
  bool ColorAutoGenerated{ true };
  /// Generated color that is used if there was no recommended color in selected terminology
  QColor GeneratedColor;

  /// Flag indicating whether terminology combobox is being populated.
  /// Used to omit certain operations when terminology selection is made when populating
  bool TerminologyComboboxPopulating{ false };
  /// Flag indicating whether region context combobox is being populated.
  /// Used to omit certain operations when region context selection is made when populating
  bool RegionContextComboboxPopulating{ false };

  /// Color table view
  qMRMLSimpleColorTableView* ColorTableView{ nullptr };
};

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidgetPrivate::qSlicerTerminologyNavigatorWidgetPrivate(qSlicerTerminologyNavigatorWidget& object)
  : q_ptr(&object)
  , NoneItemName(QString("[ ") + qSlicerTerminologyNavigatorWidget::tr("None") + QString(" ]"))
  , GeneratedColor(vtkSlicerTerminologyType::INVALID_COLOR[0], vtkSlicerTerminologyType::INVALID_COLOR[1], vtkSlicerTerminologyType::INVALID_COLOR[2])
{
  this->CurrentCategoryObject = vtkSmartPointer<vtkSlicerTerminologyCategory>::New();
  this->CurrentTypeObject = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  this->CurrentTypeModifierObject = vtkSmartPointer<vtkSlicerTerminologyType>::New();

  this->CurrentRegionObject = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  this->CurrentRegionModifierObject = vtkSmartPointer<vtkSlicerTerminologyType>::New();
}

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidgetPrivate::~qSlicerTerminologyNavigatorWidgetPrivate() {}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::init()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);
  this->setupUi(q);

  // Create color table
  this->ColorTableView = new qMRMLSimpleColorTableView(q);
  qMRMLSortFilterColorProxyModel* sortFilterModel = this->ColorTableView->sortFilterProxyModel();
  // sortFilterModel->setFilterKeyColumn(colorModel->labelColumn());
  // sortFilterModel->setFilterRegExp("^(?!.*none).*$"); // Do not show entries with empty terminology
  sortFilterModel->setShowEmptyColors(false);
  QHBoxLayout* colorTableLayout = new QHBoxLayout();
  colorTableLayout->addWidget(this->ColorTableView);
  this->frame_ColorTableView->setLayout(colorTableLayout);

  // Make connections
  QObject::connect(this->ComboBox_Terminology, SIGNAL(currentIndexChanged(int)), q, SLOT(onTerminologySelectionChanged(int)));
  QObject::connect(this->ComboBox_Terminology_2, SIGNAL(currentIndexChanged(int)), q, SLOT(onTerminologySelectionChanged(int)));
  QObject::connect(this->tableWidget_Category, SIGNAL(itemSelectionChanged()), q, SLOT(onCategorySelectionChanged()));
  QObject::connect(this->pushButton_SelectAllCategories, SIGNAL(clicked()), q, SLOT(onSelectAllCategoriesButtonClicked()));
  QObject::connect(this->tableWidget_Type, SIGNAL(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)), q, SLOT(onTypeSelected(QTableWidgetItem*, QTableWidgetItem*)));
  QObject::connect(this->tableWidget_Type, SIGNAL(cellDoubleClicked(int, int)), q, SLOT(onTypeCellDoubleClicked(int, int)));
  QObject::connect(this->ComboBox_TypeModifier, SIGNAL(currentIndexChanged(int)), q, SLOT(onTypeModifierSelectionChanged(int)));
  QObject::connect(this->SearchBox_Category, SIGNAL(textChanged(QString)), q, SLOT(onCategorySearchTextChanged(QString)));
  QObject::connect(this->SearchBox_Type, SIGNAL(textChanged(QString)), q, SLOT(onTypeSearchTextChanged(QString)));

  QObject::connect(this->ComboBox_RegionContext, SIGNAL(currentIndexChanged(int)), q, SLOT(onRegionContextSelectionChanged(int)));
  QObject::connect(this->tableWidget_Region, SIGNAL(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)), q, SLOT(onRegionSelected(QTableWidgetItem*, QTableWidgetItem*)));
  QObject::connect(this->ComboBox_RegionModifier, SIGNAL(currentIndexChanged(int)), q, SLOT(onRegionModifierSelectionChanged(int)));
  QObject::connect(this->SearchBox_Region, SIGNAL(textChanged(QString)), q, SLOT(onRegionSearchTextChanged(QString)));

  QObject::connect(this->lineEdit_Name, SIGNAL(textChanged(QString)), q, SLOT(onNameChanged(QString)));
  QObject::connect(this->pushButton_ResetName, SIGNAL(clicked()), q, SLOT(onResetNameClicked()));
  QObject::connect(this->ColorPickerButton_RecommendedRGB, SIGNAL(colorChanged(QColor)), q, SLOT(onColorChanged(QColor)));
  QObject::connect(this->pushButton_ResetColor, SIGNAL(clicked()), q, SLOT(onResetColorClicked()));

  QObject::connect(this->ColorTableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), q, SLOT(onColorSelected(QItemSelection, QItemSelection)));
  QObject::connect(this->ColorTableView, SIGNAL(doubleClicked(const QModelIndex&)), q, SLOT(onColorRowDoubleClicked(const QModelIndex&)));

  // Set default settings for widgets
  this->tableWidget_Category->setEnabled(false);
  this->SearchBox_Category->setEnabled(false);
  this->tableWidget_Type->setEnabled(true); // None item always present
  this->SearchBox_Type->setEnabled(false);
  this->ComboBox_TypeModifier->setEnabled(false);

  this->SearchBox_Region->setEnabled(false);
  this->tableWidget_Region->setEnabled(false);
  this->ComboBox_RegionModifier->setEnabled(false);

  // Apply initial state of expand buttons
  if (qSlicerApplication::application())
  {
    QSettings* settings = qSlicerApplication::application()->userSettings();
    this->CategoryExpandButton->setChecked(settings->value("Terminology/ShowCategorySelector", false).toBool());
    this->RegionExpandButton->setChecked(settings->value("Terminology/ShowRegionSelector", false).toBool());
  }

  // Set reset button sizes
  this->pushButton_ResetName->setMaximumHeight(this->lineEdit_Name->sizeHint().height());
  this->pushButton_ResetName->setMaximumWidth(this->lineEdit_Name->sizeHint().height());
  this->pushButton_ResetColor->setMaximumHeight(this->lineEdit_Name->sizeHint().height());
  this->pushButton_ResetColor->setMaximumWidth(this->lineEdit_Name->sizeHint().height());

  // Use the CTK color picker
  ctkColorPickerButton::ColorDialogOptions options = ctkColorPickerButton::UseCTKColorDialog;
  this->ColorPickerButton_RecommendedRGB->setDialogOptions(options);

  // Setup load buttons
  QObject::connect(this->pushButton_LoadTerminology, SIGNAL(clicked()), q, SLOT(onLoadTerminologyClicked()));
  QObject::connect(this->pushButton_LoadTerminology_2, SIGNAL(clicked()), q, SLOT(onLoadTerminologyClicked()));
  QObject::connect(this->pushButton_LoadRegionContext, SIGNAL(clicked()), q, SLOT(onLoadRegionContextClicked()));

  // Populate terminology combobox with the loaded terminologies
  this->populateTerminologyComboBox();
  // Populate region context combobox with the loaded region contexts
  this->populateRegionContextComboBox();
}

//-----------------------------------------------------------------------------
vtkSlicerTerminologiesModuleLogic* qSlicerTerminologyNavigatorWidgetPrivate::terminologyLogic()
{
  qSlicerCoreApplication* app = qSlicerCoreApplication::application();
  if (!app)
  {
    // this happens when the widget is instantiated by Qt Designer plugin
    qCritical() << Q_FUNC_INFO << ": Terminologies logic is not found (not a Slicer core application instance)";
    return nullptr;
  }
  vtkSlicerTerminologiesModuleLogic* terminologiesLogic = vtkSlicerTerminologiesModuleLogic::SafeDownCast(app->moduleLogic("Terminologies"));
  if (!terminologiesLogic)
  {
    qCritical() << Q_FUNC_INFO << ": Terminologies logic is not found";
  }
  return terminologiesLogic;
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::setNameFromCurrentTerminology()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);
  QString name = q->nameFromCurrentTerminology();
  if (name.isEmpty())
  {
    return;
  }

  // Set to auto-generated
  this->NameAutoGenerated = true;

  // Disable reset name button
  this->pushButton_ResetName->setEnabled(!this->NameAutoGenerated);

  this->lineEdit_Name->blockSignals(true); // The callback function is to save the user's manual name entry
  this->lineEdit_Name->setText(name);
  this->lineEdit_Name->blockSignals(false);
}

//-----------------------------------------------------------------------------
QColor qSlicerTerminologyNavigatorWidgetPrivate::terminologyRecommendedColor()
{
  // Return 'invalid' color if type is not selected
  if (!this->CurrentTypeObject)
  {
    return QColor();
  }

  // Get recommended color from terminology entry.
  // If the current type has no modifiers then set color form the type
  unsigned char r = vtkSlicerTerminologyType::INVALID_COLOR[0];
  unsigned char g = vtkSlicerTerminologyType::INVALID_COLOR[1];
  unsigned char b = vtkSlicerTerminologyType::INVALID_COLOR[2];

  // Use the color specified in the modifier by default
  if (this->CurrentTypeObject->GetHasModifiers() && this->CurrentTypeModifierObject->GetCodeValue())
  {
    this->CurrentTypeModifierObject->GetRecommendedDisplayRGBValue(r, g, b);
  }

  // If color is not defined for the modifier then use the non-modified type object's color
  if (r == vtkSlicerTerminologyType::INVALID_COLOR[0]    //
      && g == vtkSlicerTerminologyType::INVALID_COLOR[1] //
      && b == vtkSlicerTerminologyType::INVALID_COLOR[2])
  {
    this->CurrentTypeObject->GetRecommendedDisplayRGBValue(r, g, b);
  }

  // Use generated color otherwise (i.e. the default invalid color)
  if (r == vtkSlicerTerminologyType::INVALID_COLOR[0]    //
      && g == vtkSlicerTerminologyType::INVALID_COLOR[1] //
      && b == vtkSlicerTerminologyType::INVALID_COLOR[2])
  {
    r = this->GeneratedColor.red();
    g = this->GeneratedColor.green();
    b = this->GeneratedColor.blue();
  }

  return QColor::fromRgb(r, g, b);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::setRecommendedColorFromCurrentTerminology()
{
  // Set 'invalid' gray color if type is not selected or the selected type has modifiers but no modifier is selected
  QColor color = this->terminologyRecommendedColor();
  if (!color.isValid())
  {
    this->ColorPickerButton_RecommendedRGB->blockSignals(true); // The callback function is to save the user's custom color selection
    this->ColorPickerButton_RecommendedRGB->setColor(
      QColor(vtkSlicerTerminologyType::INVALID_COLOR[0], vtkSlicerTerminologyType::INVALID_COLOR[1], vtkSlicerTerminologyType::INVALID_COLOR[2]));
    this->ColorPickerButton_RecommendedRGB->blockSignals(false);
    return;
  }

  // Set to auto-generated
  this->ColorAutoGenerated = true;

  // Disable reset color button
  this->pushButton_ResetColor->setEnabled(!this->ColorAutoGenerated);

  this->ColorPickerButton_RecommendedRGB->blockSignals(true); // The callback function is to save the user's custom color selection
  this->ColorPickerButton_RecommendedRGB->setColor(color);
  this->ColorPickerButton_RecommendedRGB->blockSignals(false);
}

//-----------------------------------------------------------------------------
QColor qSlicerTerminologyNavigatorWidgetPrivate::recommendedColorForType(std::string terminologyName, vtkSlicerTerminologyCategory* category, vtkSlicerTerminologyType* type)
{
  if (terminologyName.empty() || !category || !type)
  {
    return QColor();
  }
  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return QColor();
  }

  unsigned char* colorArray = type->GetRecommendedDisplayRGBValue();
  QColor color = QColor::fromRgb(colorArray[0], colorArray[1], colorArray[2]);
  if (color.isValid()                                                    //
      && (colorArray[0] != vtkSlicerTerminologyType::INVALID_COLOR[0]    //
          || colorArray[1] != vtkSlicerTerminologyType::INVALID_COLOR[1] //
          || colorArray[2] != vtkSlicerTerminologyType::INVALID_COLOR[2]))
  {
    return color;
  }

  // If color was invalid check its modifiers
  if (!type->GetHasModifiers())
  {
    return QColor();
  }
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> typeModifiers;
  vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId = vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(category);
  vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeId = vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(type);
  logic->GetTypeModifiersInTerminologyType(terminologyName.c_str(), categoryId, typeId, typeModifiers);
  vtkNew<vtkSlicerTerminologyType> typeModifierObject;
  for (auto modifierId : typeModifiers)
  {
    logic->GetTypeModifierInTerminologyType(terminologyName, categoryId, typeId, modifierId, typeModifierObject);
    colorArray = typeModifierObject->GetRecommendedDisplayRGBValue();
    color = QColor::fromRgb(colorArray[0], colorArray[1], colorArray[2]);
    if (color.isValid()                                                    //
        && (colorArray[0] != vtkSlicerTerminologyType::INVALID_COLOR[0]    //
            || colorArray[1] != vtkSlicerTerminologyType::INVALID_COLOR[1] //
            || colorArray[2] != vtkSlicerTerminologyType::INVALID_COLOR[2]))
    {
      return color;
    }
  }
  return QColor();
}

//-----------------------------------------------------------------------------
QTableWidgetItem* qSlicerTerminologyNavigatorWidgetPrivate::findTableWidgetItemForCategory(vtkSlicerTerminologyCategory* category)
{
  if (!category)
  {
    return nullptr;
  }

  QString categoryName(category->GetCodeMeaning());
  Qt::MatchFlags flags = Qt::MatchExactly | Qt::MatchCaseSensitive;
  QList<QTableWidgetItem*> items = this->tableWidget_Category->findItems(categoryName, flags);
  if (items.count() == 0)
  {
    return nullptr;
  }

  for (QTableWidgetItem* const item : items)
  {
    QString codingSchemeDesignator = item->data(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole).toString();
    QString codeValue = item->data(qSlicerTerminologyNavigatorWidget::CodeValueRole).toString();
    if (category->GetCodingSchemeDesignator() && !codingSchemeDesignator.compare(category->GetCodingSchemeDesignator()) //
        && category->GetCodeValue() && !codeValue.compare(category->GetCodeValue()))
    {
      return item;
    }
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
QTableWidgetItem* qSlicerTerminologyNavigatorWidgetPrivate::findTableWidgetItemForType(QTableWidget* tableWidget, vtkSlicerTerminologyType* type)
{
  if (!tableWidget || !type)
  {
    return nullptr;
  }

  QString typeName(type->GetCodeMeaning());
  Qt::MatchFlags flags = Qt::MatchExactly | Qt::MatchCaseSensitive;
  QList<QTableWidgetItem*> items = tableWidget->findItems(typeName, flags);
  if (items.count() == 0)
  {
    return nullptr;
  }

  for (QTableWidgetItem* const item : items)
  {
    QString codingSchemeDesignator = item->data(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole).toString();
    QString codeValue = item->data(qSlicerTerminologyNavigatorWidget::CodeValueRole).toString();
    if (type->GetCodingSchemeDesignator() && !codingSchemeDesignator.compare(type->GetCodingSchemeDesignator()) //
        && type->GetCodeValue() && !codeValue.compare(type->GetCodeValue()))
    {
      return item;
    }
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
int qSlicerTerminologyNavigatorWidgetPrivate::findComboBoxIndexForModifier(ctkComboBox* comboBox, vtkSlicerTerminologyType* modifier)
{
  if (!comboBox || !modifier)
  {
    return -1;
  }

  QString modifierName(modifier->GetCodeMeaning());
  int modifierIndex = comboBox->findText(modifierName);
  return modifierIndex;
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::populateTerminologyComboBox()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);
  QSignalBlocker blocker(this->ComboBox_Terminology);
  QSignalBlocker blocker2(this->ComboBox_Terminology_2);
  this->ComboBox_Terminology->clear();
  this->ComboBox_Terminology_2->clear();

  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    return;
  }

  this->TerminologyComboboxPopulating = true;
  std::vector<std::string> terminologyNames;
  // Note: Currently we consider all the loaded terminology contexts, and these include the ones converted
  // from the compatible color table nodes in the scene. We could instead just use the compatible color
  // nodes, however, we keep this conversion for now (it could be useful for saving them, or if we decide
  // to use the terminology selector again for color table terminologies).
  logic->GetLoadedTerminologyNames(terminologyNames);
  for (std::vector<std::string>::iterator termIt = terminologyNames.begin(); termIt != terminologyNames.end(); ++termIt)
  {
    this->ComboBox_Terminology->addItem(termIt->c_str());
    this->ComboBox_Terminology_2->addItem(termIt->c_str());
  }
  vtkMRMLScene* scene = logic->GetMRMLScene();
  if (scene)
  {
    std::vector<std::string> colorNodeIds = logic->GetCompatibleColorNodeIDs();
    for (std::string& colorNodeId : colorNodeIds)
    {
      vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(scene->GetNodeByID(colorNodeId.c_str()));
      if (!colorNode || !colorNode->GetName())
      {
        continue;
      }
      this->ComboBox_Terminology->addItem(QString::fromUtf8(colorNode->GetName()), QString::fromStdString(colorNodeId));
      this->ComboBox_Terminology_2->addItem(QString::fromUtf8(colorNode->GetName()), QString::fromStdString(colorNodeId));
    }
  }
  this->TerminologyComboboxPopulating = false;
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::populateCategoryTable()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  this->tableWidget_Category->clearContents();

  if (this->CurrentTerminologyName.isEmpty())
  {
    this->tableWidget_Category->setRowCount(0);
    return;
  }

  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  // Get category names containing the search string. If no search string then add every category
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> categories;
  logic->FindCategoriesInTerminology(this->CurrentTerminologyName.toUtf8().constData(), categories, this->SearchBox_Category->text().toUtf8().constData());

  QTableWidgetItem* selectedItem = nullptr;
  this->tableWidget_Category->setRowCount(categories.size());
  int index = 0;
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  for (idIt = categories.begin(); idIt != categories.end(); ++idIt, ++index)
  {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedCategoryId = (*idIt);
    QString addedCategoryName(addedCategoryId.CodeMeaning.c_str());
    QTableWidgetItem* addedCategoryItem = new QTableWidgetItem(addedCategoryName);
    addedCategoryItem->setData(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole, QString(addedCategoryId.CodingSchemeDesignator.c_str()));
    addedCategoryItem->setData(qSlicerTerminologyNavigatorWidget::CodeValueRole, QString(addedCategoryId.CodeValue.c_str()));
    this->tableWidget_Category->setItem(index, 0, addedCategoryItem);

    if (this->CurrentCategoryObject->GetCodingSchemeDesignator()                                                     //
        && !addedCategoryId.CodingSchemeDesignator.compare(this->CurrentCategoryObject->GetCodingSchemeDesignator()) //
        && this->CurrentCategoryObject->GetCodeValue()                                                               //
        && !addedCategoryId.CodeValue.compare(this->CurrentCategoryObject->GetCodeValue()))
    {
      selectedItem = addedCategoryItem;
    }
  }

  // Select category if selection was valid and item shows up in search
  if (selectedItem)
  {
    this->tableWidget_Category->setCurrentItem(selectedItem);
  }
  // Otherwise select all
  else
  {
    this->tableWidget_Category->selectAll();
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::populateTypeTable()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  this->tableWidget_Type->clearContents();

  // Collect selected categories. Use current category if selected list is empty and current is valid
  // (selection happens from UI, current is set when setting from outside using setTerminologyEntry)
  QList<vtkSlicerTerminologyCategory*> selectedCategories;
  for (const vtkSmartPointer<vtkSlicerTerminologyCategory>& category : this->SelectedCategoryObjects)
  {
    selectedCategories << category.GetPointer();
  }
  if (!selectedCategories.count() && this->CurrentCategoryObject && this->CurrentCategoryObject->GetCodeValue())
  {
    selectedCategories << this->CurrentCategoryObject;
  }

  // Empty table only contains none item
  if (this->CurrentTerminologyName.isEmpty() || selectedCategories.count() == 0)
  {
    this->tableWidget_Type->setRowCount(1);
    QTableWidgetItem* noneItem = new QTableWidgetItem(this->NoneItemName);
    this->tableWidget_Type->setItem(0, 0, noneItem);
    this->tableWidget_Type->setCurrentItem(noneItem);
    return;
  }

  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  // Get types in selected categories containing the search string. If no search string then add every type
  struct TypeInfo
  {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier id;
    vtkSlicerTerminologyCategory* category;
    QColor color;
  };
  std::vector<TypeInfo> types;
  std::string searchTerm(this->SearchBox_Type->text().toUtf8().constData());
  std::vector<vtkSmartPointer<vtkSlicerTerminologyType>>::iterator typeObjIt;
  std::set<std::string> existingTypesSchemeValue;
  for (vtkSlicerTerminologyCategory* const category : selectedCategories)
  {
    std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> typesInCategory;
    std::vector<vtkSmartPointer<vtkSlicerTerminologyType>> typesObjectsInCategory;
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId = vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(category);

    logic->FindTypesInTerminologyCategory(this->CurrentTerminologyName.toUtf8().constData(),
                                          vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(category),
                                          typesInCategory,
                                          searchTerm,
                                          &typesObjectsInCategory);

    std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
    for (idIt = typesInCategory.begin(), typeObjIt = typesObjectsInCategory.begin(); idIt != typesInCategory.end(); ++idIt, ++typeObjIt)
    {
      // Determine if type already exists in list
      std::string typesSchemeValue = idIt->CodeValue + idIt->CodingSchemeDesignator;
      if (existingTypesSchemeValue.find(typesSchemeValue) != existingTypesSchemeValue.end())
      {
        // duplicate
        continue;
      }

      // Add type
      TypeInfo typeInfo;
      typeInfo.id = *idIt;
      typeInfo.category = category;
      typeInfo.color = this->recommendedColorForType(this->CurrentTerminologyName.toUtf8().constData(), category, *typeObjIt);
      types.push_back(typeInfo);

      // Store type-category relationship
      existingTypesSchemeValue.insert(typesSchemeValue);
    }
  }

  QTableWidgetItem* selectedItem = nullptr;

  // Show none item only if search term is empty (if user is searching then they want an actual type)
  int noneTypeExists = 0;
  QTableWidgetItem* noneItem = nullptr;
  if (searchTerm.empty())
  {
    noneTypeExists = 1;
    noneItem = new QTableWidgetItem(this->NoneItemName);
    this->tableWidget_Type->setRowCount(types.size() + noneTypeExists);
    this->tableWidget_Type->setItem(0, 0, noneItem);
  }
  else
  {
    this->tableWidget_Type->setRowCount(types.size());
  }

  // Add type items to table
  int typeIndex = 0;
  vtkNew<vtkSlicerTerminologyType> typeObject;
  std::vector<TypeInfo>::iterator typeIt;
  for (typeIt = types.begin(); typeIt != types.end(); ++typeIt, ++typeIndex)
  {
    const vtkSlicerTerminologiesModuleLogic::CodeIdentifier& addedTypeId = typeIt->id;
    QString addedTypeName(addedTypeId.CodeMeaning.c_str());
    QTableWidgetItem* addedTypeItem = new QTableWidgetItem(addedTypeName);
    addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole, QString(addedTypeId.CodingSchemeDesignator.c_str()));
    addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CodeValueRole, QString(addedTypeId.CodeValue.c_str()));
    // Reference containing category so that it can be set when type is selected
    vtkSlicerTerminologyCategory* category = typeIt->category;
    addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CategoryCodingSchemeDesignatorRole, QString(category->GetCodingSchemeDesignator()));
    addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CategoryCodeValueRole, QString(category->GetCodeValue()));
    addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CategoryCodeMeaningRole, QString(category->GetCodeMeaning()));
    QString tooltip = QString("Category: %1 (anatomy:%2)").arg(category->GetCodeMeaning()).arg((category->GetShowAnatomy() ? "available" : "N/A"));
    addedTypeItem->setToolTip(tooltip);

    if (typeIt->color.isValid())
    {
      addedTypeItem->setData(Qt::DecorationRole, typeIt->color);
    }

    // Insert type item
    this->tableWidget_Type->setItem(typeIndex + noneTypeExists, 0, addedTypeItem);

    if (this->CurrentTypeObject->GetCodingSchemeDesignator()                                                 //
        && !addedTypeId.CodingSchemeDesignator.compare(this->CurrentTypeObject->GetCodingSchemeDesignator()) //
        && this->CurrentTypeObject->GetCodeValue()                                                           //
        && !addedTypeId.CodeValue.compare(this->CurrentTypeObject->GetCodeValue()))
    {
      selectedItem = addedTypeItem;
    }
  }

  if (selectedItem)
  {
    // Select type if selection was valid and item shows up in search
    this->tableWidget_Type->setCurrentItem(selectedItem);
  }
  else if (noneItem)
  {
    // Select none item if it exists (no search and no selected item specified)
    this->tableWidget_Type->setCurrentItem(noneItem);
  }
  else
  {
    // Select first type otherwise
    this->tableWidget_Type->setCurrentCell(0, 0);
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::populateTypeModifierComboBox()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  this->ComboBox_TypeModifier->clear();

  if (this->CurrentTerminologyName.isEmpty() || !this->CurrentCategoryObject || !this->CurrentTypeObject || !this->CurrentTypeObject->GetCodeValue())
  {
    this->ComboBox_TypeModifier->setEnabled(false);
    return;
  }
  // If current type has no modifiers then leave it empty and disable
  if (!this->CurrentTypeObject->GetHasModifiers())
  {
    this->ComboBox_TypeModifier->setEnabled(false);
    return;
  }

  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  int selectedIndex = 0;

  // Add none item so that no modifier can be selected even if there are options
  this->ComboBox_TypeModifier->addItem(qSlicerTerminologyNavigatorWidget::tr("No type modifier"));

  // Get type modifier names
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> typeModifiers;
  logic->GetTypeModifiersInTerminologyType(this->CurrentTerminologyName.toUtf8().constData(),
                                           vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(this->CurrentCategoryObject),
                                           vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(this->CurrentTypeObject),
                                           typeModifiers);

  int index = 1; // None modifier has index 0
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  for (idIt = typeModifiers.begin(); idIt != typeModifiers.end(); ++idIt, ++index)
  {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedTypeModifierId = (*idIt);
    QString addedTypeModifierName(addedTypeModifierId.CodeMeaning.c_str());

    QMap<QString, QVariant> userData;
    userData[QString::number(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole)] = QString(addedTypeModifierId.CodingSchemeDesignator.c_str());
    userData[QString::number(qSlicerTerminologyNavigatorWidget::CodeValueRole)] = QString(addedTypeModifierId.CodeValue.c_str());
    this->ComboBox_TypeModifier->addItem(addedTypeModifierName, QVariant(userData));

    if (selectedIndex == -1 && !addedTypeModifierName.compare(this->CurrentTypeModifierObject->GetCodeMeaning()))
    {
      selectedIndex = index;
    }
  }

  // Select modifier if selection was valid
  if (selectedIndex != -1)
  {
    this->ComboBox_TypeModifier->setCurrentIndex(selectedIndex);
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::setCurrentTerminology(QString terminologyName, QString colorNodeID)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);
  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  // If no change then nothing to do
  if (this->CurrentTerminologyName == terminologyName && this->CurrentColorNodeID == colorNodeID)
  {
    return;
  }

  int terminologyIndex = -1;
  if (colorNodeID.isEmpty())
  {
    terminologyIndex = this->ComboBox_Terminology->findText(terminologyName);
  }
  else
  {
    terminologyIndex = this->ComboBox_Terminology->findData(colorNodeID);
  }
  {
    QSignalBlocker blocker(this->ComboBox_Terminology);
    QSignalBlocker blocker2(this->ComboBox_Terminology_2);
    this->ComboBox_Terminology->setCurrentIndex(terminologyIndex);
    this->ComboBox_Terminology_2->setCurrentIndex(terminologyIndex);
  }

  // Save last used terminology context in application settings
  if (!this->TerminologyComboboxPopulating)
  {
    QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
    QStringList lastTerminologyContextNames = settings->value("Terminology/LastTerminologyContexts").toStringList();
    QString terminologyOrColorName = terminologyName;
    if (!colorNodeID.isEmpty())
    {
      vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(logic->GetMRMLScene()->GetNodeByID(colorNodeID.toUtf8()));
      if (colorNode && colorNode->GetName())
      {
        terminologyOrColorName = QString::fromUtf8(colorNode->GetName());
      }
    }
    // Remove the terminology name from the list so that there are no duplicate entries in the list
    lastTerminologyContextNames.removeOne(terminologyOrColorName);
    // Prepend terminology name to the list so that the last used terminology is first
    lastTerminologyContextNames.insert(0, terminologyOrColorName);
    // Trim to a reasonable maximum to prevent unbounded growth
    const int maxLastTerminologyContextNames = 30;
    if (lastTerminologyContextNames.size() > maxLastTerminologyContextNames)
    {
      lastTerminologyContextNames = lastTerminologyContextNames.mid(0, maxLastTerminologyContextNames);
    }
    settings->setValue("Terminology/LastTerminologyContexts", lastTerminologyContextNames);
  }

  // Reset current category, type, and type modifier
  this->CurrentCategoryObject->Initialize();
  this->CurrentTypeObject->Initialize();
  this->CurrentTypeModifierObject->Initialize();

  // Set current terminology
  this->CurrentTerminologyName = terminologyName;
  this->CurrentColorNodeID = colorNodeID;

  bool terminologyIsColorTable = !colorNodeID.isEmpty();

  if (terminologyIsColorTable)
  {
    // Selected terminology is a color table
    vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(logic->GetMRMLScene()->GetNodeByID(colorNodeID.toUtf8()));
    this->ColorTableView->setMRMLColorNode(colorNode);
  }
  else
  {
    // Selected terminology is loaded terminology JSON
    // Populate category table, and reset type table and type modifier combobox
    this->populateCategoryTable();
    this->populateTypeTable();
    this->populateTypeModifierComboBox();

    // Only enable category table if there are items in it
    if (this->tableWidget_Category->rowCount() == 0)
    {
      this->tableWidget_Category->setEnabled(!this->SearchBox_Type->text().isEmpty()); // Might be empty because of a search
      // this->tableWidget_Type->setEnabled(false);
      this->SearchBox_Type->setEnabled(false);
      this->ComboBox_TypeModifier->setEnabled(false);
    }
    else
    {
      this->tableWidget_Category->setEnabled(true);
      this->SearchBox_Category->setEnabled(true);
    }
  }

  // Show widgets corresponding to selection
  this->frame_TerminologyEntry->setVisible(!terminologyIsColorTable);
  this->frame_ColorTable->setVisible(terminologyIsColorTable);

  // Selection is valid if there is a valid type object
  emit q->selectionValidityChanged(this->CurrentTypeObject && this->CurrentTypeObject->GetCodeValue());
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentCategory(vtkSlicerTerminologyCategory* category)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  // Reset current type and type modifier
  this->CurrentTypeObject->Initialize();
  this->CurrentTypeModifierObject->Initialize();
  // Reset region information as well
  this->CurrentRegionObject->Initialize();
  this->CurrentRegionModifierObject->Initialize();

  if (!category)
  {
    this->CurrentCategoryObject->Initialize();
    return false;
  }

  // Set current category
  this->CurrentCategoryObject->Copy(category);

  // Populate type table, and reset type modifier combobox and region widgets
  this->populateTypeTable();
  this->populateTypeModifierComboBox();
  this->tableWidget_Region->setCurrentItem(nullptr);
  this->populateRegionModifierComboBox();

  // Update widget UI from current category
  this->updateWidgetFromCurrentCategory();

  // Selection is invalid until type is selected
  emit q->selectionValidityChanged(false);

  // Select category if found
  QTableWidgetItem* categoryItem = this->findTableWidgetItemForCategory(category);
  return (categoryItem != nullptr); // Return true if category found and selected
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::updateWidgetFromCurrentCategory()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  // Only enable type table if there are items in it
  if (this->tableWidget_Type->rowCount() == 1) // None item always present
  {
    this->ComboBox_TypeModifier->setEnabled(false);
  }
  else
  {
    // this->tableWidget_Type->setEnabled(true);
    this->SearchBox_Type->setEnabled(true);
  }

  // Enable region controls if related flag is on
  this->ComboBox_RegionContext->setEnabled(this->CurrentCategoryObject->GetShowAnatomy());
  this->tableWidget_Region->setEnabled(this->CurrentCategoryObject->GetShowAnatomy());
  this->SearchBox_Region->setEnabled(this->CurrentCategoryObject->GetShowAnatomy());
  this->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection
  if (q->regionSectionVisible())
  {
    // Always enable expand button if panel is visible
    this->RegionExpandButton->setEnabled(true);
  }
  else
  {
    // Only enable expand button if region is enabled in selected category
    this->RegionExpandButton->setEnabled(this->CurrentCategoryObject->GetShowAnatomy());
    // Blink button if region is enabled to let the user know there are additional options available
    if (this->CurrentCategoryObject->GetShowAnatomy())
    {
      this->RegionExpandButton->setDown(true);
      QTimer::singleShot(50, q, SLOT(onRegionExpandButtonUp()));
      QTimer::singleShot(100, q, SLOT(onRegionExpandButtonDown()));
      QTimer::singleShot(150, q, SLOT(onRegionExpandButtonUp()));
      QTimer::singleShot(200, q, SLOT(onRegionExpandButtonDown()));
      QTimer::singleShot(250, q, SLOT(onRegionExpandButtonUp()));
    }
  }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentType(vtkSlicerTerminologyType* type)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  // Reset current type modifier
  this->CurrentTypeModifierObject->Initialize();

  // Null type means None selection
  if (!type)
  {
    this->CurrentTypeObject->Initialize();
    this->tableWidget_Type->blockSignals(true);
    this->tableWidget_Type->setCurrentCell(0, 0);
    this->tableWidget_Type->blockSignals(false);
    emit q->selectionValidityChanged(true); // None selection is valid too
    return false;
  }

  // Set current type
  this->CurrentTypeObject->Copy(type);

  // Populate type modifier combobox
  this->populateTypeModifierComboBox();

  // Only enable type modifier combobox if there are items in it
  this->ComboBox_TypeModifier->setEnabled(this->ComboBox_TypeModifier->count());

  // With valid type selected, terminology selection becomes also valid
  emit q->selectionValidityChanged(true);

  // Select type if found
  QTableWidgetItem* typeItem = this->findTableWidgetItemForType(this->tableWidget_Type, type);
  if (typeItem)
  {
    this->tableWidget_Type->blockSignals(true);
    this->tableWidget_Type->setCurrentItem(typeItem);
    this->tableWidget_Type->blockSignals(false);
  }
  return typeItem; // Return true if type found and selected
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentTypeModifier(vtkSlicerTerminologyType* modifier)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  if (!modifier)
  {
    this->CurrentTypeObject->Initialize();
    qCritical() << Q_FUNC_INFO << ": Invalid type modifier object set";
    return false;
  }

  // Set current type modifier
  this->CurrentTypeModifierObject->Copy(modifier);

  // Select modifier if found
  int modifierIndex = this->findComboBoxIndexForModifier(this->ComboBox_TypeModifier, modifier);
  if (modifierIndex != -1)
  {
    this->ComboBox_TypeModifier->blockSignals(true);
    this->ComboBox_TypeModifier->setCurrentIndex(modifierIndex);
    this->ComboBox_TypeModifier->blockSignals(false);
  }
  return (modifierIndex != -1);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::copyContextToUserDirectory(QString filePath)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);
  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology logic";
    return;
  }

  // Make sure the file can be copied to the settings folder
  QDir settingsFolder(logic->GetUserContextsPath());
  if (!settingsFolder.exists())
  {
    settingsFolder.mkpath(logic->GetUserContextsPath());
  }
  if (!settingsFolder.exists())
  {
    qCritical() << Q_FUNC_INFO << ": Settings folder '" << settingsFolder.absolutePath() << "' does not exist. Copying context file failed.";
    return;
  }
  QString fileNameOnly = QFileInfo(filePath).fileName();
  QString targetFilePath = settingsFolder.absoluteFilePath(fileNameOnly);

  // Check if there is a file with the same name in the settings folder and ask the user in that case
  if (QFile::exists(targetFilePath))
  {
    QString message = QString(qSlicerTerminologyNavigatorWidget::tr("There is a file with name '%1' in the stored contexts.\n\n"
                                                                    "Do you wish to update the stored context file with the just loaded one?"))
                        .arg(fileNameOnly);
    QMessageBox::StandardButton answer =
      QMessageBox::question(nullptr, qSlicerTerminologyNavigatorWidget::tr("Context file exists"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
    if (answer == QMessageBox::No)
    {
      return;
    }
    else
    {
      // Remove file before copying (copy function does not overwrite)
      settingsFolder.remove(fileNameOnly);
    }
  }

  // Copy file to settings folder for automatic loading on startup
  QFile file(filePath);
  file.copy(targetFilePath);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::populateRegionContextComboBox()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  this->ComboBox_RegionContext->clear();

  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    return;
  }

  this->RegionContextComboboxPopulating = true;
  std::vector<std::string> regionContextNames;
  logic->GetLoadedRegionContextNames(regionContextNames);
  for (std::vector<std::string>::iterator anIt = regionContextNames.begin(); anIt != regionContextNames.end(); ++anIt)
  {
    this->ComboBox_RegionContext->addItem(anIt->c_str());
  }
  this->RegionContextComboboxPopulating = false;
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::populateRegionTable()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  this->tableWidget_Region->clearContents();

  if (this->CurrentRegionContextName.isEmpty())
  {
    this->tableWidget_Region->setRowCount(0);
    return;
  }

  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  // Get region names containing the search string. If no search string then add every region
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> regions;
  logic->FindRegionsInRegionContext(this->CurrentRegionContextName.toUtf8().constData(), regions, this->SearchBox_Region->text().toUtf8().constData());

  this->tableWidget_Region->setRowCount(regions.size() + 1); // +1 for the "None" item

  int index = 0;

  // Add "None" item
  QTableWidgetItem* noneRegionItem = new QTableWidgetItem(this->NoneItemName);
  this->tableWidget_Region->setItem(index++, 0, noneRegionItem);
  QTableWidgetItem* selectedItem = noneRegionItem;

  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  for (idIt = regions.begin(); idIt != regions.end(); ++idIt, ++index)
  {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedRegionId = (*idIt);
    QString addedRegionName(addedRegionId.CodeMeaning.c_str());
    QTableWidgetItem* addedRegionItem = new QTableWidgetItem(addedRegionName);
    addedRegionItem->setData(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole, QString(addedRegionId.CodingSchemeDesignator.c_str()));
    addedRegionItem->setData(qSlicerTerminologyNavigatorWidget::CodeValueRole, QString(addedRegionId.CodeValue.c_str()));
    this->tableWidget_Region->setItem(index, 0, addedRegionItem);

    if (this->CurrentRegionObject->GetCodingSchemeDesignator()                                                   //
        && !addedRegionId.CodingSchemeDesignator.compare(this->CurrentRegionObject->GetCodingSchemeDesignator()) //
        && this->CurrentRegionObject->GetCodeValue()                                                             //
        && !addedRegionId.CodeValue.compare(this->CurrentRegionObject->GetCodeValue()))
    {
      selectedItem = addedRegionItem;
    }
  }

  // Select region if selection was valid and item shows up in search
  if (selectedItem)
  {
    this->tableWidget_Region->setCurrentItem(selectedItem);
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::populateRegionModifierComboBox()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  this->ComboBox_RegionModifier->clear();

  if (this->CurrentRegionContextName.isEmpty() || !this->CurrentRegionObject || !this->CurrentRegionObject->GetCodeValue())
  {
    this->ComboBox_RegionModifier->setEnabled(false);
    return;
  }
  // If current region has no modifiers then leave it empty and disable
  if (!this->CurrentRegionObject->GetHasModifiers())
  {
    this->ComboBox_RegionModifier->setEnabled(false);
    return;
  }

  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  // Get region modifier names
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> regionModifiers;
  logic->GetRegionModifiersInRegion(
    this->CurrentRegionContextName.toUtf8().constData(), vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(this->CurrentRegionObject), regionModifiers);

  // Add "none" item
  this->ComboBox_RegionModifier->addItem(qSlicerTerminologyNavigatorWidget::tr("No region modifier"));
  int selectedIndex = 0;
  int index = 1; // "none" item is 0, start adding items from 1

  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  for (idIt = regionModifiers.begin(); idIt != regionModifiers.end(); ++idIt, ++index)
  {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedRegionModifierId = (*idIt);
    QString addedRegionModifierName(addedRegionModifierId.CodeMeaning.c_str());

    QMap<QString, QVariant> userData;
    userData[QString::number(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole)] = QString(addedRegionModifierId.CodingSchemeDesignator.c_str());
    userData[QString::number(qSlicerTerminologyNavigatorWidget::CodeValueRole)] = QString(addedRegionModifierId.CodeValue.c_str());
    this->ComboBox_RegionModifier->addItem(addedRegionModifierName, QVariant(userData));

    if (!addedRegionModifierName.compare(this->CurrentRegionModifierObject->GetCodeMeaning()))
    {
      selectedIndex = index;
    }
  }

  // Select modifier if selection was valid
  if (selectedIndex != -1)
  {
    this->ComboBox_RegionModifier->setCurrentIndex(selectedIndex);
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::setCurrentRegionContext(QString contextName)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  // Reset current region and region modifier
  this->CurrentRegionObject->Initialize();
  this->CurrentRegionModifierObject->Initialize();

  // Set current region context
  this->CurrentRegionContextName = contextName;
  if (contextName.isEmpty())
  {
    return;
  }

  // Populate region table and reset region modifier combobox
  this->populateRegionTable();
  this->populateRegionModifierComboBox();

  // Only enable region table if there are items in it
  if (this->tableWidget_Region->rowCount() == 0)
  {
    this->tableWidget_Region->setEnabled(false);
    if (this->SearchBox_Region->text().isEmpty())
    {
      // Table might be empty because of a search
      this->SearchBox_Region->setEnabled(false);
    }
    this->ComboBox_RegionModifier->setEnabled(false);
  }
  else if (this->CurrentCategoryObject->GetShowAnatomy())
  {
    this->tableWidget_Region->setEnabled(true);
    this->SearchBox_Region->setEnabled(true);
  }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentRegion(vtkSlicerTerminologyType* region)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  // Reset current region modifier
  this->CurrentRegionModifierObject->Initialize();

  if (!region)
  {
    // Reset current type and type modifier
    this->CurrentRegionObject->Initialize();
    qCritical() << Q_FUNC_INFO << ": Invalid region object set";
    return false;
  }

  // Ignore selection if current category does not support anatomy (possible due to multi-selection)
  if (!this->CurrentCategoryObject)
  {
    // Reset current type and type modifier
    this->CurrentRegionObject->Initialize();
    qCritical() << Q_FUNC_INFO << ": Missing current category";
    return false;
  }

  // Update state of anatomy controls based on the category of the selected type
  // (the controls may have been enabled because some of the selected categories supported anatomy)
  this->ComboBox_RegionContext->setEnabled(this->CurrentCategoryObject->GetShowAnatomy());
  this->tableWidget_Region->setEnabled(this->CurrentCategoryObject->GetShowAnatomy());
  this->SearchBox_Region->setEnabled(this->CurrentCategoryObject->GetShowAnatomy());
  this->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection

  // Reject region selection if current type's category does not support anatomy
  if (!this->CurrentCategoryObject->GetShowAnatomy())
  {
    this->CurrentRegionObject->Initialize();
    return false;
  }

  // Set current region
  this->CurrentRegionObject->Copy(region);

  // Populate region modifier combobox
  this->populateRegionModifierComboBox();

  // Only enable region modifier combobox if there are items in it
  this->ComboBox_RegionModifier->setEnabled(this->ComboBox_RegionModifier->count());

  // "None" is selected
  if (!region->GetCodeValue())
  {
    this->CurrentRegionObject->Initialize();
    this->tableWidget_Region->blockSignals(true);
    this->tableWidget_Region->setCurrentCell(0, 0);
    this->tableWidget_Region->blockSignals(false);
    return false;
  }

  // Select region if found
  QTableWidgetItem* regionItem = this->findTableWidgetItemForType(this->tableWidget_Region, region);
  if (regionItem)
  {
    this->tableWidget_Region->blockSignals(true);
    this->tableWidget_Region->setCurrentItem(regionItem);
    this->tableWidget_Region->blockSignals(false);
  }
  return regionItem; // Return true if region found and selected
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentRegionModifier(vtkSlicerTerminologyType* modifier)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);

  if (!modifier)
  {
    this->CurrentRegionModifierObject->Initialize();
    qCritical() << Q_FUNC_INFO << ": Invalid region modifier object set";
    return false;
  }

  // Set current type modifier
  this->CurrentRegionModifierObject->Copy(modifier);

  // Select modifier if found
  int modifierIndex = this->findComboBoxIndexForModifier(this->ComboBox_RegionModifier, modifier);
  if (modifierIndex != -1)
  {
    this->ComboBox_RegionModifier->blockSignals(true);
    this->ComboBox_RegionModifier->setCurrentIndex(modifierIndex);
    this->ComboBox_RegionModifier->blockSignals(false);
  }
  return (modifierIndex != -1);
}

//-----------------------------------------------------------------------------
// qSlicerTerminologyNavigatorWidget methods

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::qSlicerTerminologyNavigatorWidget(QWidget* _parent)
  : qMRMLWidget(_parent)
  , d_ptr(new qSlicerTerminologyNavigatorWidgetPrivate(*this))
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->init();

  // Connect logic modified event (cannot call QVTK from private implementation)
  vtkSlicerTerminologiesModuleLogic* logic = qSlicerTerminologyNavigatorWidgetPrivate::terminologyLogic();
  if (logic)
  {
    qvtkConnect(logic, vtkCommand::ModifiedEvent, this, SLOT(onLogicModified()));
  }
}

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::~qSlicerTerminologyNavigatorWidget()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  if (qSlicerApplication::application())
  {
    QSettings* settings = qSlicerApplication::application()->userSettings();
    settings->setValue("Terminology/ShowCategorySelector", d->CategoryExpandButton->isChecked());
    settings->setValue("Terminology/ShowRegionSelector", d->RegionExpandButton->isChecked());
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::terminologyInfo(TerminologyInfoBundle& terminologyInfo)
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  this->terminologyEntry(terminologyInfo.GetTerminologyEntry());
  terminologyInfo.Name = d->lineEdit_Name->text();
  terminologyInfo.NameAutoGenerated = d->NameAutoGenerated;
  terminologyInfo.Color = d->ColorPickerButton_RecommendedRGB->color();
  terminologyInfo.ColorAutoGenerated = d->ColorAutoGenerated;
  terminologyInfo.GeneratedColor = d->GeneratedColor;
}

// public method to set terminology in GUI
//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setTerminologyInfo(TerminologyInfoBundle& terminologyInfo)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set terminology entry
  if (!this->setTerminologyEntry(terminologyInfo.GetTerminologyEntry()))
  {
    qWarning() << Q_FUNC_INFO << ": Failed to set terminology entry from given terminology info bundle";
  }
  bool noneType = (d->CurrentTypeObject->GetCodeValue() == nullptr);

  // Set name
  if (terminologyInfo.Name.isEmpty())
  {
    // If given name is empty, then generate it from terminology (auto-generate flag will be true)
    d->setNameFromCurrentTerminology();
  }
  else
  {
    d->lineEdit_Name->blockSignals(true); // Only call callback function if user changes from UI
    d->lineEdit_Name->setText(terminologyInfo.Name);
    d->lineEdit_Name->blockSignals(false);

    d->NameAutoGenerated = terminologyInfo.NameAutoGenerated;
    d->pushButton_ResetName->setEnabled(!d->NameAutoGenerated && !noneType);
  }

  // Store generated color. It is used when the selected terminology contains no recommended color
  if (terminologyInfo.GeneratedColor.isValid())
  {
    d->GeneratedColor = terminologyInfo.GeneratedColor;
  }

  // Set color
  if (!terminologyInfo.Color.isValid())
  {
    // If given color is invalid, then get it from terminology (auto-generate flag will be true)
    d->setRecommendedColorFromCurrentTerminology();
  }
  else
  {
    d->ColorPickerButton_RecommendedRGB->blockSignals(true); // Only call callback function if user changes from UI
    d->ColorPickerButton_RecommendedRGB->setColor(terminologyInfo.Color);
    d->ColorPickerButton_RecommendedRGB->blockSignals(false);

    d->ColorAutoGenerated = terminologyInfo.ColorAutoGenerated;

    bool enableResetColor = !d->ColorAutoGenerated //
                            || (d->ColorPickerButton_RecommendedRGB->color() != d->terminologyRecommendedColor());
    d->pushButton_ResetColor->setEnabled(enableResetColor && !noneType);
  }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::terminologyEntry(vtkSlicerTerminologyEntry* entry)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!entry)
  {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology entry object";
    return false;
  }
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology logic";
    return false;
  }

  if (!entry->GetCategoryObject() || !entry->GetTypeObject() || !entry->GetTypeModifierObject() //
      || !entry->GetRegionObject() || !entry->GetRegionModifierObject())
  {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology entry given";
    // Invalidate whole terminology entry
    entry->SetTerminologyContextName(nullptr);
    entry->SetRegionContextName(nullptr);
    return false;
  }

  // Terminology or color table name
  QString terminologyOrColorName = d->CurrentTerminologyName;
  if (!d->CurrentColorNodeID.isEmpty())
  {
    vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(logic->GetMRMLScene()->GetNodeByID(d->CurrentColorNodeID.toUtf8()));
    if (colorNode && colorNode->GetName())
    {
      terminologyOrColorName = QString::fromUtf8(colorNode->GetName());
    }
  }
  if (terminologyOrColorName.isEmpty())
  {
    // No terminology selected
    return false;
  }
  entry->SetTerminologyContextName(terminologyOrColorName.toUtf8().constData());

  // Terminology category
  if (!d->CurrentCategoryObject)
  {
    // No terminology category selected
    return false;
  }
  entry->GetCategoryObject()->Copy(d->CurrentCategoryObject);

  // Terminology type
  if (!d->CurrentTypeObject)
  {
    // No terminology type selected
    return false;
  }
  entry->GetTypeObject()->Copy(d->CurrentTypeObject);

  // Terminology type modifier
  if (d->CurrentTypeModifierObject)
  {
    entry->GetTypeModifierObject()->Copy(d->CurrentTypeModifierObject);
  }
  else
  {
    entry->GetTypeModifierObject()->Initialize();
  }

  // Region context name
  if (!d->CurrentRegionContextName.isEmpty())
  {
    entry->SetRegionContextName(d->CurrentRegionContextName.toUtf8().constData());
  }

  // Region
  if (d->CurrentRegionObject)
  {
    entry->GetRegionObject()->Copy(d->CurrentRegionObject);
  }
  else
  {
    entry->GetRegionObject()->Initialize();
  }

  // Region modifier
  if (d->CurrentRegionModifierObject)
  {
    entry->GetRegionModifierObject()->Copy(d->CurrentRegionModifierObject);
  }
  else
  {
    entry->GetRegionModifierObject()->Initialize();
  }

  return true;
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidgetPrivate::findTerminology(vtkSlicerTerminologyEntry* entry,
                                                               QString& terminologyNameToSelect,
                                                               QString& colorNodeIDToSelect,
                                                               int& colorIndexInColorTable)
{
  Q_Q(qSlicerTerminologyNavigatorWidget);
  vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return false;
  }
  if (this->ComboBox_Terminology->count() < 1)
  {
    return false;
  }

  // Get list of last terminology contexts selected by the user from application settings
  std::vector<std::string> preferredTerminologyNames;
  QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
  if (settings->contains("Terminology/LastTerminologyContexts"))
  {
    QStringList lastTerminologyContextNames = settings->value("Terminology/LastTerminologyContexts").toStringList();
    for (auto& name : lastTerminologyContextNames)
    {
      preferredTerminologyNames.push_back(name.toStdString().c_str());
    }
  }

  std::string foundColorNodeID;
  int foundColorIndexInColorTable{ -1 };
  std::string foundTerminologyName;
  if (logic->FindFirstColorNodeOrTerminology(entry, preferredTerminologyNames, foundTerminologyName, foundColorNodeID, foundColorIndexInColorTable))
  {
    terminologyNameToSelect = QString::fromStdString(foundTerminologyName);
    colorNodeIDToSelect = QString::fromStdString(foundColorNodeID);
    colorIndexInColorTable = foundColorIndexInColorTable;
  }
  else
  {
    // Not found, select the first terminology item by default
    terminologyNameToSelect = (!preferredTerminologyNames.empty() ? QString::fromStdString(preferredTerminologyNames[0]) : this->ComboBox_Terminology->itemText(0));
    colorNodeIDToSelect = this->ComboBox_Terminology->itemData(0).toString();
    colorIndexInColorTable = -1;
  }

  return true;
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::setTerminologyEntry(vtkSlicerTerminologyEntry* entry)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!entry)
  {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology entry object";
    return false;
  }

  QString terminologyName;
  QString colorNodeId;
  int colorIndexInColorTable = -1;
  if (!d->findTerminology(entry, terminologyName, colorNodeId, colorIndexInColorTable))
  {
    return false;
  }
  d->setCurrentTerminology(terminologyName, colorNodeId);

  bool returnValue = true;
  if (!colorNodeId.isEmpty())
  {
    // Select from color table
    d->ColorTableView->selectColorByIndex(colorIndexInColorTable);
  }
  else
  {
    // Select from terminology

    vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();

    // Select category
    vtkSlicerTerminologyCategory* categoryObject = entry->GetCategoryObject();
    d->setCurrentCategory(categoryObject);

    // Select type
    vtkSlicerTerminologyType* typeObject = entry->GetTypeObject();
    // Get additional metadata (such as recommended display RGB value or 3D Slicer label) by getting type object from terminology.
    vtkNew<vtkSlicerTerminologyType> resolvedTypeObject;
    if (logic && categoryObject && typeObject)
    {
      vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId = vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(categoryObject);
      vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeId = vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(typeObject);
      if (logic->GetTypeInTerminologyCategory(d->CurrentTerminologyName.toStdString(), categoryId, typeId, resolvedTypeObject))
      {
        typeObject = resolvedTypeObject;
      }
    }
    d->setCurrentType(typeObject);

    // Select type modifier
    vtkSlicerTerminologyType* typeModifierObject = entry->GetTypeModifierObject();
    if (typeObject && typeObject->GetHasModifiers() && typeModifierObject && typeModifierObject->GetCodeValue())
    {
      vtkNew<vtkSlicerTerminologyType> resolvedTypeModifierObject;
      if (logic && categoryObject)
      {
        vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId = //
          vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(categoryObject);
        vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeId = //
          vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(typeObject);
        vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeModifierId = //
          vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(typeModifierObject);
        if (logic->GetTypeModifierInTerminologyType(d->CurrentTerminologyName.toStdString(), categoryId, typeId, typeModifierId, resolvedTypeModifierObject))
        {
          typeModifierObject = resolvedTypeModifierObject;
        }
      }
      d->setCurrentTypeModifier(typeModifierObject);
    }

    // Set region context selection if category allows
    if (categoryObject->GetShowAnatomy())
    {
      // Select region context
      QString regionContextName(entry->GetRegionContextName() ? entry->GetRegionContextName() : "");
      if (regionContextName.isEmpty())
      {
        QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
        if (settings->contains("Terminology/LastRegionContext"))
        {
          QString lastRegionContextName = settings->value("Terminology/LastRegionContext").toString();
          if (!lastRegionContextName.isEmpty())
          {
            regionContextName = lastRegionContextName;
          }
        }
      }
      if (!regionContextName.isEmpty()) // Optional
      {
        int regionContextIndex = d->ComboBox_RegionContext->findText(regionContextName);
        if (regionContextIndex == -1)
        {
          qCritical() << Q_FUNC_INFO << ": Failed to find region context with context name " << regionContextName;
          returnValue = false;
        }
        if (regionContextIndex != d->ComboBox_RegionContext->currentIndex())
        {
          d->setCurrentRegionContext(d->ComboBox_RegionContext->itemText(regionContextIndex));
        }
        d->ComboBox_RegionContext->blockSignals(true);
        d->ComboBox_RegionContext->setCurrentIndex(regionContextIndex);
        d->ComboBox_RegionContext->blockSignals(false);
      }

      // Select region
      vtkSlicerTerminologyType* regionObject = entry->GetRegionObject();
      if (regionObject) // Optional
      {
        d->setCurrentRegion(regionObject);

        // Select region modifier
        vtkSlicerTerminologyType* regionModifierObject = entry->GetRegionModifierObject();
        if (regionObject->GetHasModifiers() && regionModifierObject)
        {
          d->setCurrentRegionModifier(regionModifierObject);
        }
      } // If region is selected
    } // If showAnatomy is true
    else
    {
      // Set region context combobox selection to the last selected context anyway, so that when
      // the user changes category, the selected region context is the last selected one
      QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
      if (settings->contains("Terminology/LastRegionContext"))
      {
        QString lastRegionContextName = settings->value("Terminology/LastRegionContext").toString();
        int lastRegionContextIndex = d->ComboBox_RegionContext->findText(lastRegionContextName);
        if (lastRegionContextIndex >= 0)
        {
          d->ComboBox_RegionContext->blockSignals(true);
          d->ComboBox_RegionContext->setCurrentIndex(lastRegionContextIndex);
          d->ComboBox_RegionContext->blockSignals(false);

          d->setCurrentRegionContext(lastRegionContextName);
        }
      }
    }
  }

  QTimer::singleShot(0, this, SLOT(setInitialFocus()));

  return returnValue;
}

//-----------------------------------------------------------------------------
QString qSlicerTerminologyNavigatorWidget::nameFromTerminology(vtkSlicerTerminologyEntry* entry)
{
  QString name;
  if (!entry->GetTypeObject() || entry->GetTypeObject()->IsEmpty())
  {
    // Incomplete terminology selection, name is empty
    return name;
  }

  // Try to set name based on '3dSlicerLabel' field in terminology entry
  if (entry->GetTypeObject()->GetSlicerLabel() //
      && (!entry->GetTypeObject()->GetHasModifiers() || entry->GetTypeModifierObject()->GetCodeValue() == nullptr))
  {
    name = entry->GetTypeObject()->GetSlicerLabel();
  }
  else if (entry->GetTypeModifierObject()->GetSlicerLabel())
  {
    name = entry->GetTypeModifierObject()->GetSlicerLabel();
  }
  // Assemble from selection if there is no label
  if (name.isEmpty())
  {
    // Set type and modifier in name
    if (entry->GetTypeObject()->GetHasModifiers() && entry->GetTypeModifierObject() && entry->GetTypeModifierObject()->GetCodeValue())
    {
      //: For formatting of terminology entry with a modifier. %1 is structure name (e.g., "Kidney"), %2 is modifier (e.g., "Left")
      name = tr("%1, %2").arg(entry->GetTypeObject()->GetCodeMeaning()).arg(entry->GetTypeModifierObject()->GetCodeMeaning());
    }
    else
    {
      name = entry->GetTypeObject()->GetCodeMeaning();
    }
  }

  if (entry->GetRegionObject() && entry->GetRegionObject()->GetCodeValue() //
      && strlen(entry->GetRegionObject()->GetCodeValue()) > 0)
  {
    if (entry->GetRegionModifierObject() && entry->GetRegionModifierObject()->GetCodeValue() //
        && strlen(entry->GetRegionModifierObject()->GetCodeValue()) > 0)
    {
      //: For formatting of terminology entry name. %1 is type name (e.g., "Mass"), %2 is region name (e.g., "Kidney"), %2 is region modifier (e.g., "Left")
      name = tr("%1 in %2, %3").arg(name).arg(entry->GetRegionObject()->GetCodeMeaning()).arg(entry->GetRegionModifierObject()->GetCodeMeaning());
    }
    else
    {
      //: For formatting of terminology entry name. %1 is type name (e.g., "Mass"), %2 is region name (e.g., "Liver")
      name = tr("%1 in %2").arg(name).arg(entry->GetRegionObject()->GetCodeMeaning());
    }
  }

  if (name.isEmpty())
  {
    qCritical() << Q_FUNC_INFO << ": Failed to generate name";
  }
  return name;
}

//-----------------------------------------------------------------------------
QString qSlicerTerminologyNavigatorWidget::nameFromCurrentTerminology()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  if (!d->CurrentTypeObject || //
      (d->CurrentTypeObject->GetHasModifiers() && !d->CurrentTypeModifierObject))
  {
    // Incomplete terminology selection, name is empty
    return QString();
  }
  vtkNew<vtkSlicerTerminologyEntry> terminologyEntry;
  if (!this->terminologyEntry(terminologyEntry))
  {
    // Failed to get current terminology
    return QString();
  }

  // Serialized terminology entries may only contain code triples and not 3dSlicerLabel.
  // Resolve full terminology objects from logic so reset-name uses the same generated label
  // as interactive terminology selection.
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (logic && !d->CurrentTerminologyName.isEmpty())
  {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId = //
      vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(terminologyEntry->GetCategoryObject());
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeId = //
      vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(terminologyEntry->GetTypeObject());

    vtkNew<vtkSlicerTerminologyType> resolvedType;
    if (logic->GetTypeInTerminologyCategory(d->CurrentTerminologyName.toStdString(), categoryId, typeId, resolvedType))
    {
      terminologyEntry->GetTypeObject()->Copy(resolvedType);

      if (resolvedType->GetHasModifiers() && terminologyEntry->GetTypeModifierObject() && terminologyEntry->GetTypeModifierObject()->GetCodeValue())
      {
        vtkSlicerTerminologiesModuleLogic::CodeIdentifier modifierId =
          vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(terminologyEntry->GetTypeModifierObject());
        vtkNew<vtkSlicerTerminologyType> resolvedModifier;
        if (logic->GetTypeModifierInTerminologyType(d->CurrentTerminologyName.toStdString(), categoryId, typeId, modifierId, resolvedModifier))
        {
          terminologyEntry->GetTypeModifierObject()->Copy(resolvedModifier);
        }
      }
    }
  }

  return qSlicerTerminologyNavigatorWidget::nameFromTerminology(terminologyEntry);
}

//-----------------------------------------------------------------------------
QColor qSlicerTerminologyNavigatorWidget::recommendedColorFromTerminology(vtkSlicerTerminologyEntry* entry)
{
  QColor color;
  if (!entry || !entry->GetTypeObject())
  {
    return color;
  }

  vtkSlicerTerminologyType* typeObject = entry->GetTypeObject();
  if (typeObject->GetHasModifiers())
  {
    // Get color from modifier if any
    typeObject = entry->GetTypeModifierObject();
  }

  unsigned char colorChar[3] = { 0, 0, 0 };
  typeObject->GetRecommendedDisplayRGBValue(colorChar);
  color.setRgb(colorChar[0], colorChar[1], colorChar[2]);
  return color;
}

//-----------------------------------------------------------------------------
QColor qSlicerTerminologyNavigatorWidget::recommendedColorFromCurrentTerminology()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  QColor color;
  if (d->CurrentRegionContextName.isEmpty() || !d->CurrentCategoryObject || !d->CurrentTypeObject)
  {
    qWarning() << Q_FUNC_INFO << ": Invalid current terminology";
    return color;
  }

  vtkSlicerTerminologyType* typeObject = d->CurrentTypeObject;
  if (d->CurrentTypeObject->GetHasModifiers())
  {
    // Get color from modifier if any
    typeObject = d->CurrentTypeModifierObject;
  }

  unsigned char colorChar[3] = { 0, 0, 0 };
  typeObject->GetRecommendedDisplayRGBValue(colorChar);
  color.setRgb(colorChar[0], colorChar[1], colorChar[2]);
  return color;
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::regionSectionVisible() const
{
  Q_D(const qSlicerTerminologyNavigatorWidget);

  return d->RegionExpandButton->isChecked();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setRegionSectionVisible(bool visible)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->RegionExpandButton->setChecked(visible);
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::overrideSectionVisible() const
{
  Q_D(const qSlicerTerminologyNavigatorWidget);
  return d->frame_TerminologyOverride->isVisible();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setOverrideSectionVisible(bool visible)
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->frame_TerminologyOverride->setVisible(visible);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTerminologySelectionChanged(int index)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Make selection in the other combobox as well
  ctkComboBox* visibleComboBox = (d->ComboBox_Terminology->isVisible() ? d->ComboBox_Terminology : d->ComboBox_Terminology_2);
  ctkComboBox* invisibleComboBox = (!d->ComboBox_Terminology->isVisible() ? d->ComboBox_Terminology : d->ComboBox_Terminology_2);
  QSignalBlocker blocker(invisibleComboBox);
  invisibleComboBox->setCurrentIndex(visibleComboBox->currentIndex());

  // Set current terminology
  QString terminologyName = visibleComboBox->itemText(index);
  QString colorNodeId = visibleComboBox->itemData(index).toString();
  d->setCurrentTerminology(terminologyName, colorNodeId);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onCategorySelectionChanged()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  QList<QTableWidgetItem*> selectedItems = d->tableWidget_Category->selectedItems();

  if (selectedItems.count() == 0)
  {
    // Happens when clearing the table
    return;
  }

  // Reset current category because it will be set when the type is selected
  d->CurrentCategoryObject->Initialize();
  // Reset current type and type modifier
  d->CurrentTypeObject->Initialize();
  d->CurrentTypeModifierObject->Initialize();
  // Reset region information as well
  d->CurrentRegionObject->Initialize();
  d->CurrentRegionModifierObject->Initialize();

  // Get current category object
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  bool showAnatomyOnInAnyCategories = false;

  d->SelectedCategoryObjects.clear();
  for (QTableWidgetItem* const currentItem : selectedItems)
  {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId(currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(),
                                                                 currentItem->data(CodeValueRole).toString().toUtf8().constData(),
                                                                 currentItem->text().toUtf8().constData());
    vtkSmartPointer<vtkSlicerTerminologyCategory> category = vtkSmartPointer<vtkSlicerTerminologyCategory>::New();
    if (!logic->GetCategoryInTerminology(d->CurrentTerminologyName.toUtf8().constData(), categoryId, category))
    {
      qCritical() << Q_FUNC_INFO << ": Failed to find category '" << currentItem->text();
      continue;
    }

    showAnatomyOnInAnyCategories |= category->GetShowAnatomy();

    d->SelectedCategoryObjects << category;
  }

  // Populate type table, and reset type modifier combobox and region widgets
  d->populateTypeTable();
  d->populateTypeModifierComboBox();
  d->populateRegionModifierComboBox();

  // Only enable type table if there are items in it
  if (d->tableWidget_Type->rowCount() == 1) // None item always present
  {
    d->ComboBox_TypeModifier->setEnabled(false);
  }
  else
  {
    d->SearchBox_Type->setEnabled(true);
  }

  // Enable region controls if related flag is on
  d->ComboBox_RegionContext->setEnabled(showAnatomyOnInAnyCategories);
  d->tableWidget_Region->setEnabled(showAnatomyOnInAnyCategories);
  d->SearchBox_Region->setEnabled(showAnatomyOnInAnyCategories);
  d->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection

  // Selection is invalid until type is selected
  emit selectionValidityChanged(false);

  // Generate name based on selection (empty name in this case) if not custom
  if (d->NameAutoGenerated)
  {
    d->setNameFromCurrentTerminology();
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onSelectAllCategoriesButtonClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->tableWidget_Category->selectAll();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTypeSelected(QTableWidgetItem* currentItem, QTableWidgetItem* previousItem)
{
  Q_UNUSED(previousItem);
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!currentItem)
  {
    d->CurrentTypeObject->Initialize();
    d->CurrentTypeModifierObject->Initialize();
    return;
  }

  // Reset terminology elements if none item is selected
  if (!currentItem->text().compare(d->NoneItemName))
  {
    // Do not reset category because it invalidates entry when setting terminology
    // programmatically using setTerminologyEntry
    d->CurrentTypeObject->Initialize();
    d->CurrentTypeModifierObject->Initialize();
    d->CurrentRegionObject->Initialize();
    d->CurrentRegionModifierObject->Initialize();
    return;
  }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  // Get category object for selected type object
  vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId(currentItem->data(CategoryCodingSchemeDesignatorRole).toString().toUtf8().constData(),
                                                               currentItem->data(CategoryCodeValueRole).toString().toUtf8().constData(),
                                                               currentItem->data(CategoryCodeMeaningRole).toString().toUtf8().constData());
  vtkSmartPointer<vtkSlicerTerminologyCategory> category = vtkSmartPointer<vtkSlicerTerminologyCategory>::New();
  if (!logic->GetCategoryInTerminology(d->CurrentTerminologyName.toUtf8().constData(), categoryId, category))
  {
    qCritical() << Q_FUNC_INFO << ": Failed to find category '" << categoryId.CodeMeaning.c_str();
    return;
  }

  // Get current type object
  vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeId(currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(),
                                                           currentItem->data(CodeValueRole).toString().toUtf8().constData(),
                                                           currentItem->text().toUtf8().constData());
  vtkSmartPointer<vtkSlicerTerminologyType> type = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  if (!logic->GetTypeInTerminologyCategory(d->CurrentTerminologyName.toUtf8().constData(), categoryId, typeId, type))
  {
    qCritical() << Q_FUNC_INFO << ": Failed to find type '" << currentItem->text();
    return;
  }

  // Set current category object for selected type
  d->CurrentCategoryObject->Copy(category);
  d->updateWidgetFromCurrentCategory();
  // Set type from item
  d->setCurrentType(type);

  // Update state of anatomy controls based on the category of the selected type
  d->ComboBox_RegionContext->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->tableWidget_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->SearchBox_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection

  // Generate name based on selection if not custom
  if (d->NameAutoGenerated)
  {
    d->setNameFromCurrentTerminology();
  }
  d->pushButton_ResetName->setEnabled(!d->NameAutoGenerated);

  // Set recommended color to color picker if not custom
  if (d->ColorAutoGenerated)
  {
    d->setRecommendedColorFromCurrentTerminology();
  }
  d->pushButton_ResetColor->setEnabled(!d->ColorAutoGenerated);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTypeCellDoubleClicked(int row, int column)
{
  Q_UNUSED(row);
  Q_UNUSED(column);
  emit typeDoubleClicked();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onColorSelected(const QItemSelection& selected, const QItemSelection& deselected)
{
  Q_UNUSED(selected);
  Q_UNUSED(deselected);
  Q_D(qSlicerTerminologyNavigatorWidget);
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }
  if (!d->ColorTableView->currentIndex().isValid())
  {
    return;
  }

  // Get color node
  vtkMRMLColorNode* colorNode = d->ColorTableView->mrmlColorNode();

  // Get color index from selected model index
  int colorIndex = d->ColorTableView->selectedColorIndex();

  if (colorNode->GetTerminologyCategory(colorIndex) == nullptr || colorNode->GetTerminologyCategory(colorIndex)->GetCodeMeaning() == nullptr //
      || colorNode->GetTerminologyType(colorIndex) == nullptr || colorNode->GetTerminologyType(colorIndex)->GetCodeMeaning() == nullptr)
  {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology in selected color (" << colorNode->GetName() << ": " << colorIndex << ")";
    emit selectionValidityChanged(false);
    return;
  }

  // Reset current terminology information
  d->CurrentCategoryObject->Initialize();
  d->CurrentTypeObject->Initialize();
  d->CurrentTypeModifierObject->Initialize();
  d->CurrentRegionObject->Initialize();
  d->CurrentRegionModifierObject->Initialize();

  // Set category and type
  d->CurrentCategoryObject->vtkCodedEntry::Copy(colorNode->GetTerminologyCategory(colorIndex));
  d->CurrentTypeObject->vtkCodedEntry::Copy(colorNode->GetTerminologyType(colorIndex));
  double color[4] = { 0.0 };
  colorNode->GetColor(colorIndex, color);
  d->CurrentTypeObject->SetRecommendedDisplayRGBValue((unsigned char)(color[0] * 255.0), (unsigned char)(color[1] * 255.0), (unsigned char)(color[2] * 255.0));
  emit selectionValidityChanged(true);

  // Set optional information if any
  if (colorNode->GetTerminologyTypeModifier(colorIndex) != nullptr //
      && colorNode->GetTerminologyTypeModifier(colorIndex)->GetCodeMeaning() != nullptr)
  {
    d->CurrentTypeModifierObject->vtkCodedEntry::Copy(colorNode->GetTerminologyTypeModifier(colorIndex));
  }
  else
  {
    d->CurrentTypeModifierObject->Initialize();
  }
  if (colorNode->GetTerminologyRegion(colorIndex) != nullptr //
      && colorNode->GetTerminologyRegion(colorIndex)->GetCodeMeaning() != nullptr)
  {
    d->CurrentRegionObject->vtkCodedEntry::Copy(colorNode->GetTerminologyRegion(colorIndex));
  }
  else
  {
    d->CurrentRegionObject->Initialize();
  }
  if (colorNode->GetTerminologyRegionModifier(colorIndex) != nullptr //
      && colorNode->GetTerminologyRegionModifier(colorIndex)->GetCodeMeaning() != nullptr)
  {
    d->CurrentRegionModifierObject->vtkCodedEntry::Copy(colorNode->GetTerminologyRegionModifier(colorIndex));
  }
  else
  {
    d->CurrentRegionModifierObject->Initialize();
  }

  // Set name and color
  d->lineEdit_Name->setText(colorNode->GetColorName(colorIndex));
  d->NameAutoGenerated = false;
  d->pushButton_ResetName->setEnabled(!d->NameAutoGenerated);
  double rgba[4] = { 0.0, 0.0, 0.0, 0.0 };
  colorNode->GetColor(colorIndex, rgba);
  d->ColorPickerButton_RecommendedRGB->setColor(QColor::fromRgbF(rgba[0], rgba[1], rgba[2], rgba[3]));
  d->ColorAutoGenerated = false;
  d->pushButton_ResetColor->setEnabled(!d->ColorAutoGenerated);
}
//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onColorRowDoubleClicked(const QModelIndex& index)
{
  Q_UNUSED(index);
  emit colorDoubleClicked();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTypeModifierSelectionChanged(int index)
{
  Q_UNUSED(index);
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Invalidate type modifier if there are no modifiers for the current type
  if (d->ComboBox_TypeModifier->count() == 0)
  {
    d->CurrentTypeModifierObject->Initialize();
    return;
  }

  vtkSmartPointer<vtkSlicerTerminologyType> modifier = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  if (index < 0)
  {
    // If new index is invalid (happens on clearing the combobox), then set empty modifier
    d->setCurrentTypeModifier(modifier);
    return;
  }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }

  // Get current modifier object is not None modifier is selected
  if (index != 0 || !d->ComboBox_TypeModifier->itemData(index).isNull())
  {
    QMap<QString, QVariant> userData = d->ComboBox_TypeModifier->itemData(index).toMap();
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier modifierId(userData[QString::number(CodingSchemeDesignatorRole)].toString().toUtf8().constData(),
                                                                 userData[QString::number(CodeValueRole)].toString().toUtf8().constData(),
                                                                 d->ComboBox_TypeModifier->itemText(index).toUtf8().constData());
    if (!logic->GetTypeModifierInTerminologyType(d->CurrentTerminologyName.toUtf8().constData(),
                                                 vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentCategoryObject),
                                                 vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentTypeObject),
                                                 modifierId,
                                                 modifier))
    {
      qCritical() << Q_FUNC_INFO << ": Failed to find modifier '" << d->ComboBox_TypeModifier->itemText(index);
      return;
    }
  }

  // Set current type modifier
  d->setCurrentTypeModifier(modifier);

  // Generate name based on selection if not custom
  if (d->NameAutoGenerated)
  {
    d->setNameFromCurrentTerminology();
  }
  // Set recommended color to color picker if not custom
  if (d->ColorAutoGenerated)
  {
    d->setRecommendedColorFromCurrentTerminology();
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onCategorySearchTextChanged(QString search)
{
  Q_UNUSED(search);
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->populateCategoryTable();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTypeSearchTextChanged(QString search)
{
  Q_UNUSED(search);
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->populateTypeTable();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onNameChanged(QString name)
{
  Q_UNUSED(name);
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set as manual
  d->NameAutoGenerated = false;

  // Enable reset name button
  d->pushButton_ResetName->setEnabled(true);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onResetNameClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->setNameFromCurrentTerminology();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onColorChanged(QColor color)
{
  Q_UNUSED(color);
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set as manual
  d->ColorAutoGenerated = false;

  // Enable reset color button
  d->pushButton_ResetColor->setEnabled(true);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onResetColorClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->setRecommendedColorFromCurrentTerminology();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onLoadTerminologyClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  const QString& terminologyFileName = QFileDialog::getOpenFileName(this, "Select terminology json file...", QString(), "Json files (*.json);; All files (*)");
  if (!terminologyFileName.isEmpty())
  {
    vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
    if (!logic)
    {
      qCritical() << Q_FUNC_INFO << ": Invalid terminology logic";
      return;
    }
    QString loadedContextName = logic->LoadTerminologyFromFile(terminologyFileName.toUtf8().constData()).c_str();
    if (!loadedContextName.isEmpty())
    {
      QMessageBox::information(this, "Load succeeded", QString("Loading terminology context named %1 succeeded").arg(loadedContextName));

      d->copyContextToUserDirectory(terminologyFileName);
    }
    else
    {
      QString errorMessage = "Loading terminology from file %1 failed!<br><br>"
                             "Please check validity of the file using the <a href=\"https://qiicr.org/dcmqi/#/validators\">online validator tool</a>.";
      QMessageBox::critical(this, "Load failed", errorMessage.arg(terminologyFileName));
    }
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onLoadRegionContextClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  const QString& regionContextFileName = QFileDialog::getOpenFileName(this, "Select region context json file...", QString(), "Json files (*.json);; All files (*)");
  if (!regionContextFileName.isEmpty())
  {
    vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
    if (!logic)
    {
      qCritical() << Q_FUNC_INFO << ": Invalid terminology logic";
      return;
    }
    QString loadedContextName = logic->LoadRegionContextFromFile(regionContextFileName.toUtf8().constData()).c_str();
    if (!loadedContextName.isEmpty())
    {
      QMessageBox::information(this, "Load succeeded", QString("Loading region context named %1 succeeded").arg(loadedContextName));

      d->copyContextToUserDirectory(regionContextFileName);
    }
    else
    {
      QString errorMessage = "Loading region context from file %1 failed!<br><br>"
                             "Please check validity of the file using the <a href=\"https://qiicr.org/dcmqi/#/validators\">online validator tool</a>.";
      QMessageBox::critical(this, "Load failed", errorMessage.arg(regionContextFileName));
    }
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionContextSelectionChanged(int index)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set current region context
  QString regionContextName = d->ComboBox_RegionContext->itemText(index);
  d->setCurrentRegionContext(regionContextName);

  // Save last selection to application settings
  if (!d->RegionContextComboboxPopulating)
  {
    QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
    settings->setValue("Terminology/LastRegionContext", regionContextName);
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionSelected(QTableWidgetItem* currentItem, QTableWidgetItem* previousItem)
{
  Q_UNUSED(previousItem);
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!currentItem)
  {
    d->CurrentRegionObject->Initialize();
    d->CurrentRegionModifierObject->Initialize();
    return;
  }

  // Get current region object
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }
  vtkSmartPointer<vtkSlicerTerminologyType> region = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  if (!currentItem->data(CodeValueRole).isNull())
  {
    // Not the "None" item is selected
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier regionId(currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(),
                                                               currentItem->data(CodeValueRole).toString().toUtf8().constData(),
                                                               currentItem->text().toUtf8().constData());
    if (!logic->GetRegionInRegionContext(d->CurrentRegionContextName.toUtf8().constData(), regionId, region))
    {
      qCritical() << Q_FUNC_INFO << ": Failed to find region '" << currentItem->text();
      return;
    }
  }
  // Set current region
  d->setCurrentRegion(region);

  // Generate name based on selection if not custom
  if (d->NameAutoGenerated)
  {
    d->setNameFromCurrentTerminology();
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionModifierSelectionChanged(int index)
{
  Q_UNUSED(index);
  Q_D(qSlicerTerminologyNavigatorWidget);

  vtkSmartPointer<vtkSlicerTerminologyType> modifier = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  if (index < 0)
  {
    // If new index is invalid (happens on clearing the combobox), then set empty modifier
    d->setCurrentRegionModifier(modifier);
    return;
  }

  // Get current modifier object
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
  {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
  }
  if (index > 0)
  {
    // Modifier selected (not the "No region modifier" item, which is at index = 0)
    QMap<QString, QVariant> userData = d->ComboBox_RegionModifier->itemData(index).toMap();
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier modifierId(userData[QString::number(CodingSchemeDesignatorRole)].toString().toUtf8().constData(),
                                                                 userData[QString::number(CodeValueRole)].toString().toUtf8().constData(),
                                                                 d->ComboBox_RegionModifier->itemText(index).toUtf8().constData());
    if (!logic->GetRegionModifierInRegion(
          d->CurrentRegionContextName.toUtf8().constData(), vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentRegionObject), modifierId, modifier))
    {
      qCritical() << Q_FUNC_INFO << ": Failed to find modifier '" << d->ComboBox_RegionModifier->itemText(index);
      return;
    }
  }

  // Set current region modifier
  d->setCurrentRegionModifier(modifier);

  // Generate name based on selection if not custom
  if (d->NameAutoGenerated)
  {
    d->setNameFromCurrentTerminology();
  }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionSearchTextChanged(QString search)
{
  Q_UNUSED(search);
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->populateRegionTable();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onLogicModified()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->populateTerminologyComboBox();
  d->CurrentCategoryObject->Initialize();

  d->populateRegionContextComboBox();
  d->CurrentRegionObject->Initialize();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionExpandButtonUp()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->RegionExpandButton->setDown(false);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionExpandButtonDown()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->RegionExpandButton->setDown(true);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setInitialFocus()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  if (d->ColorTableView->isVisible())
  {
    int colorIndex = d->ColorTableView->selectedColorIndex();
    if (colorIndex >= 0)
    {
      d->ColorTableView->selectColorByIndex(colorIndex);
    }
    d->ColorTableView->setFocus();
  }
  else
  {
    if (d->NameAutoGenerated)
    {
      d->SearchBox_Type->setFocus();
    }
    else
    {
      d->lineEdit_Name->setFocus();
    }
  }
}
