Merge branch 'feature/UndoRedoWithModelData' into develop

This commit is contained in:
2025-12-22 11:39:03 +01:00
11 changed files with 419 additions and 11 deletions

View File

@ -29,8 +29,10 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
${PROJECT_SOURCES} ${PROJECT_SOURCES}
utils/messagehandler.h utils/messagehandler.h
assets/icons.qrc assets/icons.qrc
Dialogs/abstractdialog.h Dialogs/abstractdialog.cpp dialogs/abstractdialog.h dialogs/abstractdialog.cpp
Dialogs/newitemdialog.h Dialogs/newitemdialog.cpp dialogs/newitemdialog.h dialogs/newitemdialog.cpp
dialogs/edititemdialog.h dialogs/edititemdialog.cpp
views/itemdetailmapper.h views/itemdetailmapper.cpp
) )
# Define target properties for Android with Qt 6 as: # Define target properties for Android with Qt 6 as:
# set_property(TARGET ${TARGET_APP} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR # set_property(TARGET ${TARGET_APP} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR

View File

@ -0,0 +1,33 @@
#include "edititemdialog.h"
#include <QVBoxLayout>
#include "../views/itemdetailmapper.h"
EditItemDialog::EditItemDialog(QTableView* tableView, QWidget* parent)
: AbstractDialog(parent)
, m_tableView(tableView) {}
void EditItemDialog::createContent() {
if (m_contentContainer) {
delete m_contentContainer;
}
setWindowTitle(tr("Edit item..."));
m_detailMapper = new ItemDetailMapper(this);
m_detailMapper->setModelMappings(m_tableView);
m_contentContainer = m_detailMapper;
m_outerLayout->insertWidget(0, m_contentContainer);
}
void EditItemDialog::accept() {
m_detailMapper->submit();
QDialog::accept();
}
void EditItemDialog::reject() {
m_detailMapper->revert();
QDialog::reject();
}

46
dialogs/edititemdialog.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef EDITITEMDIALOG_H
#define EDITITEMDIALOG_H
#include "abstractdialog.h"
class QDoubleSpinBox;
class QLineEdit;
class QSpinBox;
class QLabel;
class QTableView;
class ItemDetailMapper;
class EditItemDialog : public AbstractDialog {
Q_OBJECT
public:
EditItemDialog(QTableView* tableView, QWidget* parent = nullptr);
/// AbstractDialog interface
void createContent() override;
public slots:
void accept() override;
void reject() override;
private:
QTableView* m_tableView = nullptr;
ItemDetailMapper* m_detailMapper;
QLabel* m_nameLabel = nullptr;
QLineEdit* m_nameEdit = nullptr;
QLabel* m_descriptionLabel = nullptr;
QLineEdit* m_descriptionEdit = nullptr;
QLabel* m_infoLabel = nullptr;
QLineEdit* m_infoEdit = nullptr;
QLabel* m_amountLabel = nullptr;
QSpinBox* m_amountBox = nullptr;
QLabel* m_factorLabel = nullptr;
QDoubleSpinBox* m_factorBox = nullptr;
};
#endif // EDITITEMDIALOG_H

View File

@ -7,6 +7,7 @@ class QDoubleSpinBox;
class QLineEdit; class QLineEdit;
class QSpinBox; class QSpinBox;
class QLabel; class QLabel;
class NewItemDialog : public AbstractDialog { class NewItemDialog : public AbstractDialog {
Q_OBJECT Q_OBJECT

View File

@ -3,10 +3,13 @@
#include <QCloseEvent> #include <QCloseEvent>
#include <QMessageBox> #include <QMessageBox>
#include <QUndoStack>
#include <QUndoView>
#include "../../ApplicationConfig.h" #include "../../ApplicationConfig.h"
#include "Dialogs/newitemdialog.h"
#include "data/settingshandler.h" #include "data/settingshandler.h"
#include "dialogs/edititemdialog.h"
#include "dialogs/newitemdialog.h"
#include "genericcore.h" #include "genericcore.h"
#include "model/tablemodel.h" #include "model/tablemodel.h"
@ -19,6 +22,7 @@ MainWindow::MainWindow(QWidget* parent)
ui->setupUi(this); ui->setupUi(this);
m_core = std::make_unique<GenericCore>(); m_core = std::make_unique<GenericCore>();
setWindowTitle(windowTitle() + " [*]");
/// application icon /// application icon
const QString iconString = "://feature.png"; const QString iconString = "://feature.png";
@ -49,10 +53,13 @@ MainWindow::MainWindow(QWidget* parent)
connect(this, &MainWindow::checkForUpdates, this, connect(this, &MainWindow::checkForUpdates, this,
&MainWindow::on_actionCheck_for_update_triggered, Qt::QueuedConnection); &MainWindow::on_actionCheck_for_update_triggered, Qt::QueuedConnection);
connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, // connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
&MainWindow::onSelectionChanged); // &MainWindow::onSelectionChanged);
connect(ui->tableView->selectionModel(), &QItemSelectionModel::currentChanged, this,
&MainWindow::onCurrentChanged);
onSelectionChanged(QItemSelection(), QItemSelection()); onSelectionChanged(QItemSelection(), QItemSelection());
onCurrentChanged(QModelIndex(), QModelIndex());
} }
MainWindow::~MainWindow() { delete ui; } MainWindow::~MainWindow() { delete ui; }
@ -100,6 +107,20 @@ void MainWindow::showStatusMessage(const QString text) {
ui->statusbar->showMessage(text); ui->statusbar->showMessage(text);
} }
void MainWindow::onCurrentChanged(const QModelIndex& current, const QModelIndex& previous) {
// Q_UNUSED(current);
Q_UNUSED(previous);
// QItemSelection localSelection = ui->tableView->selectionModel()->selection();
if (current == QModelIndex()) {
// qDebug() << "Nothing selected. Disabling delete action";
m_deleteItemAct->setEnabled(false);
} else {
// qDebug() << "Something selected. Enabling delete action";
m_deleteItemAct->setEnabled(true);
}
}
void MainWindow::onSelectionChanged(const QItemSelection& selected, void MainWindow::onSelectionChanged(const QItemSelection& selected,
const QItemSelection& deselected) { const QItemSelection& deselected) {
Q_UNUSED(selected); Q_UNUSED(selected);
@ -153,6 +174,22 @@ void MainWindow::openNewItemDialog() {
m_newItemDialog->show(); m_newItemDialog->show();
} }
void MainWindow::openEditItemDialog() {
showStatusMessage(tr("Invoked 'Edit|Edit Item'"));
m_editItemDialog->show();
}
void MainWindow::deleteCurrentItem() {
showStatusMessage(tr("Invoked 'Edit|Delete Item'"));
// QItemSelection localSelection = ui->tableView->selectionModel()->selection();
const QModelIndex currentIndex = ui->tableView->selectionModel()->currentIndex();
if (currentIndex == QModelIndex()) {
qDebug() << "No current item. Nothing to remove.";
} else {
m_tableModel->removeRows(currentIndex.row(), 1);
}
}
void MainWindow::deleteSelectedtItems() { void MainWindow::deleteSelectedtItems() {
showStatusMessage(tr("Invoked 'Edit|Delete Item'")); showStatusMessage(tr("Invoked 'Edit|Delete Item'"));
QItemSelection localSelection = ui->tableView->selectionModel()->selection(); QItemSelection localSelection = ui->tableView->selectionModel()->selection();
@ -172,9 +209,33 @@ void MainWindow::deleteSelectedtItems() {
} }
} }
void MainWindow::onCleanStateChanged(bool clean) {
qInfo() << "Slot onCleanStateChanged triggered: clean:" << clean;
setWindowModified(!clean);
if (!clean) {
ui->statusbar->clearMessage();
}
}
void MainWindow::onShowUndoViewToggled(bool checked) {
// qInfo() << "Slot onShowUndoViewToggled toggled: checked:" << checked;
// m_undoView->setVisible(checked);
/// workaround until m_showUndoViewAction is checkable
qInfo() << "Slot onShowUndoViewToggled triggered, toggleing undo view...";
Q_UNUSED(checked);
if (m_modelUndoView->isVisible()) {
m_modelUndoView->setVisible(false);
} else {
m_modelUndoView->setVisible(true);
}
}
void MainWindow::createActions() { void MainWindow::createActions() {
// TODO add generic menu actions (file/new, edit/cut, ...) // TODO add generic menu actions (file/new, edit/cut, ...)
createFileActions(); createFileActions();
createUndoActions();
createEditActions(); createEditActions();
} }
@ -232,6 +293,44 @@ void MainWindow::createFileActions() {
ui->menu_File->addAction(m_exitAct.get()); ui->menu_File->addAction(m_exitAct.get());
} }
void MainWindow::createUndoActions() {
if (!m_core) {
qCritical() << "No core instance set, aborting...";
return;
}
m_modelUndoStack = m_core->getModelUndoStack();
connect(m_modelUndoStack, &QUndoStack::cleanChanged, this, &MainWindow::onCleanStateChanged);
m_undoAct.reset(m_modelUndoStack->createUndoAction(this, tr("&Undo")));
m_undoAct->setShortcuts(QKeySequence::Undo);
m_undoAct->setStatusTip(tr("Undo the last operation"));
ui->menu_Edit->addAction(m_undoAct.get());
m_redoAct.reset(m_modelUndoStack->createRedoAction(this, tr("&Redo")));
m_redoAct->setShortcuts(QKeySequence::Redo);
m_redoAct->setStatusTip(tr("Redo the last operation"));
ui->menu_Edit->addAction(m_redoAct.get());
m_modelUndoView = make_unique<QUndoView>(m_modelUndoStack);
m_modelUndoView->setWindowTitle(tr("Undo list"));
m_modelUndoView->setAttribute(Qt::WA_QuitOnClose, false);
m_modelUndoView->setMinimumSize(450, 600);
m_showModelUndoViewAct = make_unique<QAction>(tr("Show undo/redo window"), this);
m_showModelUndoViewAct->setStatusTip(tr("Opens a window displaying the items on the undo stack"));
// TODO showUndoViewAction should be checkable
// m_showUndoViewAction->setCheckable(true);
// connect(m_showUndoViewAction, &QAction::toggled, this, &MainWindow::onShowUndoViewToggled);
connect(m_showModelUndoViewAct.get(), &QAction::triggered, this,
&MainWindow::onShowUndoViewToggled);
// TODO ? add a keyboard short cut?
// m_showUndoViewAction->setShortcuts(QKeySequence::Copy);
ui->menu_View->addAction(m_showModelUndoViewAct.get());
}
void MainWindow::createEditActions() { void MainWindow::createEditActions() {
m_cutAct = make_unique<QAction>(tr("Cu&t"), this); m_cutAct = make_unique<QAction>(tr("Cu&t"), this);
m_cutAct->setShortcuts(QKeySequence::Cut); m_cutAct->setShortcuts(QKeySequence::Cut);
@ -271,14 +370,15 @@ void MainWindow::createEditActions() {
m_openEditItemDialogAct = make_unique<QAction>(tr("&Edit item"), this); m_openEditItemDialogAct = make_unique<QAction>(tr("&Edit item"), this);
m_openEditItemDialogAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E)); m_openEditItemDialogAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E));
m_openEditItemDialogAct->setStatusTip(tr("Opens a edit dialog for the current item")); m_openEditItemDialogAct->setStatusTip(tr("Opens a edit dialog for the current item"));
// connect(m_openEditItemDialogAct, &QAction::triggered, this, &MainWindow::openEditDialog); connect(m_openEditItemDialogAct.get(), &QAction::triggered, this,
m_openEditItemDialogAct->setEnabled(false); &MainWindow::openEditItemDialog);
ui->menu_Edit->addAction(m_openEditItemDialogAct.get()); ui->menu_Edit->addAction(m_openEditItemDialogAct.get());
m_deleteItemAct = make_unique<QAction>(tr("&Delete item(s)"), this); m_deleteItemAct = make_unique<QAction>(tr("&Delete item(s)"), this);
m_deleteItemAct->setShortcuts(QKeySequence::Delete); m_deleteItemAct->setShortcuts(QKeySequence::Delete);
m_deleteItemAct->setStatusTip(tr("Delete currently selected item(s)")); m_deleteItemAct->setStatusTip(tr("Delete currently selected item(s)"));
connect(m_deleteItemAct.get(), &QAction::triggered, this, &MainWindow::deleteSelectedtItems); // connect(m_deleteItemAct.get(), &QAction::triggered, this, &MainWindow::deleteSelectedtItems);
connect(m_deleteItemAct.get(), &QAction::triggered, this, &MainWindow::deleteCurrentItem);
ui->menu_Edit->addAction(m_deleteItemAct.get()); ui->menu_Edit->addAction(m_deleteItemAct.get());
ui->menu_Edit->addSeparator(); ui->menu_Edit->addSeparator();
@ -302,8 +402,12 @@ void MainWindow::createHelpMenu() {
} }
void MainWindow::createGuiDialogs() { void MainWindow::createGuiDialogs() {
/// new item dialog
m_newItemDialog = make_unique<NewItemDialog>(this); m_newItemDialog = make_unique<NewItemDialog>(this);
m_newItemDialog->createContent(); m_newItemDialog->createContent();
connect(m_newItemDialog.get(), &NewItemDialog::addItems, m_tableModel.get(), connect(m_newItemDialog.get(), &NewItemDialog::addItems, m_tableModel.get(),
&TableModel::appendItems); &TableModel::appendItems);
/// edit item dialog
m_editItemDialog = make_unique<EditItemDialog>(ui->tableView, this);
m_editItemDialog->createContent();
} }

View File

@ -4,17 +4,20 @@
#include <QItemSelection> #include <QItemSelection>
#include <QMainWindow> #include <QMainWindow>
class NewItemDialog;
class TableModel;
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QUndoStack;
class QUndoView;
namespace Ui { namespace Ui {
class MainWindow; class MainWindow;
} }
QT_END_NAMESPACE QT_END_NAMESPACE
class GenericCore; class GenericCore;
class TableModel;
class NewItemDialog;
class EditItemDialog;
using namespace std; using namespace std;
@ -34,6 +37,7 @@ class MainWindow : public QMainWindow {
private slots: private slots:
void showStatusMessage(const QString text); void showStatusMessage(const QString text);
void onCurrentChanged(const QModelIndex& current, const QModelIndex& previous);
void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
void onAboutClicked(); void onAboutClicked();
@ -43,14 +47,21 @@ class MainWindow : public QMainWindow {
/// slots for menu actions /// slots for menu actions
void openNewItemDialog(); void openNewItemDialog();
void openEditItemDialog();
void deleteCurrentItem();
void deleteSelectedtItems(); void deleteSelectedtItems();
void onCleanStateChanged(bool clean);
void onShowUndoViewToggled(bool checked);
private: private:
Ui::MainWindow* ui; Ui::MainWindow* ui;
// GenericCore* m_core; // GenericCore* m_core;
unique_ptr<GenericCore> m_core; unique_ptr<GenericCore> m_core;
shared_ptr<TableModel> m_tableModel; shared_ptr<TableModel> m_tableModel;
QUndoStack* m_modelUndoStack;
unique_ptr<QUndoView> m_modelUndoView;
/// File actions /// File actions
unique_ptr<QAction> m_newFileAct; unique_ptr<QAction> m_newFileAct;
@ -70,13 +81,17 @@ class MainWindow : public QMainWindow {
unique_ptr<QAction> m_openEditItemDialogAct; unique_ptr<QAction> m_openEditItemDialogAct;
unique_ptr<QAction> m_deleteItemAct; unique_ptr<QAction> m_deleteItemAct;
unique_ptr<QAction> m_findItemAct; unique_ptr<QAction> m_findItemAct;
/// View actions
unique_ptr<QAction> m_showModelUndoViewAct;
/// Dialogs /// Dialogs
unique_ptr<NewItemDialog> m_newItemDialog; unique_ptr<NewItemDialog> m_newItemDialog;
unique_ptr<EditItemDialog> m_editItemDialog;
/// Setup functions /// Setup functions
void createActions(); void createActions();
void createFileActions(); void createFileActions();
void createUndoActions();
void createEditActions(); void createEditActions();
void createHelpMenu(); void createHelpMenu();
void createGuiDialogs(); void createGuiDialogs();

142
views/itemdetailmapper.cpp Normal file
View File

@ -0,0 +1,142 @@
#include "itemdetailmapper.h"
#include <QAbstractItemModel>
#include <QDataWidgetMapper>
#include <QGridLayout>
#include <QJsonArray>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QTableView>
ItemDetailMapper::ItemDetailMapper(QWidget* parent)
: QWidget{parent} {
/// model mapping
m_mapper = std::make_unique<QDataWidgetMapper>(this);
m_mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
/// model mapping buttons
m_nextButton = new QPushButton(tr("Ne&xt"));
m_previousButton = new QPushButton(tr("&Previous"));
connect(m_previousButton, &QAbstractButton::clicked, this, &ItemDetailMapper::toPrevious);
connect(m_nextButton, &QAbstractButton::clicked, this, &ItemDetailMapper::toNext);
/// the individual widgets
// REFACTOR deduce label names and types from meta data & use a factory
m_nameLabel = new QLabel("&Name");
m_nameEdit = new QLineEdit();
m_nameLabel->setBuddy(m_nameEdit);
m_descriptionLabel = new QLabel("&Description");
m_descriptionEdit = new QLineEdit();
m_descriptionLabel->setBuddy(m_descriptionEdit);
m_infoLabel = new QLabel("&Info");
m_infoEdit = new QLineEdit();
m_infoLabel->setBuddy(m_infoEdit);
m_amountLabel = new QLabel("&Amount");
m_amountBox = new QSpinBox();
m_amountBox->setMaximum(1000);
m_amountLabel->setBuddy(m_amountBox);
m_factorLabel = new QLabel("&Factor");
m_factorBox = new QDoubleSpinBox();
m_factorBox->setMaximum(1000);
m_factorLabel->setBuddy(m_factorBox);
QGridLayout* layout = new QGridLayout();
layout->addWidget(m_nameLabel, 0, 0, 1, 1);
layout->addWidget(m_nameEdit, 0, 1, 1, 1);
layout->addWidget(m_descriptionLabel, 1, 0, 1, 1);
layout->addWidget(m_descriptionEdit, 1, 1, 1, 1);
layout->addWidget(m_infoLabel, 2, 0, 1, 1);
layout->addWidget(m_infoEdit, 2, 1, 1, 1);
layout->addWidget(m_amountLabel, 3, 0, 1, 1);
layout->addWidget(m_amountBox, 3, 1, 1, 1);
layout->addWidget(m_factorLabel, 4, 0, 1, 1);
layout->addWidget(m_factorBox, 4, 1, 1, 1);
layout->addWidget(m_previousButton, 5, 0, 1, 1);
layout->addWidget(m_nextButton, 5, 1, 1, 1);
setLayout(layout);
}
void ItemDetailMapper::setModelMappings(QTableView* tableView) {
qDebug() << "Setting model...";
m_tableView = tableView;
m_model = tableView->model();
m_selectionModel = tableView->selectionModel();
m_mapper->setModel(m_model);
m_mapper->addMapping(m_nameEdit, 0);
m_mapper->addMapping(m_descriptionEdit, 1);
m_mapper->addMapping(m_infoEdit, 2);
m_mapper->addMapping(m_amountBox, 3);
m_mapper->addMapping(m_factorBox, 4);
m_mapper->setCurrentIndex(m_selectionModel->currentIndex().row());
connect(m_model, &QAbstractItemModel::rowsInserted, this, &ItemDetailMapper::rowsInserted);
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ItemDetailMapper::rowsRemoved);
connect(m_selectionModel, &QItemSelectionModel::currentChanged, this,
&ItemDetailMapper::onCurrentIndexChanged);
updateButtons(m_selectionModel->currentIndex().row());
}
bool ItemDetailMapper::submit() { return m_mapper->submit(); }
void ItemDetailMapper::revert() { m_mapper->revert(); }
void ItemDetailMapper::onCurrentIndexChanged(const QModelIndex& current,
const QModelIndex& previous) {
if (!isEnabled()) {
setEnabled(true);
}
m_mapper->setCurrentModelIndex(current);
updateButtons(current.row());
}
void ItemDetailMapper::rowsInserted(const QModelIndex& parent, int start, int end) {
updateButtons(m_mapper->currentIndex());
}
void ItemDetailMapper::rowsRemoved(const QModelIndex& parent, int start, int end) {
if (m_model->rowCount() == 0) {
setEnabled(false);
m_nameEdit->clear();
m_descriptionEdit->clear();
m_infoEdit->clear();
m_amountBox->clear();
m_factorBox->clear();
}
updateButtons(m_mapper->currentIndex());
}
void ItemDetailMapper::toPrevious() {
int currentRow = m_selectionModel->currentIndex().row();
QModelIndex previousIndex = m_selectionModel->currentIndex().siblingAtRow(currentRow - 1);
if (previousIndex.isValid()) {
m_selectionModel->setCurrentIndex(previousIndex, QItemSelectionModel::ClearAndSelect);
}
}
void ItemDetailMapper::toNext() {
int currentRow = m_selectionModel->currentIndex().row();
QModelIndex nextIndex = m_selectionModel->currentIndex().siblingAtRow(currentRow + 1);
if (nextIndex.isValid()) {
m_selectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::ClearAndSelect);
}
}
void ItemDetailMapper::updateButtons(int row) {
m_previousButton->setEnabled(row > 0);
m_nextButton->setEnabled(row < m_model->rowCount() - 1);
}

65
views/itemdetailmapper.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef ITEMDETAILMAPPER_H
#define ITEMDETAILMAPPER_H
#include <QDataWidgetMapper>
#include <QWidget>
class QLabel;
class QLineEdit;
class QDoubleSpinBox;
class QSpinBox;
class QPushButton;
class QAbstractItemModel;
class QItemSelectionModel;
class QTableView;
class ItemDetailMapper : public QWidget {
Q_OBJECT
public:
explicit ItemDetailMapper(QWidget* parent = nullptr);
void setModelMappings(QTableView* tableView);
bool submit();
void revert();
signals:
private slots:
void onCurrentIndexChanged(const QModelIndex& current, const QModelIndex& previous);
void rowsInserted(const QModelIndex& parent, int start, int end);
void rowsRemoved(const QModelIndex& parent, int start, int end);
void toPrevious();
void toNext();
void updateButtons(int row);
private:
/// *** members ***
/// Model stuff
QTableView* m_tableView = nullptr;
QAbstractItemModel* m_model = nullptr;
QItemSelectionModel* m_selectionModel = nullptr;
std::unique_ptr<QDataWidgetMapper> m_mapper;
/// GUI elements
QLabel* m_nameLabel = nullptr;
QLineEdit* m_nameEdit = nullptr;
QLabel* m_descriptionLabel = nullptr;
QLineEdit* m_descriptionEdit = nullptr;
QLabel* m_infoLabel = nullptr;
QLineEdit* m_infoEdit = nullptr;
QLabel* m_amountLabel = nullptr;
QSpinBox* m_amountBox = nullptr;
QLabel* m_factorLabel = nullptr;
QDoubleSpinBox* m_factorBox = nullptr;
QPushButton* m_nextButton;
QPushButton* m_previousButton;
};
#endif // ITEMDETAILMAPPER_H