Compare commits

...

32 Commits

Author SHA1 Message Date
683f5d224c Changing submit policy of QDataWidgetMapper to AutoSubmit because if multiple changes are made at once not all of them are applied. 2026-01-01 09:59:12 +01:00
bcba8f8452 Using JsonParser to generate JSON document to pass in the addItems signal & using roles instead of their names (compiler will complain if a role is not existent anymore). 2025-12-31 10:47:28 +01:00
12e5596b34 Merge branch 'feature/saveLoadItemsInFromFile' into develop 2025-12-23 14:02:12 +01:00
8333be2044 Items can be saved via menu action and when exiting application with unclean undo stack. 2025-12-23 10:18:00 +01:00
6cd0519237 Using the application name set in the core (with "-dev" suffix in debug builds) as window title. 2025-12-23 10:08:11 +01:00
d94a928fc0 Merge branch 'feature/UndoRedoWithModelData' into develop 2025-12-22 15:53:16 +01:00
fd7e0975f5 Merge branch 'feature/UndoRedoWithModelData' into develop 2025-12-22 11:39:03 +01:00
b3f83ccdb0 Using QDataWidgetMapper in ItemDetailMapper to work with the model data (current index of the TableView). 2025-12-22 11:21:44 +01:00
8d523bb5bc Moved EditItemDialog content widget into separate widget class ItemDetailMapper. 2025-12-18 11:00:53 +01:00
467ab58fae Renamed source code folder "Dialogs" into "dialogs". 2025-12-17 10:39:34 +01:00
47338e5ae2 Added an EditItemDialog dummy, based on the NewItemDialog. 2025-12-17 10:33:18 +01:00
902ed1c84b Added a undo view and react to change of the cleanness of the undo stack. 2025-12-11 16:01:48 +01:00
ec0d72953f Merge branch 'develop' into feature/UndoRedoWithModelData 2025-12-09 09:59:16 +01:00
c80a692a28 Selected items can be deleted from model via menu action "Delete item(s)" 2025-12-08 15:05:04 +01:00
c51c06ba50 The "factor" value can be inserted as a floating point. 2025-12-08 13:28:01 +01:00
ea75e57dbf An item can be added to the model with accepting the NewItemDialog. 2025-12-08 13:26:42 +01:00
928b795f4e Added an AbstractDialog class and using it as base for a NewItemDialog class (no new item functionality yet). 2025-12-07 14:50:58 +01:00
370bcd5a41 Retrieve QUndoStack pointer from core and create actions for undo and redo. 2025-12-06 10:36:21 +01:00
c7012ceff5 Added menu action dummies for the menus "File" and "Edit". 2025-12-04 16:44:31 +01:00
5e54f52cd6 Removed obsolete TODO comment. 2025-12-03 11:09:13 +01:00
64a65b5916 Retrieving model from the core and showing its data in a QTableView. 2025-12-02 16:01:44 +01:00
9a30fb2aad Fix: Don't create a second "Help" menu for the "About" dialogs. 2025-11-04 21:44:06 +01:00
8105fac8d3 Updated Widgets-UI project version to 0.1.0 2025-11-04 21:23:11 +01:00
7dd9d3d95b Added "About" & "About Qt" dialogs. 2025-11-04 21:21:28 +01:00
c8825bfe14 Disable translations because it throws errors when compiling in Release mode. 2025-11-04 21:19:31 +01:00
43df76c88f Available update can be checked via menu action Help/"Check for update". 2025-10-31 15:31:33 +01:00
dd8847d63a Save and restore window geometry and state. 2025-10-31 15:23:21 +01:00
f359a1fb11 Displaying status messages from the core. Using non-smart pointer for GenericCore to connect to its signal. 2025-10-31 15:02:14 +01:00
9c67fdabdc Renamed target for cleaner build script. 2025-10-29 11:48:58 +01:00
641cf02db4 Added window icon (and others). Plus minor fixes. 2025-10-26 13:03:20 +01:00
1b0e257602 Added a message handler to have colored console output in debug mode. 2025-10-02 16:46:50 +02:00
3ed9e21b93 Simple widgets UI to demonstrate usage of GenericCore library. 2025-10-02 15:03:26 +02:00
21 changed files with 1484 additions and 0 deletions

83
.gitignore vendored Normal file
View File

@ -0,0 +1,83 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
*.qbs.user*
CMakeLists.txt.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe
# Directories with generated files
.moc/
.obj/
.pch/
.rcc/
.uic/
/build*/
_build*/

91
CMakeLists.txt Normal file
View File

@ -0,0 +1,91 @@
cmake_minimum_required(VERSION 3.16)
set(TARGET_APP "GenericQtClient-Widgets")
project(${TARGET_APP} VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
set(TS_FILES ${TARGET_APP}_en_US.ts)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
${TS_FILES}
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(${TARGET_APP}
MANUAL_FINALIZATION
${PROJECT_SOURCES}
utils/messagehandler.h
assets/icons.qrc
dialogs/abstractdialog.h dialogs/abstractdialog.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:
# set_property(TARGET ${TARGET_APP} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
# qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
else()
if(ANDROID)
add_library(${TARGET_APP} SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(${TARGET_APP}
${PROJECT_SOURCES}
)
endif()
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
endif()
include_directories(${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/)
target_link_libraries(${TARGET_APP} PRIVATE GenericCore)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.${TARGET_APP})
endif()
set_target_properties(${TARGET_APP} PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
include(GNUInstallDirs)
install(TARGETS ${TARGET_APP}
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(${TARGET_APP})
endif()

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US"></TS>

BIN
assets/feature.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

7
assets/icons.qrc Normal file
View File

@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/">
<file>software-application.png</file>
<file>feature.png</file>
<file>no-picture-taking.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

5
assets/urls.txt Normal file
View File

@ -0,0 +1,5 @@
software-application.png:
https://www.flaticon.com/free-icon/software-application_5063917
feature.png:
https://www.flaticon.com/free-icon/feature_1085784

View File

@ -0,0 +1,51 @@
#include "abstractdialog.h"
#include <QDialogButtonBox>
#include <QGuiApplication>
#include <QLabel>
#include <QScreen>
#include <QVBoxLayout>
AbstractDialog::AbstractDialog(QDialogButtonBox::StandardButtons buttons, QWidget* parent)
: QDialog(parent) {
setWindowTitle(tr("Dialog does what?..."));
setModal(true);
m_buttonBox = new QDialogButtonBox(buttons, this);
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &AbstractDialog::accept);
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &AbstractDialog::reject);
m_contentContainer = new QLabel("content", this);
m_outerLayout = new QVBoxLayout;
m_outerLayout->addWidget(m_contentContainer);
m_outerLayout->addWidget(m_buttonBox);
setLayout(m_outerLayout);
}
void AbstractDialog::show() {
centerInParent();
QWidget::show();
}
void AbstractDialog::accept() { QDialog::accept(); }
void AbstractDialog::reject() { QDialog::reject(); }
void AbstractDialog::centerInParent() {
// BUG the centering in the parent doesn't work the first time (and the later ones seem off too)
QWidget* parent = parentWidget();
if (parent) {
auto parentRect = parent->geometry();
move(parentRect.center() - rect().center());
} else {
QRect screenGeometry = QGuiApplication::screens().at(0)->geometry();
int x = (screenGeometry.width() - width()) / 2;
int y = (screenGeometry.height() - height()) / 2;
move(x, y);
}
}
void AbstractDialog::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); }

33
dialogs/abstractdialog.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef ABSTRACTDIALOG_H
#define ABSTRACTDIALOG_H
#include <QDialog>
#include <QDialogButtonBox>
class QGridLayout;
class QVBoxLayout;
class AbstractDialog : public QDialog {
Q_OBJECT
public:
AbstractDialog(QDialogButtonBox::StandardButtons buttons, QWidget* parent = nullptr);
virtual void createContent() = 0;
/// QDialog interface
public slots:
void show();
void accept() override;
void reject() override;
protected:
void centerInParent();
protected:
QWidget* m_contentContainer;
QVBoxLayout* m_outerLayout;
QDialogButtonBox* m_buttonBox = nullptr;
void closeEvent(QCloseEvent* event) override;
};
#endif // ABSTRACTDIALOG_H

View File

@ -0,0 +1,34 @@
#include "edititemdialog.h"
#include <QDialogButtonBox>
#include <QVBoxLayout>
#include "../views/itemdetailmapper.h"
EditItemDialog::EditItemDialog(QTableView* tableView, QWidget* parent)
: AbstractDialog(QDialogButtonBox::Close, 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

81
dialogs/newitemdialog.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "newitemdialog.h"
#include <QGridLayout>
#include <QJsonArray>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include "formats/jsonparser.h"
#include "model/metadata.h"
NewItemDialog::NewItemDialog(QWidget* parent)
: AbstractDialog(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, parent) {}
void NewItemDialog::createContent() {
if (m_contentContainer) {
delete m_contentContainer;
}
setWindowTitle(tr("New item..."));
m_contentContainer = new QWidget(this);
// REFACTOR use a data structure for input widgets which can be iterated through
// using a factory which iterates through the roles from metadata.h
// and create the input widgets based on the data type of this role
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);
m_contentContainer->setLayout(layout);
m_outerLayout->insertWidget(0, m_contentContainer);
}
void NewItemDialog::accept() {
QHash<int, QVariant> itemValues;
// TODO (after refactoring data structure for input widgets) use iteration through the relevant
// roles and their input widgets
itemValues.insert(NameRole, m_nameEdit->text());
itemValues.insert(DescriptionRole, m_descriptionEdit->text());
itemValues.insert(InfoRole, m_infoEdit->text());
itemValues.insert(AmountRole, m_amountBox->value());
itemValues.insert(FactorRole, m_factorBox->value());
const QByteArray jsonDoc = JsonParser::itemValuesListToJson({itemValues}, ITEM_KEY_STRING);
emit addItems(jsonDoc);
// resetContent();
AbstractDialog::accept();
}

43
dialogs/newitemdialog.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef NEWITEMDIALOG_H
#define NEWITEMDIALOG_H
#include "abstractdialog.h"
class QDoubleSpinBox;
class QLineEdit;
class QSpinBox;
class QLabel;
class NewItemDialog : public AbstractDialog {
Q_OBJECT
public:
NewItemDialog(QWidget* parent = nullptr);
void createContent() override;
signals:
void addItems(const QByteArray& jsonDoc);
public slots:
void accept() override;
// void reject() override;
private:
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 // NEWITEMDIALOG_H

29
main.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "mainwindow.h"
#include <QApplication>
#include <QLocale>
#include <QTranslator>
#ifdef QT_DEBUG
#include "utils/messagehandler.h"
#endif
int main(int argc, char* argv[]) {
#ifdef QT_DEBUG
qInstallMessageHandler(consoleHandlerColoredVerboseInDarkTheme);
#endif
QApplication a(argc, argv);
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
for (const QString& locale : uiLanguages) {
const QString baseName = "GenericWidgets_" + QLocale(locale).name();
if (translator.load(":/i18n/" + baseName)) {
a.installTranslator(&translator);
break;
}
}
MainWindow w;
w.show();
return a.exec();
}

414
mainwindow.cpp Normal file
View File

@ -0,0 +1,414 @@
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QCloseEvent>
#include <QMessageBox>
#include <QUndoStack>
#include <QUndoView>
#include "../../ApplicationConfig.h"
#include "data/settingshandler.h"
#include "dialogs/edititemdialog.h"
#include "dialogs/newitemdialog.h"
#include "genericcore.h"
#include "model/tablemodel.h"
static QString updateTextClean = "Do you want to update the application now?";
static QString updateTextDirty = "Do you want to save the tasks & update the application now?";
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) {
ui->setupUi(this);
m_core = std::make_unique<GenericCore>();
setWindowTitle(QCoreApplication::applicationName() + " [*]");
/// application icon
const QString iconString = "://feature.png";
#ifdef QT_DEBUG
QPixmap pixmap = QPixmap(iconString);
QTransform transform = QTransform();
transform.rotate(180);
QPixmap rotated = pixmap.transformed(transform);
setWindowIcon(QIcon(rotated));
#else
setWindowIcon(QIcon(iconString));
#endif
const QVariantMap settings = SettingsHandler::getSettings("GUI");
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowState").toByteArray());
m_tableModel = m_core->getModel();
ui->tableView->setModel(m_tableModel.get());
createActions();
createHelpMenu();
createGuiDialogs();
connect(m_core.get(), &GenericCore::displayStatusMessage, this,
&MainWindow::displayStatusMessage);
connect(this, &MainWindow::displayStatusMessage, this, &MainWindow::showStatusMessage);
connect(this, &MainWindow::checkForUpdates, this,
&MainWindow::on_actionCheck_for_update_triggered, Qt::QueuedConnection);
// connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
// &MainWindow::onSelectionChanged);
connect(ui->tableView->selectionModel(), &QItemSelectionModel::currentChanged, this,
&MainWindow::onCurrentChanged);
onSelectionChanged(QItemSelection(), QItemSelection());
onCurrentChanged(QModelIndex(), QModelIndex());
}
MainWindow::~MainWindow() { delete ui; }
void MainWindow::closeEvent(QCloseEvent* event) {
if (isWindowModified()) {
QMessageBox msgBox;
msgBox.setWindowTitle(windowTitle() + " - Save dialog");
msgBox.setText("The document has been modified.");
msgBox.setInformativeText("Do you want to save your changes?");
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret = msgBox.exec();
switch (ret) {
case QMessageBox::Save:
emit saveItems();
event->accept();
break;
case QMessageBox::Discard:
event->accept();
break;
case QMessageBox::Cancel:
event->ignore();
break;
default:
/// should never be reached
qCritical() << "unexpected switch case in closeEvent:" << ret;
event->ignore();
break;
}
} else {
event->accept();
}
if (event->isAccepted()) {
qInfo() << "Saving GUI settings...";
SettingsHandler::saveSettings({{"geometry", saveGeometry()}, {"windowState", saveState()}},
"GUI");
}
}
void MainWindow::showStatusMessage(const QString text) {
qInfo() << 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,
const QItemSelection& deselected) {
Q_UNUSED(selected);
Q_UNUSED(deselected);
QItemSelection localSelection = ui->tableView->selectionModel()->selection();
if (localSelection.empty()) {
// qDebug() << "Nothing selected. Disabling delete action";
m_deleteItemAct->setEnabled(false);
} else {
// qDebug() << "Something selected. Enabling delete action";
m_deleteItemAct->setEnabled(true);
}
}
void MainWindow::onAboutClicked() {
const QString applicationName = APPLICATION_NAME;
const QString titlePrefix = tr("About ");
const QString aboutText =
tr(QString("<b>%1 v%2</b> is a template for Qt applications."
"<br><br><a href=\"https://working-copy.org/\">Working-Copy_Collective website</a>"
"<br><br><a href=\"mailto:support@working-copy.org\">Mail to support</a>"
"<br><br>It uses the <a href=\"https://qt.io\">Qt Framework</a>.")
.arg(applicationName)
.arg(APPLICATION_VERSION)
.toLatin1());
QMessageBox::about(this, titlePrefix + applicationName, aboutText);
}
void MainWindow::on_actionCheck_for_update_triggered() {
showStatusMessage("Checking for update...");
const bool updateAvailable = m_core->isApplicationUpdateAvailable();
if (updateAvailable) {
const QString text = isWindowModified() ? updateTextDirty : updateTextClean;
const QMessageBox::StandardButton clickedButton = QMessageBox::question(
this, tr("Update available."), text, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (clickedButton == QMessageBox::Yes) {
m_core->triggerApplicationUpdate();
close();
}
}
}
void MainWindow::on_pushButton_clicked() {
const QString prefix("Backend provided by: ");
ui->label->setText(prefix + m_core->toString());
}
void MainWindow::openNewItemDialog() {
showStatusMessage(tr("Invoked 'Edit|New Item'"));
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() {
showStatusMessage(tr("Invoked 'Edit|Delete Item'"));
QItemSelection localSelection = ui->tableView->selectionModel()->selection();
if (localSelection.empty()) {
qDebug() << "No items selected. Nothing to remove.";
} else {
for (QList<QItemSelectionRange>::reverse_iterator iter = localSelection.rbegin(),
rend = localSelection.rend();
iter != rend; ++iter) {
// qInfo() << "iter:" << *iter;
// const QModelIndex parentIndex = iter->parent();
const int topRow = iter->top();
const int bottomRow = iter->bottom();
const int nRows = bottomRow - topRow + 1;
m_tableModel->removeRows(topRow, nRows);
}
}
}
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::saveItems() {
showStatusMessage(tr("Invoked 'File|Save'"));
m_core->saveItems();
}
void MainWindow::createActions() {
// TODO add generic menu actions (file/new, edit/cut, ...)
createFileActions();
createUndoActions();
createEditActions();
}
void MainWindow::createFileActions() {
m_newFileAct = make_unique<QAction>(tr("&New File"), this);
m_newFileAct->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_N));
m_newFileAct->setStatusTip(tr("Create a new file"));
// connect(m_newAct, &QAction::triggered, this, &MainWindow::newFile);
m_newFileAct->setEnabled(false);
ui->menu_File->addAction(m_newFileAct.get());
m_openAct = make_unique<QAction>(tr("&Open..."), this);
m_openAct->setShortcuts(QKeySequence::Open);
m_openAct->setStatusTip(tr("Open an existing file"));
// connect(m_openAct, &QAction::triggered, this, &MainWindow::open);
m_openAct->setEnabled(false);
ui->menu_File->addAction(m_openAct.get());
m_saveAct = make_unique<QAction>(tr("&Save"), this);
m_saveAct->setShortcuts(QKeySequence::Save);
m_saveAct->setStatusTip(tr("Save the document to disk"));
connect(m_saveAct.get(), &QAction::triggered, this, &MainWindow::saveItems);
ui->menu_File->addAction(m_saveAct.get());
ui->menu_File->addSeparator();
m_importAct = make_unique<QAction>(tr("&Import CSV..."), this);
m_importAct->setStatusTip(tr("Import an existing CSV file"));
// connect(m_importAct, &QAction::triggered, this, &MainWindow::importCSV);
m_importAct->setEnabled(false);
ui->menu_File->addAction(m_importAct.get());
m_exportAct = make_unique<QAction>(tr("&Export CSV..."), this);
m_exportAct->setStatusTip(tr("Export content to a CSV document to disk"));
// connect(m_exportAct, &QAction::triggered, this, &MainWindow::exportCSV);
m_exportAct->setEnabled(false);
ui->menu_File->addAction(m_exportAct.get());
ui->menu_File->addSeparator();
m_printAct = make_unique<QAction>(tr("&Print..."), this);
m_printAct->setShortcuts(QKeySequence::Print);
m_printAct->setStatusTip(tr("Print the document"));
// connect(m_printAct, &QAction::triggered, this, &MainWindow::print);
m_printAct->setEnabled(false);
ui->menu_File->addAction(m_printAct.get());
ui->menu_File->addSeparator();
m_exitAct = make_unique<QAction>(tr("E&xit"), this);
m_exitAct->setShortcuts(QKeySequence::Quit);
m_exitAct->setStatusTip(tr("Exit the application"));
connect(m_exitAct.get(), &QAction::triggered, this, &QWidget::close);
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);
ui->menu_View->addAction(m_showModelUndoViewAct.get());
}
void MainWindow::createEditActions() {
m_cutAct = make_unique<QAction>(tr("Cu&t"), this);
m_cutAct->setShortcuts(QKeySequence::Cut);
m_cutAct->setStatusTip(
tr("Cut the current selection's contents to the "
"clipboard"));
// connect(m_cutAct, &QAction::triggered, this, &MainWindow::cut);
m_cutAct->setEnabled(false);
ui->menu_Edit->addAction(m_cutAct.get());
m_copyAct = make_unique<QAction>(tr("&Copy"), this);
m_copyAct->setShortcuts(QKeySequence::Copy);
m_copyAct->setStatusTip(
tr("Copy the current selection's contents to the "
"clipboard"));
// connect(m_copyAct, &QAction::triggered, this, &MainWindow::copy);
m_copyAct->setEnabled(false);
ui->menu_Edit->addAction(m_copyAct.get());
m_pasteAct = make_unique<QAction>(tr("&Paste"), this);
m_pasteAct->setShortcuts(QKeySequence::Paste);
m_pasteAct->setStatusTip(
tr("Paste the clipboard's contents into the current "
"selection"));
// connect(m_pasteAct, &QAction::triggered, this, &MainWindow::paste);
m_pasteAct->setEnabled(false);
ui->menu_Edit->addAction(m_pasteAct.get());
ui->menu_Edit->addSeparator();
m_openNewItemDialogAct = make_unique<QAction>(tr("&New item"), this);
m_openNewItemDialogAct->setShortcut(QKeySequence::New);
m_openNewItemDialogAct->setStatusTip(tr("Opens a dialog to add a new item"));
connect(m_openNewItemDialogAct.get(), &QAction::triggered, this, &MainWindow::openNewItemDialog);
ui->menu_Edit->addAction(m_openNewItemDialogAct.get());
m_openEditItemDialogAct = make_unique<QAction>(tr("&Edit item"), this);
m_openEditItemDialogAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E));
m_openEditItemDialogAct->setStatusTip(tr("Opens a edit dialog for the current item"));
connect(m_openEditItemDialogAct.get(), &QAction::triggered, this,
&MainWindow::openEditItemDialog);
ui->menu_Edit->addAction(m_openEditItemDialogAct.get());
m_deleteItemAct = make_unique<QAction>(tr("&Delete item(s)"), this);
m_deleteItemAct->setShortcuts(QKeySequence::Delete);
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::deleteCurrentItem);
ui->menu_Edit->addAction(m_deleteItemAct.get());
ui->menu_Edit->addSeparator();
m_findItemAct = make_unique<QAction>(tr("&Find item(s)"), this);
m_findItemAct->setShortcuts(QKeySequence::Find);
m_findItemAct->setStatusTip(tr("Opens a dialog to find item(s) by containing text"));
// connect(m_findItemAct, &QAction::triggered, this, &MainWindow::findItems);
m_findItemAct->setEnabled(false);
ui->menu_Edit->addAction(m_findItemAct.get());
}
void MainWindow::createHelpMenu() {
QMenu* helpMenu = ui->menu_Help;
helpMenu->addSeparator();
QAction* aboutAct = helpMenu->addAction(tr("&About"), this, &MainWindow::onAboutClicked);
aboutAct->setStatusTip(tr("Show the application's About box"));
QAction* aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
}
void MainWindow::createGuiDialogs() {
/// new item dialog
m_newItemDialog = make_unique<NewItemDialog>(this);
m_newItemDialog->createContent();
connect(m_newItemDialog.get(), &NewItemDialog::addItems, m_tableModel.get(),
&TableModel::appendItems);
/// edit item dialog
m_editItemDialog = make_unique<EditItemDialog>(ui->tableView, this);
m_editItemDialog->createContent();
}

102
mainwindow.h Normal file
View File

@ -0,0 +1,102 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QItemSelection>
#include <QMainWindow>
QT_BEGIN_NAMESPACE
class QUndoStack;
class QUndoView;
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class GenericCore;
class TableModel;
class NewItemDialog;
class EditItemDialog;
using namespace std;
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget* parent = nullptr);
~MainWindow();
signals:
void displayStatusMessage(QString message);
void checkForUpdates();
protected:
void closeEvent(QCloseEvent* event) override;
private slots:
void showStatusMessage(const QString text);
void onCurrentChanged(const QModelIndex& current, const QModelIndex& previous);
void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
void onAboutClicked();
void on_actionCheck_for_update_triggered();
void on_pushButton_clicked();
/// slots for menu actions
void openNewItemDialog();
void openEditItemDialog();
void deleteCurrentItem();
void deleteSelectedtItems();
void onCleanStateChanged(bool clean);
void onShowUndoViewToggled(bool checked);
/// 'File' slots
void saveItems();
private:
Ui::MainWindow* ui;
// GenericCore* m_core;
unique_ptr<GenericCore> m_core;
shared_ptr<TableModel> m_tableModel;
QUndoStack* m_modelUndoStack;
unique_ptr<QUndoView> m_modelUndoView;
/// File actions
unique_ptr<QAction> m_newFileAct;
unique_ptr<QAction> m_openAct;
unique_ptr<QAction> m_saveAct;
unique_ptr<QAction> m_importAct;
unique_ptr<QAction> m_exportAct;
unique_ptr<QAction> m_printAct;
unique_ptr<QAction> m_exitAct;
/// Edit actions
unique_ptr<QAction> m_undoAct;
unique_ptr<QAction> m_redoAct;
unique_ptr<QAction> m_cutAct;
unique_ptr<QAction> m_copyAct;
unique_ptr<QAction> m_pasteAct;
unique_ptr<QAction> m_openNewItemDialogAct;
unique_ptr<QAction> m_openEditItemDialogAct;
unique_ptr<QAction> m_deleteItemAct;
unique_ptr<QAction> m_findItemAct;
/// View actions
unique_ptr<QAction> m_showModelUndoViewAct;
/// Dialogs
unique_ptr<NewItemDialog> m_newItemDialog;
unique_ptr<EditItemDialog> m_editItemDialog;
/// Setup functions
void createActions();
void createFileActions();
void createUndoActions();
void createEditActions();
void createHelpMenu();
void createGuiDialogs();
};
#endif // MAINWINDOW_H

84
mainwindow.ui Normal file
View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>GenericQtClient</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableView"/>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Push the button!</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Button</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>25</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&amp;File</string>
</property>
</widget>
<widget class="QMenu" name="menu_Edit">
<property name="title">
<string>&amp;Edit</string>
</property>
</widget>
<widget class="QMenu" name="menu_View">
<property name="title">
<string>&amp;View</string>
</property>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
<string>&amp;Help</string>
</property>
<addaction name="actionCheck_for_update"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menu_Edit"/>
<addaction name="menu_View"/>
<addaction name="menu_Help"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionCheck_for_update">
<property name="text">
<string>Check for &amp;update</string>
</property>
</action>
</widget>
<resources/>
<connections/>
<slots>
<slot>onPushButtonClicked()</slot>
</slots>
</ui>

164
utils/messagehandler.h Normal file
View File

@ -0,0 +1,164 @@
#ifndef MESSAGEHANDLER_H
#define MESSAGEHANDLER_H
/**
* Color and formatting codes
* @see: http://misc.flogisoft.com/bash/tip_colors_and_formatting
*/
#include <QObject>
// qSetMessagePattern("%{file}(%{line}): %{message}");
// qSetMessagePattern("%{type}(%{line}):\t%{message}");
// qSetMessagePattern("%{type}%{file}(%{line}):\t%{message}");
void consoleHandlerColoredVerbose(QtMsgType type,
const QMessageLogContext& context,
const QString& msg) {
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
// fprintf(stderr, "\033[1;30mDebug: (%s:%u, %s) \t%s\n\033[0m", context.file,
// context.line, context.function, localMsg.constData()); // bold
fprintf(stderr, "\033[107;30mDebug: (%s:%u, %s) \t%s\n\033[0m", context.file, context.line,
context.function, localMsg.constData());
break;
case QtInfoMsg:
fprintf(stderr, "\033[107;32mInfo: (%s:%u) \t%s\n\033[0m", context.file, context.line,
localMsg.constData());
break;
case QtWarningMsg:
fprintf(stderr, "\033[43;30mWarning: (%s:%u, %s) \t%s\n\033[0m", context.file, context.line,
context.function, localMsg.constData());
break;
case QtCriticalMsg:
fprintf(stderr, "\033[41;30mCritical: (%s:%u, %s) \t%s\n\033[0m", context.file, context.line,
context.function, localMsg.constData());
break;
case QtFatalMsg:
fprintf(stderr, "\033[41;30mFatal: (%s:%u, %s) \t%s\n\033[0m", context.file, context.line,
context.function, localMsg.constData());
abort();
}
}
void consoleHandlerColoredVerboseInDarkTheme(QtMsgType type,
const QMessageLogContext& context,
const QString& msg) {
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
// fprintf(stderr, "\033[1;30mDebug: (%s:%u, %s) \t%s\n\033[0m", context.file,
// context.line, context.function, localMsg.constData()); // bold
fprintf(stderr, "\033[107;37mDebug: (%s:%u, %s) \t%s\n\033[0m", context.file, context.line,
context.function, localMsg.constData());
break;
case QtInfoMsg:
fprintf(stderr, "\033[107;32mInfo: (%s:%u) \t%s\n\033[0m", context.file, context.line,
localMsg.constData());
break;
case QtWarningMsg:
fprintf(stderr, "\033[43;30mWarning: (%s:%u, %s) \t%s\n\033[0m", context.file, context.line,
context.function, localMsg.constData());
break;
case QtCriticalMsg:
fprintf(stderr, "\033[41;30mCritical: (%s:%u, %s) \t%s\n\033[0m", context.file, context.line,
context.function, localMsg.constData());
break;
case QtFatalMsg:
fprintf(stderr, "\033[41;30mFatal: (%s:%u, %s) \t%s\n\033[0m", context.file, context.line,
context.function, localMsg.constData());
abort();
}
}
void consoleHandlerColored(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "\033[1;30mDebug: (%s:%u) \t%s\n\033[0m", context.file, context.line,
localMsg.constData());
break;
case QtInfoMsg:
fprintf(stderr, "\033[0;30mInfo: (%s:%u) \t%s\n\033[0m", context.file, context.line,
localMsg.constData());
break;
case QtWarningMsg:
fprintf(stderr, "\033[1;33mWarning: (%s:%u) \t%s\n\033[0m", context.file, context.line,
localMsg.constData());
break;
case QtCriticalMsg:
fprintf(stderr, "\033[31mCritical: (%s:%u) \t%s\n\033[0m", context.file, context.line,
localMsg.constData());
break;
case QtFatalMsg:
fprintf(stderr, "\033[31mFatal: (%s:%u) \t%s\n\033[0m", context.file, context.line,
localMsg.constData());
abort();
}
}
void myMessageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line,
context.function);
break;
case QtInfoMsg:
fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line,
context.function);
break;
case QtWarningMsg:
fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line,
context.function);
break;
case QtCriticalMsg:
fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file,
context.line, context.function);
break;
case QtFatalMsg:
fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line,
context.function);
abort();
}
}
#ifdef Q_OS_ANDROID
#include <android/log.h>
const char* const applicationName = "Pensieve";
void androidMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
QString report = msg;
if (context.file && !QString(context.file).isEmpty()) {
report += " in file ";
report += QString(context.file);
report += " line ";
report += QString::number(context.line);
}
if (context.function && !QString(context.function).isEmpty()) {
report += +" function ";
report += QString(context.function);
}
const char* const local = report.toLocal8Bit().constData();
switch (type) {
case QtDebugMsg:
__android_log_write(ANDROID_LOG_DEBUG, applicationName, local);
break;
case QtInfoMsg:
__android_log_write(ANDROID_LOG_INFO, applicationName, local);
break;
case QtWarningMsg:
__android_log_write(ANDROID_LOG_WARN, applicationName, local);
break;
case QtCriticalMsg:
__android_log_write(ANDROID_LOG_ERROR, applicationName, local);
break;
case QtFatalMsg:
default:
__android_log_write(ANDROID_LOG_FATAL, applicationName, local);
abort();
}
}
#endif
#endif // MESSAGEHANDLER_H

149
views/itemdetailmapper.cpp Normal file
View File

@ -0,0 +1,149 @@
#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);
/// BUG: If multiple columns are changed not all changes are applied.
/// Multiple changes are set individually by calling setData().
/// Probably due to a conflicting dataChanged signal for too many roles&columns the data of
/// the remaining columns is reset before setData is called.
/// And a manual submit would also create multiple undo steps anyway.
/// Workaround: ManualSubmit -> AutoSubmit
// m_mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
m_mapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);
/// 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