diff --git a/src/ScalarAction.cpp b/src/ScalarAction.cpp index 0c8ef8d..f151703 100644 --- a/src/ScalarAction.cpp +++ b/src/ScalarAction.cpp @@ -43,6 +43,7 @@ void ScalarAction::addDataset(const Dataset& dataset) sourceModel.addDataset(dataset); + /* TODO: this connection is not removed when the dataset is removed from the model, but that should not cause any issues since the dataset will be invalid and the connection will not do anything in that case connect(&sourceModel.getDatasets().last(), &Dataset::dataChanged, this, [this, dataset]() { const auto currentDataset = getCurrentDataset(); @@ -55,6 +56,8 @@ void ScalarAction::addDataset(const Dataset& dataset) emit sourceDataChanged(dataset); }); +*/ + connect(&_magnitudeAction, &DecimalAction::valueChanged, this, [this, dataset](const float& value) { emit magnitudeChanged(value); }); @@ -72,20 +75,20 @@ Dataset ScalarAction::getCurrentDataset() const auto currentSourceIndex = _sourceAction.getPickerAction().getCurrentIndex(); if (currentSourceIndex < ScalarSourceModel::DefaultRow::DatasetStart) - return Dataset(); + return {}; return scalarSourceModel.getDataset(currentSourceIndex); } void ScalarAction::setCurrentDataset(const Dataset& dataset) { - const auto datasetRowIndex = _sourceAction.getModel().rowIndex(dataset); + const auto datasetRowIndex = _sourceAction.getModel().getRowIndex(dataset); if (datasetRowIndex >= 0) _sourceAction.getPickerAction().setCurrentIndex(datasetRowIndex); } -void ScalarAction::setCurrentSourceIndex(bool sourceIndex) +void ScalarAction::setCurrentSourceIndex(std::int32_t sourceIndex) { _sourceAction.getPickerAction().setCurrentIndex(sourceIndex); } diff --git a/src/ScalarAction.h b/src/ScalarAction.h index 39ee126..b24f375 100644 --- a/src/ScalarAction.h +++ b/src/ScalarAction.h @@ -51,7 +51,7 @@ class ScalarAction : public GroupAction * Set the current source index * @param sourceIndex Source index */ - void setCurrentSourceIndex(bool sourceIndex); + void setCurrentSourceIndex(std::int32_t sourceIndex); /** Determines whether the scalar source is a constant */ bool isSourceConstant() const; diff --git a/src/ScalarSourceModel.cpp b/src/ScalarSourceModel.cpp index d3220ce..3d0657d 100644 --- a/src/ScalarSourceModel.cpp +++ b/src/ScalarSourceModel.cpp @@ -8,189 +8,236 @@ using namespace mv; using namespace mv::util; ScalarSourceModel::ScalarSourceModel(QObject* parent /*= nullptr*/) : - QAbstractListModel(parent), + QStandardItemModel(parent), _showFullPathName(true) { + appendRow(Row(*this, {})); // Constant source + appendRow(Row(*this, {})); // Selection source } -int ScalarSourceModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const +ScalarSourceModel::Item::Item(const ScalarSourceModel& scalarSourceModel, const mv::Dataset<>& scalarDataset) : + _scalarSourceModel(scalarSourceModel), + _scalarDataset(scalarDataset) { - // Constant point size option plus the number of available datasets - return _datasets.count() + DefaultRow::DatasetStart; } -int ScalarSourceModel::rowIndex(const Dataset& dataset) const +QVariant ScalarSourceModel::Item::data(int role) const { - // Only proceed if we have a valid dataset - if (!dataset.isValid()) - return -1; + const auto rowIndex = row(); + + switch (role) + { + // Return ruler icon for constant point size and dataset icon otherwise + case Qt::DecorationRole: + { + if (rowIndex == DefaultRow::Constant) + return StyledIcon("ruler"); + + if (rowIndex == DefaultRow::Selection) + return StyledIcon("mouse-pointer"); + + if (rowIndex >= DefaultRow::DatasetStart) + return _scalarDataset->icon(); + + break; + } - // Return the index of the dataset and add one for the constant point size option - return _datasets.indexOf(dataset) + DefaultRow::DatasetStart; + // Return 'Constant' for constant point size and dataset (full path) GUI name otherwise + case Qt::DisplayRole: + { + if (rowIndex >= DefaultRow::DatasetStart) + { + if (rowIndex == 2) + return _scalarDataset->text(); + else + return getScalarSourceModel().getShowFullPathName() ? getScalarDataset()->getLocation() : getScalarDataset()->getGuiName(); + } + else { + if (rowIndex == DefaultRow::Constant) + return "Constant"; + + if (rowIndex == DefaultRow::Selection) + return "Selection"; + } + } + + default: + break; + } + + return {}; } -int ScalarSourceModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const +const ScalarSourceModel& ScalarSourceModel::Item::getScalarSourceModel() const { - return 1; + return _scalarSourceModel; } -QVariant ScalarSourceModel::data(const QModelIndex& index, int role) const +const mv::Dataset<>& ScalarSourceModel::Item::getScalarDataset() const { - // Get row/column of and smart pointer to the dataset - const auto row = index.row(); - const auto column = index.column(); - const auto scalarDataset = getDataset(row); + return _scalarDataset; +} + +ScalarSourceModel::NameItem::NameItem(const ScalarSourceModel& scalarSourceModel, const mv::Dataset<>& scalarDataset) : + Item(scalarSourceModel, scalarDataset) +{ + connect(&const_cast&>(getScalarDataset()), &Dataset<>::guiNameChanged, this, [this]() { + emitDataChanged(); + }); +} + +QVariant ScalarSourceModel::NameItem::data(int role) const +{ + const auto rowIndex = row(); switch (role) { - // Return ruler icon for constant point size and dataset icon otherwise - case Qt::DecorationRole: - { - if (row == DefaultRow::Constant) - return StyledIcon("ruler"); + case Qt::DisplayRole: + case Qt::EditRole: + { + if (rowIndex == DefaultRow::Constant) + return "Constant"; - if (row == DefaultRow::Selection) - return StyledIcon("mouse-pointer"); + if (rowIndex == DefaultRow::Selection) + return "Selection"; - if (row >= DefaultRow::DatasetStart) - return scalarDataset->icon(); + if (rowIndex >= DefaultRow::DatasetStart) + return getScalarDataset()->getGuiName(); break; - } + } - // Return 'Constant' for constant point size and dataset (full path) GUI name otherwise - case Qt::DisplayRole: + case Qt::ToolTipRole: { - if (row >= DefaultRow::DatasetStart) - { - if (row == 2) - return scalarDataset->text(); - else - return _showFullPathName ? scalarDataset->getLocation() : scalarDataset->text(); - } else { - if (row == DefaultRow::Constant) - return "Constant"; - - if (row == DefaultRow::Selection) - return "Selection"; - } + if (rowIndex == DefaultRow::Constant) + return "Constant"; + + if (rowIndex == DefaultRow::Selection) + return "Selection"; + + if (rowIndex >= DefaultRow::DatasetStart) + return getScalarDataset()->getLocation(); break; } - default: - break; + default: + break; } - return QVariant(); + return Item::data(role); } -void ScalarSourceModel::addDataset(const Dataset& dataset) +QVariant ScalarSourceModel::IdItem::data(int role) const { - // Avoid duplicates - if (hasDataset(dataset)) - return; + const auto rowIndex = row(); - // Insert row into model - beginInsertRows(QModelIndex(), rowCount(), rowCount()); + switch (role) { - // Add the dataset - _datasets << dataset; - } - endInsertRows(); + case Qt::DisplayRole: + case Qt::EditRole: + { + if (rowIndex == DefaultRow::Constant || rowIndex == DefaultRow::Selection) + return ""; - // Get smart pointer to last added dataset - auto& addedDataset = _datasets.last(); + if (rowIndex >= DefaultRow::DatasetStart) + return getScalarDataset()->getId(); - // Remove a dataset from the model when it is about to be deleted - connect(&addedDataset, &Dataset::aboutToBeRemoved, this, [this, &addedDataset]() { - removeDataset(addedDataset); - }); + break; + } - // Notify others that the model has updated when the dataset GUI name changes - connect(addedDataset.get(), &DatasetImpl::textChanged, this, [this, &addedDataset]() { + case Qt::ToolTipRole: + { + if (rowIndex == DefaultRow::Constant || rowIndex == DefaultRow::Selection) + return ""; - // Get row index of the dataset - const auto colorDatasetRowIndex = rowIndex(addedDataset); + if (rowIndex >= DefaultRow::DatasetStart) + return "ID: " + getScalarDataset()->getId(); - // Only proceed if we found a valid row index - if (colorDatasetRowIndex < 0) - return; + break; + } - // Establish model index - const auto modelIndex = index(colorDatasetRowIndex, 0); + default: + break; + } - // Only proceed if we have a valid model index - if (!modelIndex.isValid()) - return; + return Item::data(role); +} - // Notify others that the data changed - emit dataChanged(modelIndex, modelIndex); - }); +void ScalarSourceModel::addDataset(const Dataset& dataset) +{ + if (hasDataset(dataset)) + return; + + appendRow(Row(*this, dataset)); } bool ScalarSourceModel::hasDataset(const Dataset& dataset) const { - return rowIndex(dataset) >= DefaultRow::DatasetStart; + if (!dataset.isValid()) + return false; + else + return !match(index(0, static_cast(Column::Id)), Qt::EditRole, dataset->getId(), 1, Qt::MatchExactly).isEmpty(); } void ScalarSourceModel::removeDataset(const Dataset& dataset) { - // Get row index of the dataset - const auto datasetRowIndex = rowIndex(dataset); + if (!hasDataset(dataset)) + return; - // Update model - beginRemoveRows(QModelIndex(), datasetRowIndex, datasetRowIndex); - { - // Remove dataset from internal vector - _datasets.removeOne(dataset); - } - endRemoveRows(); + const auto matches = match(index(0, static_cast(Column::Id)), Qt::EditRole, dataset->getId(), 1, Qt::MatchExactly); + + if (!matches.isEmpty()) + removeRow(matches.first().row()); } void ScalarSourceModel::removeAllDatasets() { - // Remove row from model - beginRemoveRows(QModelIndex(), 0, rowCount() - DefaultRow::DatasetStart); - { - // Remove all datasets - _datasets.clear(); - } - endRemoveRows(); - - // And update model data with altered datasets - updateData(); + removeRows(DefaultRow::DatasetStart, rowCount() - DefaultRow::DatasetStart); } -const Datasets& ScalarSourceModel::getDatasets() const +Datasets ScalarSourceModel::getDatasets() const { - return _datasets; + Datasets datasets; + + for (int rowIndex = DefaultRow::DatasetStart; rowIndex < rowCount(); ++rowIndex) + { + if (auto item = dynamic_cast(itemFromIndex(index(rowIndex, 0)))) { + const auto dataset = item->getScalarDataset(); + + if (dataset.isValid()) + datasets.append(dataset); + } + } + + return datasets; } Dataset ScalarSourceModel::getDataset(const std::int32_t& rowIndex) const { - // Return empty smart pointer when out of range - if (rowIndex < DefaultRow::DatasetStart || rowIndex > (DefaultRow::DatasetStart + _datasets.count())) - return Dataset(); + if (auto item = dynamic_cast(itemFromIndex(index(rowIndex, 0)))) + return item->getScalarDataset(); - // Subtract the constant point size row - return _datasets[rowIndex - DefaultRow::DatasetStart]; + return {}; } void ScalarSourceModel::setDatasets(const Datasets& datasets) { - // Notify others that the model layout is about to be changed - emit layoutAboutToBeChanged(); - - // Add datasets for (const auto& dataset : datasets) addDataset(dataset); +} + +std::int32_t ScalarSourceModel::getRowIndex(const Dataset& dataset) const +{ + if (!dataset.isValid()) + return -1; + + const auto matches = match(index(0, static_cast(Column::Id)), Qt::EditRole, dataset->getId(), 1, Qt::MatchExactly); - // Notify others that the model layout is changed - emit layoutChanged(); + if (!matches.isEmpty()) + return matches.first().row(); - // And update model data with datasets - updateData(); + return -1; } bool ScalarSourceModel::getShowFullPathName() const @@ -201,23 +248,4 @@ bool ScalarSourceModel::getShowFullPathName() const void ScalarSourceModel::setShowFullPathName(const bool& showFullPathName) { _showFullPathName = showFullPathName; - - updateData(); -} - -void ScalarSourceModel::updateData() -{ - // Update the datasets string list model - for (auto dataset : _datasets) { - - // Continue if the dataset is not valid - if (!dataset.isValid()) - continue; - - // Get dataset model index - const auto datasetModelIndex = index(_datasets.indexOf(dataset), 0); - - // Notify others that the data changed - emit dataChanged(datasetModelIndex, datasetModelIndex); - } } diff --git a/src/ScalarSourceModel.h b/src/ScalarSourceModel.h index 9fd86e7..b6eb766 100644 --- a/src/ScalarSourceModel.h +++ b/src/ScalarSourceModel.h @@ -2,7 +2,7 @@ #include "Dataset.h" -#include +#include using namespace mv; @@ -13,7 +13,7 @@ using namespace mv; * * @author Thomas Kroes */ -class ScalarSourceModel : public QAbstractListModel +class ScalarSourceModel : public QStandardItemModel { protected: @@ -21,6 +21,7 @@ class ScalarSourceModel : public QAbstractListModel ScalarSourceModel(QObject* parent = nullptr); public: + /** Default scalar options */ enum DefaultRow { Constant, /** Scale by constant */ @@ -28,36 +29,127 @@ class ScalarSourceModel : public QAbstractListModel DatasetStart /** Start row of the dataset(s) */ }; -public: + /** Model columns */ + enum class Column { + Name, /** Scalar dataset name */ + Id, /** Globally unique scalar dataset identifier */ - /** - * Get the number of row - * @param parent Parent model index - * @return Number of rows in the model - */ - int rowCount(const QModelIndex& parent = QModelIndex()) const; + Count + }; - /** - * Get the row index of a dataset - * @param parent Parent model index - * @return Row index of the dataset - */ - int rowIndex(const Dataset& dataset) const; +protected: - /** - * Get the number of columns - * @param parent Parent model index - * @return Number of columns in the model - */ - int columnCount(const QModelIndex& parent = QModelIndex()) const; + /** Base standard model item class for a dataset */ + class Item : public QStandardItem { + public: + + /** + * Construct with reference to \p scalarSourceModel and pointer to \p scalarDataset + * @param scalarSourceModel Reference to the scalar source model + * @param scalarDataset Pointer to scalar dataset (maybe nullptr) + */ + Item(const ScalarSourceModel& scalarSourceModel, const mv::Dataset& scalarDataset); + + /** + * Get model data for \p role + * @return Data for \p role in variant form + */ + QVariant data(int role = Qt::UserRole + 1) const override; + + /** + * Get reference to the scalar source model + * @return Reference to the scalar source model + */ + const ScalarSourceModel& getScalarSourceModel() const; + + /** + * Get the scalar dataset associated with this item (if any) + * @return Pointer to scalar dataset (maybe nullptr) + */ + const mv::Dataset<>& getScalarDataset() const; + + private: + const ScalarSourceModel& _scalarSourceModel; /** Reference to the scalar source model */ + mv::Dataset<> _scalarDataset; /** Pointer to scalar dataset (maybe nullptr) */ + }; - /** - * Get data - * @param index Model index to query - * @param role Data role - * @return Data - */ - QVariant data(const QModelIndex& index, int role) const; + /** Standard model item class for displaying the dataset GUI name */ + class NameItem final : public Item, public QObject { + public: + + /** + * Construct with reference to \p scalarSourceModel and pointer to \p scalarDataset + * @param scalarSourceModel Reference to the scalar source model + * @param scalarDataset Pointer to scalar dataset (maybe nullptr) + */ + NameItem(const ScalarSourceModel& scalarSourceModel, const mv::Dataset<>& scalarDataset); + + /** + * Get model data for \p role + * @return Data for \p role in variant form + */ + QVariant data(int role = Qt::UserRole + 1) const override; + + /** + * Get header data for \p orientation and \p role + * @param orientation Horizontal/vertical + * @param role Data role + * @return Header data + */ + static QVariant headerData(Qt::Orientation orientation, int role) { + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return "Name"; + + case Qt::ToolTipRole: + return "Dataset name"; + + default: + break; + } + + return {}; + } + }; + + /** Standard model item class for displaying the dataset GUI ID */ + class IdItem final : public Item { + public: + + /** No need for specialized constructor */ + using Item::Item; + + /** + * Get model data for \p role + * @return Data for \p role in variant form + */ + QVariant data(int role = Qt::UserRole + 1) const override; + + /** + * Get header data for \p orientation and \p role + * @param orientation Horizontal/vertical + * @param role Data role + * @return Header data + */ + static QVariant headerData(Qt::Orientation orientation, int role) { + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return "ID"; + + case Qt::ToolTipRole: + return "Dataset unique identifier"; + + default: + break; + } + + return {}; + } + }; + +public: /** * Add a dataset @@ -85,7 +177,7 @@ class ScalarSourceModel : public QAbstractListModel * Get datasets * @return Vector of smart pointers to datasets */ - const Datasets& getDatasets() const; + Datasets getDatasets() const; /** * Get dataset at the specified row index @@ -100,6 +192,13 @@ class ScalarSourceModel : public QAbstractListModel */ void setDatasets(const Datasets& datasets); + /** + * Get row index of the specified \p dataset + * @param dataset Smart pointer to dataset + * @return Row index of the dataset, or -1 if the dataset is not in the model + */ + std::int32_t getRowIndex(const Dataset& dataset) const; + /** Get whether to show the full path name in the GUI */ bool getShowFullPathName() const; @@ -109,11 +208,26 @@ class ScalarSourceModel : public QAbstractListModel */ void setShowFullPathName(const bool& showFullPathName); - /** Updates the model from the datasets */ - void updateData(); +protected: + + /** Convenience class for combining items in a row */ + class Row final : public QList + { + public: + + /** + * Construct with pointer to \p scalarDataset + * @param scalarDataset Pointer to scalar dataset (maybe nullptr) + */ + Row(const ScalarSourceModel& scalarSourceModel, const mv::Dataset<>& scalarDataset) : + QList() + { + append(new NameItem(scalarSourceModel, scalarDataset)); + append(new IdItem(scalarSourceModel, scalarDataset)); + } + }; protected: - Datasets _datasets; /** Datasets used to size the scatter plot points with */ bool _showFullPathName; /** Whether to show the full path name in the GUI */ friend class ScalarAction;