From 5b5713c89ffa592355ec7d1a93d77589c7fddc30 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Sat, 6 Dec 2025 10:34:44 +0100 Subject: [PATCH 1/5] Added a QUndoStack and made it accessible for UIs. --- CMakeLists.txt | 4 ++-- genericcore.cpp | 6 ++++++ genericcore.h | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40ab47c..9d354b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core LinguistTools) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core LinguistTools) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core LinguistTools Gui) configure_file(CoreConfig.h.in CoreConfig.h) @@ -28,7 +28,7 @@ add_library(${TARGET_APP} STATIC include_directories(${CMAKE_CURRENT_BINARY_DIR}) -target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Core) +target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui) target_compile_definitions(${TARGET_APP} PRIVATE ${TARGET_APP}_LIBRARY) diff --git a/genericcore.cpp b/genericcore.cpp index b589405..6191f1c 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -12,11 +12,15 @@ #include "constants.h" #include "model/tablemodel.h" +#include + using namespace std; GenericCore::GenericCore() { qDebug() << "Creating core..."; + m_modelUndoStack = make_shared(this); + setupModels(); } @@ -70,6 +74,8 @@ void GenericCore::triggerApplicationUpdate() { QProcess::startDetached(toolFilePath, args); } +std::shared_ptr GenericCore::getModUndoStack() const { return m_modelUndoStack; } + std::shared_ptr GenericCore::getModel() const { return m_mainModel; } void GenericCore::setupModels() { diff --git a/genericcore.h b/genericcore.h index e017fb3..9fb0176 100644 --- a/genericcore.h +++ b/genericcore.h @@ -3,6 +3,7 @@ #include +class QUndoStack; class QAbstractItemModel; class QString; @@ -21,12 +22,14 @@ class GenericCore : public QObject { bool isApplicationUpdateAvailable(); void triggerApplicationUpdate(); + std::shared_ptr getModUndoStack() const; std::shared_ptr getModel() const; signals: void displayStatusMessage(QString message); private: + std::shared_ptr m_modelUndoStack; std::shared_ptr m_mainModel; void setupModels(); From 0166a00d9d4bcdb6c1c8387e857ad87b006db916 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Thu, 11 Dec 2025 15:46:28 +0100 Subject: [PATCH 2/5] Adding new items to the model can now be made undone/redone. --- CMakeLists.txt | 1 + genericcore.cpp | 7 +++-- genericcore.h | 4 +-- model/commands/insertrowscommand.cpp | 33 +++++++++++++++++++++ model/commands/insertrowscommand.h | 28 ++++++++++++++++++ model/tablemodel.cpp | 44 +++++++++++++++++++++------- model/tablemodel.h | 15 ++++++++-- 7 files changed, 114 insertions(+), 18 deletions(-) create mode 100644 model/commands/insertrowscommand.cpp create mode 100644 model/commands/insertrowscommand.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b28f992..92cf602 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(${TARGET_APP} STATIC model/tablemodel.h model/tablemodel.cpp model/modelitem.h model/modelitem.cpp formats/jsonparser.h formats/jsonparser.cpp + model/commands/insertrowscommand.h model/commands/insertrowscommand.cpp ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/genericcore.cpp b/genericcore.cpp index 5f6c506..4cf0500 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -19,7 +19,8 @@ using namespace std; GenericCore::GenericCore() { qDebug() << "Creating core..."; - m_modelUndoStack = make_shared(this); + // TODO let the model own its undo stack (& use TableModel::getUndoStack() if necessary) + m_modelUndoStack = new QUndoStack(this); setupModels(); } @@ -74,12 +75,12 @@ void GenericCore::triggerApplicationUpdate() { QProcess::startDetached(toolFilePath, args); } -std::shared_ptr GenericCore::getModUndoStack() const { return m_modelUndoStack; } +QUndoStack* GenericCore::getModelUndoStack() const { return m_modelUndoStack; } std::shared_ptr GenericCore::getModel() const { return m_mainModel; } void GenericCore::setupModels() { - m_mainModel = make_shared(this); + m_mainModel = make_shared(m_modelUndoStack, this); // TODO add QAbstractItemModelTester } diff --git a/genericcore.h b/genericcore.h index b8f194d..20ac805 100644 --- a/genericcore.h +++ b/genericcore.h @@ -22,14 +22,14 @@ class GenericCore : public QObject { bool isApplicationUpdateAvailable(); void triggerApplicationUpdate(); - std::shared_ptr getModUndoStack() const; + QUndoStack* getModelUndoStack() const; std::shared_ptr getModel() const; signals: void displayStatusMessage(QString message); private: - std::shared_ptr m_modelUndoStack; + QUndoStack* m_modelUndoStack; std::shared_ptr m_mainModel; void setupModels(); diff --git a/model/commands/insertrowscommand.cpp b/model/commands/insertrowscommand.cpp new file mode 100644 index 0000000..231ce29 --- /dev/null +++ b/model/commands/insertrowscommand.cpp @@ -0,0 +1,33 @@ +#include "insertrowscommand.h" + +#include + +#include "../tablemodel.h" + +InsertRowsCommand::InsertRowsCommand(TableModel* model, + int startRow, + QList > valueList, + QUndoCommand* parent) + : QUndoCommand(parent) + , m_tableModel(model) + , m_startRow(startRow) + , m_valueList(valueList) { + qInfo() << "New InsertCommand..."; + const QString commandText = + QString("inserting %1 item(s) on row %2").arg(valueList.length()).arg(startRow); + setText(commandText); +} + +void InsertRowsCommand::undo() { + qDebug() << "Undoing the InsertCommand..."; + if (m_tableModel) { + m_tableModel->execRemoveItems(m_startRow, m_valueList.length()); + } +} + +void InsertRowsCommand::redo() { + qDebug() << "(Re-)doing the InsertCommand..."; + if (m_tableModel) { + m_tableModel->execInsertItems(m_startRow, m_valueList); + } +} diff --git a/model/commands/insertrowscommand.h b/model/commands/insertrowscommand.h new file mode 100644 index 0000000..0341226 --- /dev/null +++ b/model/commands/insertrowscommand.h @@ -0,0 +1,28 @@ +#ifndef INSERTROWSCOMMAND_H +#define INSERTROWSCOMMAND_H + +#include + +class TableModel; + +class InsertRowsCommand : public QUndoCommand { + public: + // TODO don't use simple pointer to model + /// Using simple pointer to model because there was a crash when closing the application with an + /// unclean undo stack + InsertRowsCommand(TableModel* model, + int startRow, + QList > valueList, + QUndoCommand* parent = nullptr); + + /// QUndoCommand interface + void undo() override; + void redo() override; + + private: + TableModel* m_tableModel; + const int m_startRow; + const QList > m_valueList; +}; + +#endif // INSERTROWSCOMMAND_H diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 7a05900..81f15da 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -1,6 +1,7 @@ #include "tablemodel.h" #include "../formats/jsonparser.h" +#include "commands/insertrowscommand.h" #include "modelitem.h" QHash TableModel::ROLE_NAMES = {{NameRole, "Name"}, @@ -9,8 +10,9 @@ QHash TableModel::ROLE_NAMES = {{NameRole, "Name"}, {AmountRole, "Amount"}, {FactorRole, "Factor"}}; -TableModel::TableModel(QObject* parent) - : QAbstractTableModel{parent} { +TableModel::TableModel(QUndoStack* undoStack, QObject* parent) + : QAbstractTableModel{parent} + , m_undoStack(undoStack) { for (int row = 0; row < 5; ++row) { QHash values; values[NameRole] = QString("Item %1").arg(row); @@ -56,6 +58,12 @@ QVariant TableModel::data(const QModelIndex& index, int role) const { case Qt::DisplayRole: case Qt::EditRole: return m_items.at(row)->data(roleForColumn); + case NameRole: + case DescriptionRole: + case InfoRole: + case AmountRole: + case FactorRole: + return m_items.at(row)->data(role); } return QVariant(); @@ -122,14 +130,9 @@ void TableModel::insertItems(int startPosition, } QList> valueList = JsonParser::toItemValuesList(jsonDoc); - const int nRows = valueList.size(); - beginInsertRows(QModelIndex(), startPosition, startPosition + nRows - 1); - for (int row = 0; row < nRows; ++row) { - const int rowPosition = startPosition + row; - shared_ptr item = make_unique(valueList.at(row)); - m_items.insert(rowPosition, std::move(item)); - } - endInsertRows(); + + InsertRowsCommand* insertCommand = new InsertRowsCommand(this, startPosition, valueList); + m_undoStack->push(insertCommand); } int TableModel::getRoleForColumn(const int column) const { @@ -154,3 +157,24 @@ int TableModel::getRoleForColumn(const int column) const { break; } } + +void TableModel::execInsertItems(const int firstRow, const QList> valueList) { + const int nRows = valueList.size(); + qDebug() << "Inserting" << nRows << "items..."; + + const int lastRow = firstRow + nRows - 1; + beginInsertRows(QModelIndex(), firstRow, lastRow); + for (int row = 0; row < nRows; ++row) { + const int rowPosition = firstRow + row; + shared_ptr item = make_unique(valueList.at(row)); + m_items.insert(rowPosition, std::move(item)); + } + endInsertRows(); +} + +void TableModel::execRemoveItems(const int firstRow, const int nRows) { + const int lastRow = firstRow + nRows - 1; + beginRemoveRows(QModelIndex(), firstRow, lastRow); + m_items.remove(firstRow, nRows); + endRemoveRows(); +} diff --git a/model/tablemodel.h b/model/tablemodel.h index 5778f52..d8e0455 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -3,6 +3,7 @@ #include +class QUndoStack; class ModelItem; using namespace std; @@ -10,11 +11,13 @@ using namespace std; class TableModel : public QAbstractTableModel { Q_OBJECT + friend class InsertRowsCommand; + public: enum UserRoles { NameRole = Qt::UserRole + 1, DescriptionRole, InfoRole, AmountRole, FactorRole }; static QHash ROLE_NAMES; - explicit TableModel(QObject* parent = nullptr); + explicit TableModel(QUndoStack* undoStack, QObject* parent = nullptr); /// QAbstractItemModel interface Qt::ItemFlags flags(const QModelIndex& index) const override; @@ -36,10 +39,16 @@ class TableModel : public QAbstractTableModel { void insertItems(int startPosition, const QByteArray& jsonDoc, const QModelIndex& parentIndex); private: - /// members + /// *** members *** QList> m_items; + QUndoStack* m_undoStack; - /// functions + /// *** functions *** + /// undo/redo functions + void execInsertItems(const int firstRow, const QList> valueList); + void execRemoveItems(const int firstRow, const int nRows); + + /// misc functions int getRoleForColumn(const int column) const; }; From c75d35179b723b35224f3e4d3f75c957f9390b08 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Thu, 11 Dec 2025 15:49:16 +0100 Subject: [PATCH 3/5] Deleting items from the model can now be made undone/redone. --- CMakeLists.txt | 1 + model/commands/removerowscommand.cpp | 47 ++++++++++++++++++++++++++++ model/commands/removerowscommand.h | 28 +++++++++++++++++ model/tablemodel.cpp | 6 ++-- model/tablemodel.h | 1 + 5 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 model/commands/removerowscommand.cpp create mode 100644 model/commands/removerowscommand.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 92cf602..d5c4513 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ add_library(${TARGET_APP} STATIC model/modelitem.h model/modelitem.cpp formats/jsonparser.h formats/jsonparser.cpp model/commands/insertrowscommand.h model/commands/insertrowscommand.cpp + model/commands/removerowscommand.h model/commands/removerowscommand.cpp ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/model/commands/removerowscommand.cpp b/model/commands/removerowscommand.cpp new file mode 100644 index 0000000..306c9aa --- /dev/null +++ b/model/commands/removerowscommand.cpp @@ -0,0 +1,47 @@ +#include "removerowscommand.h" + +#include + +#include "../tablemodel.h" + +RemoveRowsCommand::RemoveRowsCommand(TableModel* model, + const int startRow, + const int nRows, + QUndoCommand* parent) + : QUndoCommand(parent) + , m_tableModel(model) + , m_startRow(startRow) { + qInfo() << "New RemoveCommand..."; + const QString commandText = + QString("removing %1 item(s) on position %2").arg(nRows).arg(startRow); + setText(commandText); + + for (int row = 0; row < nRows; ++row) { + const int rowPosition = startRow + row; + QModelIndex index = m_tableModel->index(rowPosition, 0); + + // TODO use a (static) function "getRoleValueHash" or something + QHash values; + values[TableModel::NameRole] = m_tableModel->data(index, TableModel::NameRole); + values[TableModel::DescriptionRole] = m_tableModel->data(index, TableModel::DescriptionRole); + values[TableModel::InfoRole] = m_tableModel->data(index, TableModel::InfoRole); + values[TableModel::AmountRole] = m_tableModel->data(index, TableModel::AmountRole); + values[TableModel::FactorRole] = m_tableModel->data(index, TableModel::FactorRole); + + m_valueList.append(values); + } +} + +void RemoveRowsCommand::undo() { + qDebug() << "Undoing the RemoveCommand..."; + if (m_tableModel) { + m_tableModel->execInsertItems(m_startRow, m_valueList); + } +} + +void RemoveRowsCommand::redo() { + qDebug() << "(Re-)doing the RemoveCommand..."; + if (m_tableModel) { + m_tableModel->execRemoveItems(m_startRow, m_valueList.length()); + } +} diff --git a/model/commands/removerowscommand.h b/model/commands/removerowscommand.h new file mode 100644 index 0000000..308f78b --- /dev/null +++ b/model/commands/removerowscommand.h @@ -0,0 +1,28 @@ +#ifndef REMOVEROWSCOMMAND_H +#define REMOVEROWSCOMMAND_H + +#include + +class TableModel; + +class RemoveRowsCommand : public QUndoCommand { + public: + // TODO don't use simple pointer to model + /// Using simple pointer to model because there was a crash when closing the application with an + /// unclean undo stack + RemoveRowsCommand(TableModel* model, + const int startRow, + const int nRows, + QUndoCommand* parent = nullptr); + + /// QUndoCommand interface + void undo() override; + void redo() override; + + private: + TableModel* m_tableModel; + const int m_startRow; + QList> m_valueList; +}; + +#endif // REMOVEROWSCOMMAND_H diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 81f15da..68fb51e 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -2,6 +2,7 @@ #include "../formats/jsonparser.h" #include "commands/insertrowscommand.h" +#include "commands/removerowscommand.h" #include "modelitem.h" QHash TableModel::ROLE_NAMES = {{NameRole, "Name"}, @@ -108,9 +109,8 @@ bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIn return false; } - beginRemoveRows(QModelIndex(), firstRow, lastRow); - m_items.remove(firstRow, nRows); - endRemoveRows(); + RemoveRowsCommand* removeCommand = new RemoveRowsCommand(this, firstRow, nRows); + m_undoStack->push(removeCommand); return true; } diff --git a/model/tablemodel.h b/model/tablemodel.h index d8e0455..fde428c 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -12,6 +12,7 @@ class TableModel : public QAbstractTableModel { Q_OBJECT friend class InsertRowsCommand; + friend class RemoveRowsCommand; public: enum UserRoles { NameRole = Qt::UserRole + 1, DescriptionRole, InfoRole, AmountRole, FactorRole }; From 772ab6b2ffb509796e1d25b2eb301584f8794b77 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Fri, 12 Dec 2025 13:22:27 +0100 Subject: [PATCH 4/5] Reordered function implementations according to declarations in header file. --- model/tablemodel.cpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 68fb51e..c0912a8 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -135,6 +135,27 @@ void TableModel::insertItems(int startPosition, m_undoStack->push(insertCommand); } +void TableModel::execInsertItems(const int firstRow, const QList> valueList) { + const int nRows = valueList.size(); + qDebug() << "Inserting" << nRows << "items..."; + + const int lastRow = firstRow + nRows - 1; + beginInsertRows(QModelIndex(), firstRow, lastRow); + for (int row = 0; row < nRows; ++row) { + const int rowPosition = firstRow + row; + shared_ptr item = make_unique(valueList.at(row)); + m_items.insert(rowPosition, std::move(item)); + } + endInsertRows(); +} + +void TableModel::execRemoveItems(const int firstRow, const int nRows) { + const int lastRow = firstRow + nRows - 1; + beginRemoveRows(QModelIndex(), firstRow, lastRow); + m_items.remove(firstRow, nRows); + endRemoveRows(); +} + int TableModel::getRoleForColumn(const int column) const { switch (column) { case 0: @@ -157,24 +178,3 @@ int TableModel::getRoleForColumn(const int column) const { break; } } - -void TableModel::execInsertItems(const int firstRow, const QList> valueList) { - const int nRows = valueList.size(); - qDebug() << "Inserting" << nRows << "items..."; - - const int lastRow = firstRow + nRows - 1; - beginInsertRows(QModelIndex(), firstRow, lastRow); - for (int row = 0; row < nRows; ++row) { - const int rowPosition = firstRow + row; - shared_ptr item = make_unique(valueList.at(row)); - m_items.insert(rowPosition, std::move(item)); - } - endInsertRows(); -} - -void TableModel::execRemoveItems(const int firstRow, const int nRows) { - const int lastRow = firstRow + nRows - 1; - beginRemoveRows(QModelIndex(), firstRow, lastRow); - m_items.remove(firstRow, nRows); - endRemoveRows(); -} From e54204e3944ec1a48e7932afd7324d7177254053 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Mon, 15 Dec 2025 18:04:14 +0100 Subject: [PATCH 5/5] Editing an item is now undo/redo-able. --- CMakeLists.txt | 1 + model/commands/edititemcommand.cpp | 79 ++++++++++++++++++++++++++++++ model/commands/edititemcommand.h | 30 ++++++++++++ model/modelitem.cpp | 22 +++++++++ model/modelitem.h | 3 +- model/tablemodel.cpp | 79 +++++++++++++++++++++++++++--- model/tablemodel.h | 8 ++- 7 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 model/commands/edititemcommand.cpp create mode 100644 model/commands/edititemcommand.h 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