diff --git a/src/gui/keywordWidgets/expressionVariableVector.cpp b/src/gui/keywordWidgets/expressionVariableVector.cpp index e969d41d97..4c545e4826 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->dataModel()) { // 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,15 @@ 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::ui_AddVariableButton_clicked(bool checked) {} + +void ExpressionVariableVectorKeywordWidget::ui_RemoveVariableButton_clicked(bool checked) {} + // 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..b86c766485 100644 --- a/src/gui/keywordWidgets/expressionVariableVector.h +++ b/src/gui/keywordWidgets/expressionVariableVector.h @@ -33,10 +33,13 @@ class ExpressionVariableVectorKeywordWidget : public QWidget, public KeywordWidg // Main form declaration Ui::ExpressionVariableVectorWidget ui_; // Model for table - ExpressionVariableVectorModel variableModel_; + DataTableModelInterface 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 ui_AddVariableButton_clicked(bool checked); + void ui_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..97ff965b02 100644 --- a/src/gui/keywordWidgets/expressionVariableVector.ui +++ b/src/gui/keywordWidgets/expressionVariableVector.ui @@ -7,7 +7,7 @@ 0 0 194 - 81 + 99 @@ -53,8 +53,85 @@ + + + + 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/expressionVariableVectorModel.cpp b/src/gui/models/expressionVariableVectorModel.cpp index 744870077a..146737d0c0 100644 --- a/src/gui/models/expressionVariableVectorModel.cpp +++ b/src/gui/models/expressionVariableVectorModel.cpp @@ -4,122 +4,108 @@ #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(); -} +DataTableModelInterface::DataTableModelInterface(DataModelBase &dataModel) : dataModel_(dataModel) {} -int ExpressionVariableVectorModel::rowCount(const QModelIndex &parent) const +int DataTableModelInterface::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) - return 0; - return variables_ ? variables_->get().size() : 0; + return dataModel_.nChildren(parent.row(), parent.column()); } -int ExpressionVariableVectorModel::columnCount(const QModelIndex &parent) const + +int DataTableModelInterface::columnCount(const QModelIndex &parent) const { - if (parent.isValid()) - return 0; - return 3; + return dataModel_.nProperties(parent.row(), parent.column()); } -Qt::ItemFlags ExpressionVariableVectorModel::flags(const QModelIndex &index) const +Qt::ItemFlags DataTableModelInterface::flags(const QModelIndex &index) const { + // TODO 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 +QVariant DataTableModelInterface::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole) + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) return {}; - if (orientation == Qt::Horizontal) - switch (section) - { - case 0: - return "Name"; - case 1: - return "Type"; - case 2: - return "Value"; - default: - return {}; - } - - return {}; + return QString::fromStdString(dataModel_.propertyName(section)); } // Bond model -QVariant ExpressionVariableVectorModel::data(const QModelIndex &index, int role) const +QVariant DataTableModelInterface::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()]; + // Get the specified data property + auto property = dataModel_.getProperty(index.row(), index.column()); - switch (index.column()) + switch (property.type()) { - // 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()); + case (PropertyType::Invalid): + return {}; + case (PropertyType::Integer): + return property.intValue(); + case (PropertyType::Double): + return property.doubleValue(); + case (PropertyType::String): + return QString::fromStdString(property.stringValue()); default: return {}; } - - return {}; } -bool ExpressionVariableVectorModel::setData(const QModelIndex &index, const QVariant &value, int role) +bool DataTableModelInterface::setData(const QModelIndex &index, const QVariant &value, int role) { - if (role != Qt::EditRole || !variables_ || index.column() == 1) + if (role != Qt::EditRole || dataModel_.isPropertyFlagSet(index.column(), PropertyFlag::ReadOnly)) return false; - auto &var = variables_->get()[index.row()]; - - if (index.column() == 0) + // Set new value + bool success = false; + switch (dataModel_.propertyType(index.column())) { - // 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()); + case (PropertyType::Integer): + success = dataModel_.setProperty(index.row(), index.column(), DataItemValue(value.toInt())); + break; + case (PropertyType::Double): + success = dataModel_.setProperty(index.row(), index.column(), DataItemValue(value.toDouble())); + break; + case (PropertyType::String): + success = dataModel_.setProperty(index.row(), index.column(), DataItemValue(value.toString().toStdString())); + break; + default: + break; } - else if (index.column() == 2) + + if (success) + Q_EMIT dataChanged(index, index); + + return success; +} + +bool DataTableModelInterface::insertRows(int row, int count, const QModelIndex &parent) +{ + // TODO + Q_UNUSED(count); + beginInsertRows(parent, row, row); + // parentNode_->addParameter("NewParameter", 0.0, row); + endInsertRows(); + return true; +} + +bool DataTableModelInterface::removeRows(int row, int count, const QModelIndex &parent) +{ + // TODO + Q_UNUSED(count); + if (row >= rowCount(parent) || row < 0) { - // 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()); + return false; } - Q_EMIT dataChanged(index, index); + beginRemoveRows(parent, row, row); + // ranges_->get().erase(ranges_->get().begin() + row); + endRemoveRows(); return true; } diff --git a/src/gui/models/expressionVariableVectorModel.h b/src/gui/models/expressionVariableVectorModel.h index c5190a5756..4da2f172c3 100644 --- a/src/gui/models/expressionVariableVectorModel.h +++ b/src/gui/models/expressionVariableVectorModel.h @@ -3,29 +3,23 @@ #pragma once -#include "expression/variable.h" +#include "keywords/expressionVariableVector.h" #include "templates/optionalRef.h" #include #include #include -// Forward Declarations -class ProcedureNode; - -// Expression Variable Vector Model -class ExpressionVariableVectorModel : public QAbstractTableModel +// Qt Interface to DataTableModel +class DataTableModelInterface : 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); + DataTableModelInterface(DataModelBase &dataModel); + + private: + // Model with which to interface + DataModelBase &dataModel_; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; @@ -33,4 +27,6 @@ class ExpressionVariableVectorModel : public QAbstractTableModel 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; + bool insertRows(int row, int count, const QModelIndex &parent) override; + bool removeRows(int row, int count, const QModelIndex &parent) override; }; diff --git a/src/keywords/expressionVariableVector.cpp b/src/keywords/expressionVariableVector.cpp index 630d131fe6..2cecefcb65 100644 --- a/src/keywords/expressionVariableVector.cpp +++ b/src/keywords/expressionVariableVector.cpp @@ -8,10 +8,70 @@ #include "procedure/nodes/node.h" #include +enum ExpressionVariableProperties +{ + Name, + Type, + Value +}; +std::vector expressionVariableProperties = { + {ExpressionVariableProperties::Name, "Name", PropertyType::String, {}}, + {ExpressionVariableProperties::Type, "Type", PropertyType::String, {PropertyFlag::ReadOnly}}, + {ExpressionVariableProperties::Value, "Value", PropertyType::String, {}}}; + ExpressionVariableVectorKeyword::ExpressionVariableVectorKeyword(std::vector> &data, ProcedureNode *parentNode) - : KeywordBase(typeid(this)), data_(data), parentNode_(parentNode) + : KeywordBase(typeid(this)), data_(data), parentNode_(parentNode), dataModel_(data_, expressionVariableProperties) { + dataModel_.setPropertyFunctions( + [&](const std::shared_ptr &var, int propertyIndex) + { + switch (propertyIndex) + { + case (ExpressionVariableProperties::Name): + return DataItemValue(var->baseName()); + case (ExpressionVariableProperties::Type): + return DataItemValue(std::string(var->value().type() == ExpressionValue::ValueType::Integer ? "Int" : "Real")); + case (ExpressionVariableProperties::Value): + return DataItemValue(var->value().asString()); + default: + return DataItemValue(); + } + }, + [&](std::shared_ptr &var, int propertyIndex, const DataItemValue &newValue) + { + switch (propertyIndex) + { + case (ExpressionVariableProperties::Name): + { + // Must check for existing var in scope with the same name + auto p = parentNode_->getParameter(newValue.stringValue()); + if (p && p != var) + return false; + var->setBaseName(newValue.stringValue()); + } + break; + case (ExpressionVariableProperties::Value): + { + // Value - need to check type (int vs double) + bool isFloatingPoint = false; + if (DissolveSys::isNumber(newValue.stringValue(), isFloatingPoint)) + { + if (isFloatingPoint) + var->setValue(std::stod(newValue.stringValue())); + else + var->setValue(std::stoi(newValue.stringValue())); + } + else + return Messenger::error("Value '{}' provided for variable '{}' doesn't appear to be a number.\n", + newValue.stringValue(), var->baseName()); + } + break; + default: + return false; + } + return true; + }); } /* @@ -22,7 +82,11 @@ ExpressionVariableVectorKeyword::ExpressionVariableVectorKeyword(std::vector> &ExpressionVariableVectorKeyword::data() { return data_; } const std::vector> &ExpressionVariableVectorKeyword::data() const { return data_; } +// Return data model +DataTableModel> &ExpressionVariableVectorKeyword::dataModel() { return dataModel_; } + // Return parent ProcedureNode +ProcedureNode *ExpressionVariableVectorKeyword::parentNode() { return parentNode_; } const ProcedureNode *ExpressionVariableVectorKeyword::parentNode() const { return parentNode_; } /* diff --git a/src/keywords/expressionVariableVector.h b/src/keywords/expressionVariableVector.h index bc7514ac1a..ad1b646da1 100644 --- a/src/keywords/expressionVariableVector.h +++ b/src/keywords/expressionVariableVector.h @@ -10,6 +10,170 @@ class ExpressionVariable; class ProcedureNode; +// Data property types +enum class PropertyType +{ + Invalid, + Integer, + Double, + String +}; + +// Data property flags +enum PropertyFlag +{ + ReadOnly +}; + +// Index / Column ID, Name / Column Title, Data Type, ReadOnly? +using DataItemProperty = std::tuple>; + +class DataItemValue +{ + public: + DataItemValue() : type_(PropertyType::Invalid) {} + DataItemValue(int i) : type_(PropertyType::Integer), intValue_(i) {} + DataItemValue(double d) : type_(PropertyType::Double), doubleValue_(d) {} + DataItemValue(std::string_view sv) : type_(PropertyType::String), stringValue_(sv) {} + DataItemValue(std::string s) : type_(PropertyType::String), stringValue_(std::move(s)) {} + + private: + PropertyType type_; + int intValue_{0}; + double doubleValue_{0.0}; + std::string stringValue_; + + public: + // Return value type + PropertyType type() const { return type_; } + // Return integer value + int intValue() const { return intValue_; } + // Return double value + double doubleValue() const { return doubleValue_; } + // Return string value + std::string stringValue() const { return stringValue_; } +}; + +class DataModelBase +{ + public: + DataModelBase(const std::vector &itemProperties) : itemProperties_(itemProperties) {} + + /* + * Properties + */ + protected: + // Descriptions of relevant item properties within a single object in the container + std::vector itemProperties_; + + public: + // Return name of specified property + std::string propertyName(int propertyIndex) { return std::get<1>(itemProperties_[propertyIndex]); } + // Return property type for the specified column + PropertyType propertyType(int propertyIndex) { return std::get<2>(itemProperties_[propertyIndex]); } + // Return whether the specified property flag is set + bool isPropertyFlagSet(int propertyIndex, PropertyFlag flag) + { + return std::get<3>(itemProperties_[propertyIndex]).isSet(flag); + } + + public: + // Return number of children (rows) for the specified index + virtual int nChildren(int dataIndex, int propertyIndex) = 0; + // Return number of properties (i.e. columns) for the specified index + virtual int nProperties(int dataIndex, int propertyIndex) = 0; + // Set property function + virtual bool setProperty(int dataIndex, int propertyIndex, const DataItemValue &newValue) = 0; + // Get property function + virtual DataItemValue getProperty(int dataIndex, int propertyIndex) = 0; +}; + +template class DataTableModel : public DataModelBase +{ + public: + // Data access functions + using PropertySetFunction = std::function; + using PropertyGetFunction = std::function; + DataTableModel(std::vector &data, const std::vector &itemProperties) + : DataModelBase(itemProperties), data_(data) + { + } + + private: + // Target data for the model + std::vector &data_; + + /* + * 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(); + } + // Functions for accessing data extents (table style) + std::function childCountFunction_{[&](const int dataIndex, const int propertyIndex) { + return isIndexValid(dataIndex, propertyIndex) ? 0 : data_.size(); + }}; + std::function propertyCountFunction_{[&](const int dataIndex, const int propertyIndex) + { return itemProperties_.size(); }}; + + public: + // Return number of children (rows) for the specified index + int nChildren(int dataIndex, int propertyIndex) final { return childCountFunction_(dataIndex, propertyIndex); } + // Return number of properties per child (i.e. columns) for the specified index + int nProperties(int dataIndex, int propertyIndex) final { return propertyCountFunction_(dataIndex, propertyIndex); } + + /* + * Data Access + */ + private: + // Set / get functions, unique per class + PropertySetFunction setPropertyFunction_; + PropertyGetFunction getPropertyFunction_; + + public: + // Set property access functions + void setPropertyFunctions(PropertyGetFunction getFunction, PropertySetFunction setFunction) + { + getPropertyFunction_ = std::move(getFunction); + setPropertyFunction_ = std::move(setFunction); + } + // Set property + bool setProperty(int dataIndex, int propertyIndex, const DataItemValue &newValue) final + { + // Check index validity + if (!isIndexValid(dataIndex, propertyIndex)) + return false; + + if (std::get<3>(itemProperties_[propertyIndex]).isSet(PropertyFlag::ReadOnly)) + { + fmt::print("Refusing to set data '{}' since it is read-only.\n", std::get<1>(itemProperties_[propertyIndex])); + return false; + } + + // Set the child at the specified index + if (!setPropertyFunction_) + return false; + else + return setPropertyFunction_(data_[dataIndex], std::get<0>(itemProperties_[propertyIndex]), newValue); + } + // Get property + DataItemValue getProperty(int dataIndex, int propertyIndex) final + { + // Check index validity + if (!isIndexValid(dataIndex, propertyIndex)) + return {}; + + // Set the child at the specified index + if (!getPropertyFunction_) + return {}; + else + return getPropertyFunction_(data_[dataIndex], std::get<0>(itemProperties_[propertyIndex])); + } +}; + // Keyword managing vector of ExpressionVariable class ExpressionVariableVectorKeyword : public KeywordBase { @@ -23,6 +187,7 @@ class ExpressionVariableVectorKeyword : public KeywordBase private: // Reference to vector of data std::vector> &data_; + DataTableModel> dataModel_; // Parent ProcedureNode ProcedureNode *parentNode_; @@ -32,7 +197,10 @@ class ExpressionVariableVectorKeyword : public KeywordBase // Return reference to vector of data std::vector> &data(); const std::vector> &data() const; + // Return data model + DataTableModel> &dataModel(); // Return parent ProcedureNode + ProcedureNode *parentNode(); const ProcedureNode *parentNode() const; /* diff --git a/src/procedure/nodes/node.cpp b/src/procedure/nodes/node.cpp index 0ab6e03f2b..9be05b3d5b 100644 --- a/src/procedure/nodes/node.cpp +++ b/src/procedure/nodes/node.cpp @@ -154,11 +154,18 @@ OptionalReferenceWrapper ProcedureNode::branch() { return */ // Add new parameter -std::shared_ptr ProcedureNode::addParameter(std::string_view name, const ExpressionValue &initialValue) +std::shared_ptr ProcedureNode::addParameter(std::string_view name, const ExpressionValue &initialValue, + std::optional insertAt) { - auto &newVar = parameters_.emplace_back(std::make_shared(name, initialValue)); + auto newVar = std::make_shared(name, initialValue); + if (insertAt) + parameters_.insert(parameters_.begin() + *insertAt, newVar); + else + parameters_.emplace_back(newVar); + if (type_ != ProcedureNode::NodeType::Parameters) newVar->setNamePrefix(name_); + return newVar; } diff --git a/src/procedure/nodes/node.h b/src/procedure/nodes/node.h index bab00d4cf5..02381419d9 100644 --- a/src/procedure/nodes/node.h +++ b/src/procedure/nodes/node.h @@ -139,7 +139,8 @@ class ProcedureNode : public std::enable_shared_from_this, public public: // Add new parameter - std::shared_ptr addParameter(std::string_view name, const ExpressionValue &initialValue = {}); + std::shared_ptr addParameter(std::string_view name, const ExpressionValue &initialValue = {}, + std::optional insertAt = {}); // Return the named parameter (if it exists) std::shared_ptr getParameter(std::string_view name, const std::shared_ptr &excludeParameter = {});