diff --git a/CMakeLists.txt b/CMakeLists.txt index d5c4513..d5492fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(${TARGET_APP} STATIC formats/jsonparser.h formats/jsonparser.cpp model/commands/insertrowscommand.h model/commands/insertrowscommand.cpp model/commands/removerowscommand.h model/commands/removerowscommand.cpp + model/commands/edititemcommand.h model/commands/edititemcommand.cpp ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/model/commands/edititemcommand.cpp b/model/commands/edititemcommand.cpp new file mode 100644 index 0000000..4c57bc1 --- /dev/null +++ b/model/commands/edititemcommand.cpp @@ -0,0 +1,79 @@ +#include "edititemcommand.h" + +#include + +#include "../tablemodel.h" + +EditItemCommand::EditItemCommand(TableModel* model, + const QModelIndex& index, + QMap& changedValues, + QUndoCommand* parent) + : QUndoCommand(parent) + , m_model(model) + , m_row(index.row()) { + qInfo() << "New EditCommand..."; + QString commandText; + + if (changedValues.size() == 1) { + qDebug() << "Only one value to change. Using more specific command text..."; + const int role = changedValues.firstKey(); + const QVariant value = changedValues.first(); + QString roleName = model->roleNames().value(role); + switch (role) { + case TableModel::NameRole: + case TableModel::DescriptionRole: + case TableModel::InfoRole: + case TableModel::AmountRole: + case TableModel::FactorRole: + commandText = QString("Setting '%1' of item '%2' to '%3'") + .arg(roleName) + .arg(index.data(TableModel::NameRole).toString()) + .arg(value.toString()); + break; + default: + commandText = QString("Edit item '%1'").arg(index.data(TableModel::NameRole).toString()); + break; + } + } else { + qDebug() << "More than one value to change. Using a generic command text..."; + commandText = QString("Edit item '%1'").arg(index.data(TableModel::NameRole).toString()); + } + setText(commandText); + + m_newValues = changedValues; + + /// storing old values for undo step + m_oldValues = getOldValues(index, changedValues); +} + +void EditItemCommand::undo() { + qDebug() << "Undoing the EditCommand..."; + m_model->execEditItemData(m_row, m_oldValues); +} + +void EditItemCommand::redo() { + qDebug() << "(Re-)doing the EditCommand..."; + m_model->execEditItemData(m_row, m_newValues); +} + +const QMap EditItemCommand::getOldValues( + const QModelIndex& index, + const QMap& changedValues) const { + QMap result; + QMap::const_iterator i; + for (i = changedValues.constBegin(); i != changedValues.constEnd(); ++i) { + const int role = i.key(); + const QVariant newValue = i.value(); + const QVariant oldValue = index.data(role); + // TODO check if role is a editable role? + if (oldValue != newValue) { + qDebug() << "oldValue:" << oldValue << "!= newValue:" << newValue; + result.insert(role, oldValue); + } else { + qInfo() << "oldValue is already the same as newValue:" << oldValue; + } + } + // QVariant oldModifiedDate = index.data(ModifiedDateUTCRole); + // result.insert(ModifiedDateUTCRole, oldModifiedDate); + return result; +} diff --git a/model/commands/edititemcommand.h b/model/commands/edititemcommand.h new file mode 100644 index 0000000..a8a2c23 --- /dev/null +++ b/model/commands/edititemcommand.h @@ -0,0 +1,30 @@ +#ifndef EDITITEMCOMMAND_H +#define EDITITEMCOMMAND_H + +#include +#include + +class TableModel; + +class EditItemCommand : public QUndoCommand { + public: + EditItemCommand(TableModel* model, + const QModelIndex& index, + QMap& changedValues, + QUndoCommand* parent = nullptr); + /// QUndoCommand interface + void undo(); + void redo(); + + private: + TableModel* m_model = nullptr; + const int m_row; + QMap m_oldValues; + QMap m_newValues; + + /// private functions + const QMap getOldValues(const QModelIndex& index, + const QMap& changedValues) const; +}; + +#endif // EDITITEMCOMMAND_H diff --git a/model/modelitem.cpp b/model/modelitem.cpp index b199dfc..a168b7f 100644 --- a/model/modelitem.cpp +++ b/model/modelitem.cpp @@ -16,3 +16,25 @@ bool ModelItem::setData(const QVariant& value, int role) { return valueChanged; } + +bool ModelItem::setItemData(const QMap& changedValues) { + bool valueChanged = false; + + QMap::const_iterator citer = changedValues.constBegin(); + + while (citer != changedValues.constEnd()) { + const int role = citer.key(); + const QVariant value = citer.value(); + + if (m_values.contains(role)) { + if (m_values.value(role) != value) { + valueChanged = true; + } + } + m_values[role] = value; + + citer++; + } + + return valueChanged; +} diff --git a/model/modelitem.h b/model/modelitem.h index 002eafd..6f9c366 100644 --- a/model/modelitem.h +++ b/model/modelitem.h @@ -8,8 +8,9 @@ class ModelItem { ModelItem(const QHash values); QVariant data(int role) const; - bool setData(const QVariant& value, int role); + // TODO change return value to list of changed roles + bool setItemData(const QMap& changedValues); private: QHash m_values; diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index c0912a8..675309a 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -1,6 +1,7 @@ #include "tablemodel.h" #include "../formats/jsonparser.h" +#include "commands/edititemcommand.h" #include "commands/insertrowscommand.h" #include "commands/removerowscommand.h" #include "modelitem.h" @@ -10,6 +11,7 @@ QHash TableModel::ROLE_NAMES = {{NameRole, "Name"}, {InfoRole, "Info"}, {AmountRole, "Amount"}, {FactorRole, "Factor"}}; +QList TableModel::intColumns = {"Amount", "Factor"}; TableModel::TableModel(QUndoStack* undoStack, QObject* parent) : QAbstractTableModel{parent} @@ -84,18 +86,30 @@ QVariant TableModel::headerData(int section, Qt::Orientation orientation, int ro } bool TableModel::setData(const QModelIndex& index, const QVariant& value, int role) { - if (role == Qt::EditRole) { - if (!checkIndex(index)) { - return false; - } - int columnRole = getRoleForColumn(index.column()); - shared_ptr item = m_items.at(index.row()); - return item->setData(value, columnRole); + if (role == Qt::EditRole && checkIndex(index)) { + const int column = index.column(); + const int roleForColumn = getRoleForColumn(column); + return setItemData(index, {{roleForColumn, value}}); } return false; } -// bool TableModel::setItemData(const QModelIndex& index, const QMap& roles) {} +bool TableModel::setItemData(const QModelIndex& index, const QMap& roles) { + if (!checkIndex(index)) { + return false; + } + // if (isRoleReadOnly(roleForColumn)) { + // return false; + // } + + QMap changedValues = onlyChangedValues(index, roles); + if (changedValues.size() > 0) { + EditItemCommand* editCommand = new EditItemCommand(this, index, changedValues); + m_undoStack->push(editCommand); + return true; + } + return false; +} bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIndex) { if (parentIndex != QModelIndex()) { @@ -156,6 +170,22 @@ void TableModel::execRemoveItems(const int firstRow, const int nRows) { endRemoveRows(); } +void TableModel::execEditItemData(const int row, const QMap& changedValues) { + shared_ptr item = m_items.at(row); + bool isDataChanged = item->setItemData(changedValues); + + if (isDataChanged) { + /// FIXME due to the mapping from roles to the DisplayRole of different columns the complete row + /// is getting notified about (potential) data changes; dataChanged should be called only for + /// the affected columns + const QModelIndex firstIndex = this->index(row, 0); + const QModelIndex lastIndex = this->index(row, ROLE_NAMES.size() - 1); + QList roles = changedValues.keys(); + roles.insert(0, Qt::DisplayRole); + emit dataChanged(firstIndex, lastIndex, roles.toVector()); + } +} + int TableModel::getRoleForColumn(const int column) const { switch (column) { case 0: @@ -178,3 +208,36 @@ int TableModel::getRoleForColumn(const int column) const { break; } } + +QMap TableModel::onlyChangedValues(const QModelIndex& index, + const QMap& roleValueMap) const { + QMap result; + QMap::const_iterator i; + for (i = roleValueMap.constBegin(); i != roleValueMap.constEnd(); ++i) { + const int role = i.key(); + const QVariant newValue = i.value(); + const QVariant oldValue = index.data(role); + // TODO check if role is a editable role? + if (oldValue != newValue) { + bool emptyValueIsEqualToZero = isEmptyValueEqualToZero(role); + if (emptyValueIsEqualToZero && oldValue == QVariant() && newValue == 0) { + qDebug() << "oldValue:" << oldValue << "& newValue:" << newValue + << "mean the same. Ignoring..."; + continue; + } + qDebug() << "oldValue:" << oldValue << "!= newValue:" << newValue; + result.insert(role, newValue); + } else { + qInfo() << "oldValue is already the same as newValue:" << oldValue << "-> ignoring..."; + } + } + return result; +} + +bool TableModel::isEmptyValueEqualToZero(const int role) const { + const QString roleName = ROLE_NAMES.value(role); + if (intColumns.contains(roleName)) { + return true; + } + return false; +} diff --git a/model/tablemodel.h b/model/tablemodel.h index fde428c..069179e 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -13,10 +13,12 @@ class TableModel : public QAbstractTableModel { friend class InsertRowsCommand; friend class RemoveRowsCommand; + friend class EditItemCommand; public: enum UserRoles { NameRole = Qt::UserRole + 1, DescriptionRole, InfoRole, AmountRole, FactorRole }; static QHash ROLE_NAMES; + static QList intColumns; explicit TableModel(QUndoStack* undoStack, QObject* parent = nullptr); @@ -30,7 +32,7 @@ class TableModel : public QAbstractTableModel { QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; - // bool setItemData(const QModelIndex& index, const QMap& roles) override; + bool setItemData(const QModelIndex& index, const QMap& roles) override; public slots: // bool insertRows(int position, int rows, const QModelIndex& parentIndex = QModelIndex()) @@ -48,9 +50,13 @@ class TableModel : public QAbstractTableModel { /// undo/redo functions void execInsertItems(const int firstRow, const QList> valueList); void execRemoveItems(const int firstRow, const int nRows); + void execEditItemData(const int row, const QMap& changedValues); /// misc functions int getRoleForColumn(const int column) const; + QMap onlyChangedValues(const QModelIndex& index, + const QMap& roleValueMap) const; + bool isEmptyValueEqualToZero(const int role) const; }; #endif // TABLEMODEL_H