diff --git a/src/expression/variable.cpp b/src/expression/variable.cpp index af71224aa7..96be169261 100644 --- a/src/expression/variable.cpp +++ b/src/expression/variable.cpp @@ -3,6 +3,7 @@ #include "expression/variable.h" #include "base/messenger.h" +#include "base/sysFunc.h" #include ExpressionVariable::ExpressionVariable(const ExpressionValue &value) @@ -58,6 +59,10 @@ const ExpressionValue &ExpressionVariable::value() const { return value_; } // Return pointer to value ExpressionValue *ExpressionVariable::valuePointer() { return &value_; } +/* + * I/O + */ + // Express as a serialisable value SerialisedValue ExpressionVariable::serialise() const { return {{"name", baseName_}, {"value", value_}}; } @@ -67,3 +72,41 @@ void ExpressionVariable::deserialise(const SerialisedValue &node) value_ = toml::find(node, "value"); setBaseName(toml::find(node, "name")); } + +/* + * Modelable + */ + +// Return property getters and basic setters (if relevant) +template <> +const std::vector::ModelableProperty> +DataModel::Modelable::modelableProperties() +{ + return {{{"Name", DataModel::ItemProperty::PropertyType::String, {}}, + [&](const ExpressionVariable *var) { return DataModel::PropertyValue(var->baseName()); }, + [&](ExpressionVariable *var, const DataModel::PropertyValue &newValue) + { + var->setBaseName(DataModel::propertyAsString(newValue)); + return true; + }}, + {{"Type", DataModel::ItemProperty::PropertyType::String, {DataModel::ItemProperty::PropertyFlag::ReadOnly}}, + [&](const ExpressionVariable *var) { + return DataModel::PropertyValue( + std::string(var->value().type() == ExpressionValue::ValueType::Integer ? "Int" : "Real")); + }, + [&](ExpressionVariable *var, const DataModel::PropertyValue &newValue) { return false; }}, + {{"Value", DataModel::ItemProperty::PropertyType::String, {}}, + [&](const ExpressionVariable *var) { return DataModel::PropertyValue(var->value().asString()); }, + [&](ExpressionVariable *var, const DataModel::PropertyValue &newValue) + { + // Need to check type (int vs double) + auto isFloatingPoint = false; + auto value = DataModel::propertyAsString(newValue); + if (!DissolveSys::isNumber(value, isFloatingPoint)) + return Messenger::error("Value '{}' provided for variable '{}' doesn't appear to be a number.\n", value, + var->baseName()); + + isFloatingPoint ? var->setValue(std::stod(value)) : var->setValue(std::stoi(value)); + return true; + }}}; +} diff --git a/src/expression/variable.h b/src/expression/variable.h index e144c24bb2..f2c5d2900a 100644 --- a/src/expression/variable.h +++ b/src/expression/variable.h @@ -4,9 +4,10 @@ #pragma once #include "expression/value.h" +#include "templates/dataModelItem.h" // Variable -class ExpressionVariable : public Serialisable<> +class ExpressionVariable : public Serialisable<>, public DataModel::Modelable { public: ExpressionVariable(const ExpressionValue &value = ExpressionValue()); @@ -45,6 +46,11 @@ class ExpressionVariable : public Serialisable<> const ExpressionValue &value() const; // Return pointer to value ExpressionValue *valuePointer(); + + /* + * I/O + */ + public: // Express as a serialisable value SerialisedValue serialise() const override; // Read values from a serialisable value diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b556e6b65f..99c0dc44bb 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -19,6 +19,8 @@ add_library( configurationViewerWidget.cpp configurationViewerWidget.h configurationViewerWidget.ui + dataModelTableInterface.cpp + dataModelTableInterface.h dataViewer.cpp dataViewer_input.cpp dataViewer_interaction.cpp diff --git a/src/gui/dataModelTableInterface.cpp b/src/gui/dataModelTableInterface.cpp new file mode 100644 index 0000000000..b2a15086da --- /dev/null +++ b/src/gui/dataModelTableInterface.cpp @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team Dissolve and contributors + +#include "gui/dataModelTableInterface.h" +#include "procedure/nodes/node.h" + +DataModelTableInterface::DataModelTableInterface(DataModel::Base &dataModel) : dataModel_(dataModel) +{ + // Receive signals from the underyling model + dataModel_.addMutationSignalFunction(this, [this](DataModel::Base::MutationSignal signal, int startIndex, int endIndex) + { dataMutated(signal, startIndex, endIndex); }); +} + +DataModelTableInterface::~DataModelTableInterface() +{ + // Remove our signal from the underlying model + dataModel_.removeMutationSignalFunction(this); +} + +/* + * QAbstractTableModel Overrides + */ + +// Return row count +int DataModelTableInterface::rowCount(const QModelIndex &parent) const { return dataModel_.nDataItems(); } + +// Return column count +int DataModelTableInterface::columnCount(const QModelIndex &parent) const { return dataModel_.nProperties(); } + +// Return flags for the specified model index +Qt::ItemFlags DataModelTableInterface::flags(const QModelIndex &index) const +{ + auto &propertyFlags = dataModel_.propertyFlags(index.column()); + Qt::ItemFlags flags = Qt::ItemIsSelectable; + if (!propertyFlags.isSet(DataModel::ItemProperty::ReadOnly)) + flags.setFlag(Qt::ItemIsEditable); + if (!propertyFlags.isSet(DataModel::ItemProperty::Disabled)) + flags.setFlag(Qt::ItemIsEnabled); + return flags; +} + +// Return header data for the specified section, orientation, and role +QVariant DataModelTableInterface::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return {}; + + return QString::fromStdString(dataModel_.propertyName(section)); +} + +// Return data for the index and role specified +QVariant DataModelTableInterface::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole && role != Qt::EditRole) + return {}; + + // Get the specified data property + auto property = dataModel_.getProperty(index.row(), index.column()); + + // Construct a QVariant from the contents of our std::variant + return std::visit( + [](auto arg) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return QVariant(QString::fromStdString(std::string(arg))); + else if constexpr (std::is_same_v) + return QVariant(QString::fromStdString(arg)); + else + return QVariant(arg); + }, + property); +} + +// Set data for the index and role specified +bool DataModelTableInterface::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role != Qt::EditRole || dataModel_.isPropertyFlagSet(index.column(), DataModel::ItemProperty::PropertyFlag::ReadOnly)) + return false; + + // Set new value + bool success = false; + switch (dataModel_.propertyType(index.column())) + { + case (DataModel::ItemProperty::PropertyType::Integer): + success = dataModel_.setProperty(index.row(), index.column(), DataModel::PropertyValue(value.toInt())); + break; + case (DataModel::ItemProperty::PropertyType::Double): + success = dataModel_.setProperty(index.row(), index.column(), DataModel::PropertyValue(value.toDouble())); + break; + case (DataModel::ItemProperty::PropertyType::String): + success = + dataModel_.setProperty(index.row(), index.column(), DataModel::PropertyValue(value.toString().toStdString())); + break; + default: + Messenger::error("DataModelTableInterface doesn't know how to handle this PropertyType.\n"); + break; + } + + if (success) + Q_EMIT dataChanged(index, index); + + return success; +} + +// Insert one or more rows at the specified position +bool DataModelTableInterface::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_UNUSED(parent); + + dataModel_.createItems(row, count); + + return true; +} + +// Append one or more rows +bool DataModelTableInterface::appendRows(int count, const QModelIndex &parent) +{ + Q_UNUSED(parent); + + dataModel_.appendItems(count); + + return true; +} + +// Remove one or more rows starting from the specified position +bool DataModelTableInterface::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_UNUSED(parent); + + dataModel_.removeItems(row, count); + + return true; +} + +/* + * Mutation Interface + */ + +// React to a mutation in the model +void DataModelTableInterface::dataMutated(DataModel::Base::MutationSignal signal, int startIndex, int endIndex) +{ + switch (signal) + { + case (DataModel::Base::MutationSignal::DataMutationStarted): + beginResetModel(); + break; + case (DataModel::Base::MutationSignal::DataMutationFinished): + endResetModel(); + break; + case (DataModel::Base::MutationSignal::DataCreationStarted): + beginInsertRows({}, startIndex, endIndex); + break; + case (DataModel::Base::MutationSignal::DataCreationFinished): + endInsertRows(); + break; + case (DataModel::Base::MutationSignal::DataRemovalStarted): + beginRemoveRows({}, startIndex, endIndex); + break; + case (DataModel::Base::MutationSignal::DataRemovalFinished): + endRemoveRows(); + break; + default: + Messenger::error("DataModelTableInterface::dataMutated() was passed a signal it didn't recognise...\n"); + break; + } +} diff --git a/src/gui/dataModelTableInterface.h b/src/gui/dataModelTableInterface.h new file mode 100644 index 0000000000..e3b94bdf16 --- /dev/null +++ b/src/gui/dataModelTableInterface.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team Dissolve and contributors + +#pragma once + +#include "templates/dataModelBase.h" +#include +#include + +// QAbstractTableModel Interface to DataModel::Table +class DataModelTableInterface : public QAbstractTableModel +{ + public: + DataModelTableInterface(DataModel::Base &dataModel); + ~DataModelTableInterface(); + + private: + // Model with which to interface + DataModel::Base &dataModel_; + + /* + * QAbstractTableModel Overrides + */ + public: + // Return row count + int rowCount(const QModelIndex &parent) const override; + // Return column count + int columnCount(const QModelIndex &parent) const override; + // Return flags for the specified model index + Qt::ItemFlags flags(const QModelIndex &index) const override; + // Return header data for the specified section, orientation, and role + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + // Return data for the index and role specified + QVariant data(const QModelIndex &index, int role) const override; + // Set data for the index and role specified + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + // Insert one or more rows at the specified position + bool insertRows(int row, int count, const QModelIndex &parent) override; + // Append one or more rows + bool appendRows(int count, const QModelIndex &parent); + // Remove one or more rows starting from the specified position + bool removeRows(int row, int count, const QModelIndex &parent) override; + + /* + * Mutation Interface + */ + private: + // React to a mutation in the model + void dataMutated(DataModel::Base::MutationSignal signal, int startIndex, int endIndex); +}; diff --git a/src/gui/keywordWidgets/expressionVariableVector.cpp b/src/gui/keywordWidgets/expressionVariableVector.cpp index e969d41d97..896b541271 100644 --- a/src/gui/keywordWidgets/expressionVariableVector.cpp +++ b/src/gui/keywordWidgets/expressionVariableVector.cpp @@ -9,18 +9,19 @@ ExpressionVariableVectorKeywordWidget::ExpressionVariableVectorKeywordWidget(QWidget *parent, ExpressionVariableVectorKeyword *keyword, const CoreData &coreData) - : QWidget(parent), KeywordWidgetBase(coreData), keyword_(keyword) + : QWidget(parent), KeywordWidgetBase(coreData), keyword_(keyword), variableModel_(keyword->data()) { // Create and set up the UI for our widget ui_.setupUi(this); - // Set up model - variableModel_.setData(keyword->data(), keyword->parentNode()); + // Set model ui_.VariablesTable->setModel(&variableModel_); // Connect signals / slots connect(&variableModel_, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector &)), this, - SLOT(modelDataChanged(const QModelIndex &, const QModelIndex &))); + SLOT(variableDataChanged(const QModelIndex &, const QModelIndex &))); + connect(ui_.VariablesTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + this, SLOT(variableSelectionChanged(const QItemSelection &, const QItemSelection &))); // Add suitable delegate to the table ui_.VariablesTable->setItemDelegateForColumn(2, new ExponentialSpinDelegate(this)); @@ -31,7 +32,7 @@ ExpressionVariableVectorKeywordWidget::ExpressionVariableVectorKeywordWidget(QWi */ // Variable data changed -void ExpressionVariableVectorKeywordWidget::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +void ExpressionVariableVectorKeywordWidget::variableDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (refreshing_) return; @@ -39,5 +40,18 @@ void ExpressionVariableVectorKeywordWidget::modelDataChanged(const QModelIndex & Q_EMIT(keywordDataChanged(keyword_->editSignals())); } +void ExpressionVariableVectorKeywordWidget::variableSelectionChanged(const QItemSelection ¤t, + const QItemSelection &previous) +{ + ui_.RemoveVariableButton->setEnabled(!current.empty()); +} + +void ExpressionVariableVectorKeywordWidget::on_AddVariableButton_clicked(bool checked) { variableModel_.appendRows(1, {}); } + +void ExpressionVariableVectorKeywordWidget::on_RemoveVariableButton_clicked(bool checked) +{ + variableModel_.removeRows(ui_.VariablesTable->selectionModel()->currentIndex().row(), 1, {}); +} + // Update value displayed in widget void ExpressionVariableVectorKeywordWidget::updateValue(const Flags &mutationFlags) {} diff --git a/src/gui/keywordWidgets/expressionVariableVector.h b/src/gui/keywordWidgets/expressionVariableVector.h index b3ddf50573..a599e1fb70 100644 --- a/src/gui/keywordWidgets/expressionVariableVector.h +++ b/src/gui/keywordWidgets/expressionVariableVector.h @@ -3,14 +3,11 @@ #pragma once +#include "gui/dataModelTableInterface.h" #include "gui/keywordWidgets/base.h" #include "gui/keywordWidgets/ui_expressionVariableVector.h" -#include "gui/models/expressionVariableVectorModel.h" #include "keywords/expressionVariableVector.h" -// Forward Declarations -class QWidget; - class ExpressionVariableVectorKeywordWidget : public QWidget, public KeywordWidgetBase { // All Qt declarations must include this macro @@ -33,10 +30,13 @@ class ExpressionVariableVectorKeywordWidget : public QWidget, public KeywordWidg // Main form declaration Ui::ExpressionVariableVectorWidget ui_; // Model for table - ExpressionVariableVectorModel variableModel_; + DataModelTableInterface variableModel_; private Q_SLOTS: - void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void variableDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void variableSelectionChanged(const QItemSelection ¤t, const QItemSelection &previous); + void on_AddVariableButton_clicked(bool checked); + void on_RemoveVariableButton_clicked(bool checked); Q_SIGNALS: // Keyword data changed diff --git a/src/gui/keywordWidgets/expressionVariableVector.ui b/src/gui/keywordWidgets/expressionVariableVector.ui index ada6448ab3..ae928423a6 100644 --- a/src/gui/keywordWidgets/expressionVariableVector.ui +++ b/src/gui/keywordWidgets/expressionVariableVector.ui @@ -7,7 +7,7 @@ 0 0 194 - 81 + 106 @@ -51,10 +51,90 @@ QAbstractItemView::SingleSelection + + QAbstractItemView::SelectRows + + + + + 4 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 0 + 0 + + + + Remove + + + + :/general/icons/remove.svg:/general/icons/remove.svg + + + + 16 + 16 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 0 + 0 + + + + Add + + + + :/general/icons/add.svg:/general/icons/add.svg + + + + 16 + 16 + + + + Qt::ToolButtonTextBesideIcon + + + + + - + + + diff --git a/src/gui/models/CMakeLists.txt b/src/gui/models/CMakeLists.txt index 118e761b6c..5ff05b4eda 100644 --- a/src/gui/models/CMakeLists.txt +++ b/src/gui/models/CMakeLists.txt @@ -68,7 +68,6 @@ set(models_SRCS dissolveModelImageProvider.cpp dissolveModelImageProvider.h enumOptionsModel.cpp - expressionVariableVectorModel.cpp externalPotentialModel.cpp ffSortFilterModel.cpp forcefieldModel.cpp @@ -124,7 +123,6 @@ qt6_wrap_cpp( braggReflectionModel.h dataManagerSimulationModel.h enumOptionsModel.h - expressionVariableVectorModel.h forcefieldModel.h isotopologueSetModel.h pairPotentialModel.h diff --git a/src/gui/models/expressionVariableVectorModel.cpp b/src/gui/models/expressionVariableVectorModel.cpp deleted file mode 100644 index 744870077a..0000000000 --- a/src/gui/models/expressionVariableVectorModel.cpp +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors - -#include "gui/models/expressionVariableVectorModel.h" -#include "procedure/nodes/node.h" - -// Set source variable data -void ExpressionVariableVectorModel::setData(std::vector> &variables, - const ProcedureNode *parentNode) -{ - beginResetModel(); - variables_ = variables; - parentNode_ = parentNode; - endResetModel(); -} - -int ExpressionVariableVectorModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - return variables_ ? variables_->get().size() : 0; -} -int ExpressionVariableVectorModel::columnCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - return 3; -} - -Qt::ItemFlags ExpressionVariableVectorModel::flags(const QModelIndex &index) const -{ - if (index.column() == 1) - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; - else - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; -} - -QVariant ExpressionVariableVectorModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role != Qt::DisplayRole) - return {}; - - if (orientation == Qt::Horizontal) - switch (section) - { - case 0: - return "Name"; - case 1: - return "Type"; - case 2: - return "Value"; - default: - return {}; - } - - return {}; -} - -// Bond model -QVariant ExpressionVariableVectorModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || !variables_) - return {}; - - auto &vars = variables_->get(); - - if (index.row() >= vars.size() || index.row() < 0) - return {}; - - if (role != Qt::DisplayRole && role != Qt::EditRole) - return {}; - - auto &var = vars[index.row()]; - - switch (index.column()) - { - // Name - case 0: - return QString::fromStdString(std::string(var->baseName())); - case 1: - return var->value().type() == ExpressionValue::ValueType::Integer ? "Int" : "Real"; - case 2: - return QString::fromStdString(var->value().asString()); - default: - return {}; - } - - return {}; -} - -bool ExpressionVariableVectorModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (role != Qt::EditRole || !variables_ || index.column() == 1) - return false; - - auto &var = variables_->get()[index.row()]; - - if (index.column() == 0) - { - // Name - must check for existing var in scope with the same name - auto p = parentNode_->getParameterInScope(value.toString().toStdString()); - if (p && p != var) - return false; - var->setBaseName(value.toString().toStdString()); - } - else if (index.column() == 2) - { - // Value - need to check type (int vs double) - auto varValue = value.toString().toStdString(); - bool isFloatingPoint = false; - if (DissolveSys::isNumber(varValue, isFloatingPoint)) - { - if (isFloatingPoint) - var->setValue(value.toDouble()); - else - var->setValue(value.toInt()); - } - else - return Messenger::error("Value '{}' provided for variable '{}' doesn't appear to be a number.\n", varValue, - var->baseName()); - } - - Q_EMIT dataChanged(index, index); - return true; -} diff --git a/src/gui/models/expressionVariableVectorModel.h b/src/gui/models/expressionVariableVectorModel.h deleted file mode 100644 index c5190a5756..0000000000 --- a/src/gui/models/expressionVariableVectorModel.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors - -#pragma once - -#include "expression/variable.h" -#include "templates/optionalRef.h" -#include -#include -#include - -// Forward Declarations -class ProcedureNode; - -// Expression Variable Vector Model -class ExpressionVariableVectorModel : public QAbstractTableModel -{ - Q_OBJECT - - private: - // Source variable data - OptionalReferenceWrapper>> variables_; - // Parent procedure node (to enable parameter search) - const ProcedureNode *parentNode_{nullptr}; - - public: - // Set source variable data - void setData(std::vector> &variables, const ProcedureNode *parentNode); - - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; -}; diff --git a/src/keywords/expressionVariableVector.cpp b/src/keywords/expressionVariableVector.cpp index 630d131fe6..fd0589389a 100644 --- a/src/keywords/expressionVariableVector.cpp +++ b/src/keywords/expressionVariableVector.cpp @@ -8,21 +8,52 @@ #include "procedure/nodes/node.h" #include -ExpressionVariableVectorKeyword::ExpressionVariableVectorKeyword(std::vector> &data, - ProcedureNode *parentNode) +ExpressionVariableVectorKeyword::ExpressionVariableVectorKeyword( + DataModel::VectorModelable> &data, ProcedureNode *parentNode) : KeywordBase(typeid(this)), data_(data), parentNode_(parentNode) { + // Override the setter for the "Name" property in the model as we need to ensure unique naming in the same scope + data_.setSetter("Name", + [&](ExpressionVariable *var, const DataModel::PropertyValue &newValue) + { + auto p = parentNode_->getParameter(DataModel::propertyAsString(newValue)); + if (p && p.get() != var) + return false; + var->setBaseName(DataModel::propertyAsString(newValue)); + return true; + }); + + // Set the creator function - we need to ensure both name uniqueness and the correct prefixing + data_.setCreator( + [&]() + { + auto allParameters = parentNode_->getParametersInScope(); + auto newVar = std::make_shared(); + newVar->setBaseName( + DissolveSys::uniqueName("NewVariable", allParameters, [](const auto &var) { return var->name(); })); + if (parentNode_->type() != ProcedureNode::NodeType::Parameters) + newVar->setNamePrefix(parentNode_->name()); + return newVar; + }); } /* * Data */ -// Return reference to vector of data -std::vector> &ExpressionVariableVectorKeyword::data() { return data_; } -const std::vector> &ExpressionVariableVectorKeyword::data() const { return data_; } +// Return reference to data +DataModel::VectorModelable> &ExpressionVariableVectorKeyword::data() +{ + return data_; +} +const DataModel::VectorModelable> & +ExpressionVariableVectorKeyword::data() const +{ + return data_; +} // Return parent ProcedureNode +ProcedureNode *ExpressionVariableVectorKeyword::parentNode() { return parentNode_; } const ProcedureNode *ExpressionVariableVectorKeyword::parentNode() const { return parentNode_; } /* @@ -96,7 +127,3 @@ void ExpressionVariableVectorKeyword::deserialise(const SerialisedValue &node, c toMap(node, [this](const auto &key, const auto &value) { parentNode_->addParameter(key, toml::get(value)); }); } - -/* - * Object Management - */ diff --git a/src/keywords/expressionVariableVector.h b/src/keywords/expressionVariableVector.h index bc7514ac1a..540c3fbf59 100644 --- a/src/keywords/expressionVariableVector.h +++ b/src/keywords/expressionVariableVector.h @@ -5,6 +5,7 @@ #include "expression/node.h" #include "keywords/base.h" +#include "templates/dataModelVectorModelable.h" // Forward Declarations class ExpressionVariable; @@ -14,15 +15,16 @@ class ProcedureNode; class ExpressionVariableVectorKeyword : public KeywordBase { public: - ExpressionVariableVectorKeyword(std::vector> &data, ProcedureNode *parentNode); + ExpressionVariableVectorKeyword(DataModel::VectorModelable> &data, + ProcedureNode *parentNode); ~ExpressionVariableVectorKeyword() override = default; /* * Data */ private: - // Reference to vector of data - std::vector> &data_; + // Reference to vector modelable + DataModel::VectorModelable> &data_; // Parent ProcedureNode ProcedureNode *parentNode_; @@ -30,12 +32,13 @@ class ExpressionVariableVectorKeyword : public KeywordBase // Has not changed from initial value bool isDefault() const override; // Return reference to vector of data - std::vector> &data(); - const std::vector> &data() const; + DataModel::VectorModelable> &data(); + const DataModel::VectorModelable> &data() const; // Return parent ProcedureNode + ProcedureNode *parentNode(); const ProcedureNode *parentNode() const; - /* + /*c * Arguments */ public: diff --git a/src/procedure/nodes/node.cpp b/src/procedure/nodes/node.cpp index 0ab6e03f2b..73b2095847 100644 --- a/src/procedure/nodes/node.cpp +++ b/src/procedure/nodes/node.cpp @@ -156,9 +156,13 @@ OptionalReferenceWrapper ProcedureNode::branch() { return // Add new parameter std::shared_ptr ProcedureNode::addParameter(std::string_view name, const ExpressionValue &initialValue) { - auto &newVar = parameters_.emplace_back(std::make_shared(name, initialValue)); + auto newVar = parameters_.appendItem(); + newVar->setBaseName(name); + newVar->setValue(initialValue); + if (type_ != ProcedureNode::NodeType::Parameters) newVar->setNamePrefix(name_); + return newVar; } @@ -188,7 +192,7 @@ std::shared_ptr ProcedureNode::getParameter(std::string_view } // Return references to all parameters for this node -const std::vector> &ProcedureNode::parameters() const { return parameters_; } +const std::vector> &ProcedureNode::parameters() const { return parameters_.data(); } /* * Execution diff --git a/src/procedure/nodes/node.h b/src/procedure/nodes/node.h index bab00d4cf5..582edb0825 100644 --- a/src/procedure/nodes/node.h +++ b/src/procedure/nodes/node.h @@ -5,15 +5,16 @@ #include "base/enumOptions.h" #include "base/serialiser.h" +#include "expression/variable.h" #include "keywords/store.h" #include "procedure/nodes/aliases.h" #include "procedure/nodes/context.h" +#include "templates/dataModelVectorModelable.h" #include "templates/optionalRef.h" // Forward Declarations class Configuration; class CoreData; -class ExpressionVariable; class GenericList; class LineParser; class ProcedureNodeSequence; @@ -132,7 +133,7 @@ class ProcedureNode : public std::enable_shared_from_this, public */ protected: // Defined parameters - std::vector> parameters_; + DataModel::VectorModelable> parameters_; // Set named parameter in supplied vector bool setParameter(std::vector> ¶meters, std::string_view parameter, ExpressionValue value); diff --git a/src/templates/dataModelBase.h b/src/templates/dataModelBase.h new file mode 100644 index 0000000000..3612abea33 --- /dev/null +++ b/src/templates/dataModelBase.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team Dissolve and contributors + +#pragma once + +#include "templates/dataModelItem.h" +#include + +namespace DataModel +{ +/* + * DataModel::Base - An abstract class intended to allow our custom C++ data classes to be accessed in a consistent + * and simplified way through Qt's Model-View classes, without the need for a specific Qt model for each. + */ +class Base +{ + public: + Base() {} + + /* + * Properties + */ + protected: + // Descriptions of relevant item properties within a single object in the container + std::vector itemProperties_; + + protected: + // Add item property + void addItemProperty(const ItemProperty &property) { itemProperties_.emplace_back(property); } + + public: + // Return name of specified property + std::string propertyName(int propertyIndex) { return itemProperties_[propertyIndex].name(); } + // Return property type for the specified property + ItemProperty::PropertyType propertyType(int propertyIndex) { return itemProperties_[propertyIndex].type(); } + // Return all flags set for specified property + const Flags &propertyFlags(int propertyIndex) { return itemProperties_[propertyIndex].flags(); } + // Return whether the specified property flag is set + bool isPropertyFlagSet(int propertyIndex, ItemProperty::PropertyFlag flag) + { + return itemProperties_[propertyIndex].flags().isSet(flag); + } + + public: + // Return number of data items (i.e. rows) for the specified index + virtual int nDataItems() const = 0; + // Return number of properties (i.e. columns) for the specified index + int nProperties() const { return itemProperties_.size(); } + // Set property function + virtual bool setProperty(int dataIndex, int propertyIndex, const PropertyValue &newValue) = 0; + // Get property function + virtual PropertyValue getProperty(int dataIndex, int propertyIndex) const = 0; + + /* + * Item Management + */ + public: + // Create new item(s) starting at specified vector index + virtual void createItems(int index, int count) = 0; + // Append new item(s) to the end of the data + virtual void appendItems(int count) = 0; + // Remove item(s) starting at specified vector index + virtual void removeItems(int index, int count) = 0; + // Clear all items + virtual void clear() = 0; + + /* + * Signalling + */ + public: + // Mutation Signals + enum class MutationSignal + { + DataMutationStarted, + DataMutationFinished, + DataCreationStarted, + DataCreationFinished, + DataRemovalStarted, + DataRemovalFinished + }; + + private: + // Mutation signal function used to notify an associated Qt model + using DataMutationSignalFunction = std::function; + std::map mutationSignalFunctions_; + + public: + // Add mutation signal function + void addMutationSignalFunction(void *object, const DataMutationSignalFunction &mutationSignalFunction) + { + mutationSignalFunctions_[object] = mutationSignalFunction; + } + // Remove mutation signal function for specified object + void removeMutationSignalFunction(void *object) + { + auto it = mutationSignalFunctions_.find(object); + if (it != mutationSignalFunctions_.end()) + mutationSignalFunctions_.erase(it); + } + // Emit mutation signal + void emitMutationSignal(MutationSignal signal, int startIndex = 0, int endIndex = 0) + { + for (const auto &[object, func] : mutationSignalFunctions_) + func(signal, startIndex, endIndex); + } +}; +} // namespace DataModel diff --git a/src/templates/dataModelItem.h b/src/templates/dataModelItem.h new file mode 100644 index 0000000000..3253babba5 --- /dev/null +++ b/src/templates/dataModelItem.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team Dissolve and contributors + +#pragma once + +#include "templates/flags.h" +#include +#include +#include + +namespace DataModel +{ +// Property for an Item within a DataModel +class ItemProperty +{ + public: + // Data property types + enum class PropertyType + { + Integer, + Double, + String + }; + + // Data property flags + enum PropertyFlag + { + ReadOnly, + Disabled + }; + ItemProperty(std::string_view name, PropertyType type, Flags flags = {}) + : name_{name}, type_(type), flags_(flags) + { + } + + private: + // Property name + std::string name_; + // Property type + PropertyType type_; + // Property flags + Flags flags_; + + public: + // Return property name + const std::string &name() const { return name_; } + // Return property type + PropertyType type() const { return type_; } + // Return property flags + const Flags &flags() const { return flags_; } +}; + +// Property value variant +using PropertyValue = std::variant; +// Return value as string +static std::string propertyAsString(const PropertyValue &value) +{ + return std::visit([](auto &arg) { return fmt::format("{}", arg); }, value); +} + +// Modelable - Provides declarations for use by a class defining its properties +template class Modelable +{ + public: + using ModelableGetter = std::function; + using ModelableSetter = std::function; + using ModelableProperty = std::tuple; + + /* + * We now define a static templated function to return a vector of ModelableProperty definitions for the target class D. The + * class D needs to provide this function as a templated function specialisation. + */ + public: + // Return basic property information including getter and setter (if relevant) + static const std::vector modelableProperties(); +}; +}; // namespace DataModel diff --git a/src/templates/dataModelVectorModelable.h b/src/templates/dataModelVectorModelable.h new file mode 100644 index 0000000000..0f8a45e589 --- /dev/null +++ b/src/templates/dataModelVectorModelable.h @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team Dissolve and contributors + +#pragma once + +#include "templates/dataModelBase.h" +#include +#include +#include + +namespace DataModel +{ +// Mutator for Data based on std::vector +template class VectorMutator +{ + public: + VectorMutator(std::vector &data, Base &base) : data_(data), base_(base) + { + base_.emitMutationSignal(Base::MutationSignal::DataMutationStarted); + } + ~VectorMutator() { base_.emitMutationSignal(Base::MutationSignal::DataMutationFinished); } + + private: + // Data being mutated and the base model + std::vector &data_; + Base &base_; + + public: + // Return mutable data + std::vector &data() { return data_; } +}; +// Modelable for Data based on std::vector +template class VectorModelable : public Base +{ + public: + // Modelable properties + using ModelableProperties = std::vector::ModelableProperty>; + // Property get function + using PropertyGetFunction = std::function; + // Property set function + using PropertySetFunction = std::function; + + VectorModelable() : Base() + { + // Add properties from the modelable base class + for (auto &[itemProperty, getter, setter] : Modelable::modelableProperties()) + { + addProperty(itemProperty, getter, setter); + } + } + + /* + * Target Data and Vector Compatibility + */ + private: + // Target data for the model + std::vector data_; + + public: + // Return const data + const std::vector &data() const { return data_; } + // Return mutable data + VectorMutator mutableData() { return {data_, *this}; }; + // Return opening iterator for the data + typename std::vector::const_iterator begin() const { return data_.begin(); } + // Return ending iterator for the data + typename std::vector::const_iterator end() const { return data_.end(); } + // Return whether the data is empty + bool empty() const { return data_.empty(); } + + /* + * Setters / Getters + */ + private: + // Map of named properties to data getters + std::map getters_; + // Map of named properties to data setters + std::map setters_; + + private: + // Add item property for use in the model + void addProperty(const ItemProperty &property, PropertyGetFunction getter, PropertySetFunction setter = {}) + { + // Add the property base info - the order will be reflected in the table model + addItemProperty(property); + + // Store functions + getters_[property.name()] = std::move(getter); + if (setter) + setters_[property.name()] = std::move(setter); + } + + public: + // Add or override setter for specified property + void setSetter(std::string name, PropertySetFunction setter) { setters_[name] = std::move(setter); } + + /* + * Extent + */ + private: + // Return whether the supplied index is valid + bool isIndexValid(int dataIndex, int propertyIndex) const + { + return dataIndex >= 0 && dataIndex < data_.size() && propertyIndex >= 0 && propertyIndex < itemProperties_.size(); + } + + public: + // Return number of data items (rows) in the table + int nDataItems() const final { return data_.size(); } + + /* + * Data Access + */ + private: + // Call getter + PropertyValue callGetter(const PropertyGetFunction &getter, const DataItemClass &item) const { return getter(&item); } + PropertyValue callGetter(const PropertyGetFunction &getter, const std::shared_ptr &item) const + { + return getter(item.get()); + } + PropertyValue callGetter(const PropertyGetFunction &getter, const std::unique_ptr &item) const + { + return getter(item.get()); + } + // Call setter + bool callSetter(PropertySetFunction &setter, DataItemClass &item, const PropertyValue &newValue) + { + return setter(&item, newValue); + } + bool callSetter(PropertySetFunction &setter, std::shared_ptr &item, const PropertyValue &newValue) + { + return setter(item.get(), newValue); + } + bool callSetter(PropertySetFunction &setter, std::unique_ptr &item, const PropertyValue &newValue) + { + return setter(item.get(), newValue); + } + + public: + // Get property + PropertyValue getProperty(int dataIndex, int propertyIndex) const final + { + // Check index validity + if (!isIndexValid(dataIndex, propertyIndex)) + return {}; + + return callGetter(getters_.at(itemProperties_[propertyIndex].name()), data_[dataIndex]); + } + // Set property + bool setProperty(int dataIndex, int propertyIndex, const PropertyValue &newValue) final + { + // Check index validity + if (!isIndexValid(dataIndex, propertyIndex)) + return false; + + if (itemProperties_[propertyIndex].flags().isSet(ItemProperty::PropertyFlag::ReadOnly)) + { + Messenger::error("Refusing to set data '{}' since it is read-only.\n", itemProperties_[propertyIndex].name()); + return false; + } + + // Set the child at the specified index + if (setters_.find(itemProperties_[propertyIndex].name()) == setters_.end()) + return false; + else + return callSetter(setters_[itemProperties_[propertyIndex].name()], data_[dataIndex], newValue); + } + + /* + * Item Management + */ + private: + // Item creation function (if required) otherwise a suitable default constructor will be called + using CreateItemFunction = std::function; + CreateItemFunction createItemFunction_; + // Item removal function (if required) + using RemoveItemFunction = std::function; + RemoveItemFunction removeItemFunction_; + + private: + // Create new item + void newItem(std::optional position = {}) + { + if (!createItemFunction_) + throw(std::runtime_error( + fmt::format("No CreateItemFunction has been set in this VectorModelable({},{}) but newItem() was called.\n", + typeid(DataItemClass).name(), typeid(DataItem).name()))); + + if (position) + data_.insert(data_.begin() + *position, createItemFunction_()); + else + data_.emplace_back(createItemFunction_()); + } + + public: + // Set data creation function + void setCreator(CreateItemFunction function) { createItemFunction_ = std::move(function); } + // Set data removal function + void setRemover(RemoveItemFunction function) { removeItemFunction_ = std::move(function); } + // Create new item(s) starting at specified vector index + void createItems(int index, int count) final + { + emitMutationSignal(Base::MutationSignal::DataCreationStarted, index, index + count - 1); + for (auto n = 0; n < count; ++n) + newItem(index + n); + emitMutationSignal(Base::MutationSignal::DataCreationFinished); + } + // Append new item to the end of the data and return it + DataItem &appendItem() + { + emitMutationSignal(Base::MutationSignal::DataCreationStarted, data_.size(), data_.size()); + newItem(); + emitMutationSignal(Base::MutationSignal::DataCreationFinished); + return data_.back(); + } + // Append new item(s) to the end of the data + void appendItems(int count) final + { + emitMutationSignal(Base::MutationSignal::DataCreationStarted, data_.size(), data_.size() + count - 1); + for (auto n = 0; n < count; ++n) + newItem(); + emitMutationSignal(Base::MutationSignal::DataCreationFinished); + } + // Emplace append the supplied item + void emplaceAppend(DataItem item) + { + emitMutationSignal(Base::MutationSignal::DataCreationStarted, data_.size(), data_.size()); + data_.emplace_back(std::move(item)); + emitMutationSignal(Base::MutationSignal::DataCreationFinished); + } + // Remove item using the supplied lamba match function + void removeItem(std::function matchFunction) + { + auto it = std::find_if(data_.begin(), data_.end(), matchFunction); + if (it == data_.end()) + return; + + emitMutationSignal(Base::MutationSignal::DataRemovalStarted, it - data_.begin(), it - data_.begin()); + data_.erase(it); + emitMutationSignal(Base::MutationSignal::DataRemovalFinished); + } + // Remove item(s) starting at specified vector index + void removeItems(int index, int count) final + { + emitMutationSignal(Base::MutationSignal::DataRemovalStarted, index, index + count - 1); + if (removeItemFunction_) + { + for (auto n = 0; n < count; ++n) + removeItemFunction_(index); + } + else + data_.erase(data_.begin() + index, data_.begin() + index + count); + emitMutationSignal(Base::MutationSignal::DataRemovalFinished); + } + // Clear all items + void clear() final + { + if (empty()) + return; + + emitMutationSignal(Base::MutationSignal::DataRemovalStarted, 0, data_.size() - 1); + if (removeItemFunction_) + { + for (auto n = 0; n < data_.size(); ++n) + removeItemFunction_(0); + } + else + data_.clear(); + emitMutationSignal(Base::MutationSignal::DataRemovalFinished); + } +}; +} // namespace DataModel