From 2c3d49db3005cade44543a59ff27d44ae19892f6 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Tue, 23 Dec 2025 10:11:38 +0100 Subject: [PATCH 1/3] Setting the application name with "-dev" suffix in debug builds and setting the organization name (for storing configs and other files in proper directory). --- genericcore.cpp | 9 +++++++++ model/tablemodel.h | 1 + 2 files changed, 10 insertions(+) diff --git a/genericcore.cpp b/genericcore.cpp index 4cf0500..e1a68a7 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -19,6 +19,15 @@ using namespace std; GenericCore::GenericCore() { qDebug() << "Creating core..."; + QCoreApplication::setOrganizationName("Working-Copy Collective"); + QCoreApplication::setOrganizationDomain("working-copy.org"); + +#ifdef QT_DEBUG + QCoreApplication::setApplicationName(QString(APPLICATION_NAME) + "-dev"); +#else + QCoreApplication::setApplicationName(QString(APPLICATION_NAME)); +#endif + // TODO let the model own its undo stack (& use TableModel::getUndoStack() if necessary) m_modelUndoStack = new QUndoStack(this); diff --git a/model/tablemodel.h b/model/tablemodel.h index 069179e..dd54f3a 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -43,6 +43,7 @@ class TableModel : public QAbstractTableModel { private: /// *** members *** + // TODO shared_ptr -> unique_ptr QList> m_items; QUndoStack* m_undoStack; From 0e1a0d4959825c58efcb556e3ea2fd15b89a51fe Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Tue, 23 Dec 2025 10:14:13 +0100 Subject: [PATCH 2/3] Items can be saved to JSON file "items.json" (in standard location). --- CMakeLists.txt | 1 + data/filehandler.cpp | 31 +++++++++++++++++++++++++++++++ data/filehandler.h | 15 +++++++++++++++ genericcore.cpp | 17 +++++++++++++++++ genericcore.h | 2 ++ model/modelitem.cpp | 29 +++++++++++++++++++++++++++++ model/modelitem.h | 3 +++ model/tablemodel.cpp | 19 +++++++++++++++++++ model/tablemodel.h | 2 ++ 9 files changed, 119 insertions(+) create mode 100644 data/filehandler.cpp create mode 100644 data/filehandler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d5492fe..ec6dc0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(${TARGET_APP} STATIC model/commands/insertrowscommand.h model/commands/insertrowscommand.cpp model/commands/removerowscommand.h model/commands/removerowscommand.cpp model/commands/edititemcommand.h model/commands/edititemcommand.cpp + data/filehandler.h data/filehandler.cpp ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/data/filehandler.cpp b/data/filehandler.cpp new file mode 100644 index 0000000..336b1a9 --- /dev/null +++ b/data/filehandler.cpp @@ -0,0 +1,31 @@ +#include "filehandler.h" + +#include +#include +#include +#include + +bool FileHandler::saveToFile(const QJsonDocument& doc, const QString& fileName) { + qDebug() << "saving file..."; + QString path = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).at(0); + qDebug() << path; + + QDir dir; + if (!dir.exists(path)) { + dir.mkpath(path); + } + // qDebug() << path + fileName; + const QString filePath = path + '/' + fileName; + QFile file(filePath); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning() << "can't open file"; + return false; + } + QTextStream out(&file); + out.setEncoding(QStringConverter::Utf8); + out << doc.toJson(QJsonDocument::Indented); + return true; +} + +FileHandler::FileHandler() {} diff --git a/data/filehandler.h b/data/filehandler.h new file mode 100644 index 0000000..e6b954d --- /dev/null +++ b/data/filehandler.h @@ -0,0 +1,15 @@ +#ifndef FILEHANDLER_H +#define FILEHANDLER_H + +class QJsonDocument; +class QString; + +class FileHandler { + public: + static bool saveToFile(const QJsonDocument& doc, const QString& fileName); + + private: + explicit FileHandler(); +}; + +#endif // FILEHANDLER_H diff --git a/genericcore.cpp b/genericcore.cpp index e1a68a7..2f79c04 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,6 +11,7 @@ #include "../../ApplicationConfig.h" #include "CoreConfig.h" #include "constants.h" +#include "data/filehandler.h" #include "model/tablemodel.h" #include @@ -88,6 +90,21 @@ QUndoStack* GenericCore::getModelUndoStack() const { return m_modelUndoStack; } std::shared_ptr GenericCore::getModel() const { return m_mainModel; } +void GenericCore::saveItems() { + qDebug() << "saving items..."; + + const QJsonDocument doc = m_mainModel->getAllItemsAsJsonDoc(); + const bool successfulSave = FileHandler::saveToFile(doc, "items.json"); + if (successfulSave) { + // QStringList completedTaskStrings = m_model->completedTasks(); + // appendCompletedTasksToFile(completedTaskStrings, "completed.txt"); + m_modelUndoStack->setClean(); + emit displayStatusMessage(QString("Items saved.")); + } else { + emit displayStatusMessage(QString("Error: Items couldn't be saved.")); + } +} + void GenericCore::setupModels() { m_mainModel = make_shared(m_modelUndoStack, this); // TODO add QAbstractItemModelTester diff --git a/genericcore.h b/genericcore.h index 20ac805..4ea4dd4 100644 --- a/genericcore.h +++ b/genericcore.h @@ -25,6 +25,8 @@ class GenericCore : public QObject { QUndoStack* getModelUndoStack() const; std::shared_ptr getModel() const; + void saveItems(); + signals: void displayStatusMessage(QString message); diff --git a/model/modelitem.cpp b/model/modelitem.cpp index a168b7f..5df55b6 100644 --- a/model/modelitem.cpp +++ b/model/modelitem.cpp @@ -1,5 +1,10 @@ #include "modelitem.h" +#include "tablemodel.h" + +#include +#include + ModelItem::ModelItem(const QHash values) : m_values(values) {} @@ -38,3 +43,27 @@ bool ModelItem::setItemData(const QMap& changedValues) { return valueChanged; } + +QJsonObject ModelItem::toJsonObject() const { + QJsonObject itemObject; + // itemObject.insert("uuid", m_uuid.toString()); + // itemObject.insert("entryDateUTC", m_entryDateUTC.toString(Qt::ISODate)); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::NameRole), + data(TableModel::NameRole).toString()); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::DescriptionRole), + data(TableModel::DescriptionRole).toString()); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::InfoRole), + data(TableModel::InfoRole).toString()); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::AmountRole), + data(TableModel::AmountRole).toInt()); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::FactorRole), + data(TableModel::FactorRole).toReal()); + + // if (m_modifiedDateUTC.isValid()) { + // itemObject.insert("modifiedDateUTC", m_modifiedDateUTC.toString(Qt::ISODate)); + // } + // if (m_endDateUTC.isValid()) { + // itemObject.insert("endDateUTC", m_endDateUTC.toString(Qt::ISODate)); + // } + return itemObject; +} diff --git a/model/modelitem.h b/model/modelitem.h index 6f9c366..2f3e037 100644 --- a/model/modelitem.h +++ b/model/modelitem.h @@ -12,6 +12,9 @@ class ModelItem { // TODO change return value to list of changed roles bool setItemData(const QMap& changedValues); + // QString toString() const; + QJsonObject toJsonObject() const; + private: QHash m_values; }; diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 675309a..94a5ddd 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -6,6 +6,10 @@ #include "commands/removerowscommand.h" #include "modelitem.h" +#include +#include +#include + QHash TableModel::ROLE_NAMES = {{NameRole, "Name"}, {DescriptionRole, "Description"}, {InfoRole, "Info"}, @@ -241,3 +245,18 @@ bool TableModel::isEmptyValueEqualToZero(const int role) const { } return false; } + +QJsonDocument TableModel::getAllItemsAsJsonDoc() const { + QJsonDocument doc = QJsonDocument(); + QJsonObject rootObject; + QJsonArray array; + + foreach (shared_ptr item, m_items) { + QJsonObject itemObject = item->toJsonObject(); + array.append(itemObject); + } + rootObject.insert("items", array); + + doc.setObject(rootObject); + return doc; +} diff --git a/model/tablemodel.h b/model/tablemodel.h index dd54f3a..539378d 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -34,6 +34,8 @@ class TableModel : public QAbstractTableModel { bool setData(const QModelIndex& index, const QVariant& value, int role) override; bool setItemData(const QModelIndex& index, const QMap& roles) override; + QJsonDocument getAllItemsAsJsonDoc() const; + public slots: // bool insertRows(int position, int rows, const QModelIndex& parentIndex = QModelIndex()) // override; From 1fc1b1715dfe312cb93320598388c050e69730f9 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Tue, 23 Dec 2025 13:40:33 +0100 Subject: [PATCH 3/3] Items are loaded from default JSON file in standard location at startup. If no items are found in file, example items are generated. --- data/filehandler.cpp | 22 +++++++++++++ data/filehandler.h | 2 ++ formats/jsonparser.cpp | 2 +- genericcore.cpp | 27 ++++++++++++++++ genericcore.h | 1 + model/tablemodel.cpp | 72 +++++++++++++++++++++++++----------------- model/tablemodel.h | 5 ++- 7 files changed, 100 insertions(+), 31 deletions(-) diff --git a/data/filehandler.cpp b/data/filehandler.cpp index 336b1a9..5cd9bba 100644 --- a/data/filehandler.cpp +++ b/data/filehandler.cpp @@ -28,4 +28,26 @@ bool FileHandler::saveToFile(const QJsonDocument& doc, const QString& fileName) return true; } +QByteArray FileHandler::loadJSONDataFromFile(const QString fileName) { + QByteArray jsonData; + QFile file; + QString path = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).at(0); + file.setFileName(path + "/" + fileName); + if (file.exists()) { + qDebug() << "File found, reading content..."; + const bool successfulOpened = file.open(QIODevice::ReadOnly | QIODevice::Text); + if (successfulOpened) { + // TODO learn and decide on the differences between "readAll" and using + // streams + jsonData = file.readAll(); + file.close(); + } else { + qWarning() << "File could not be opened!"; + } + } else { + qInfo() << "File not found. Returning empty result..."; + } + return jsonData; +} + FileHandler::FileHandler() {} diff --git a/data/filehandler.h b/data/filehandler.h index e6b954d..aecf072 100644 --- a/data/filehandler.h +++ b/data/filehandler.h @@ -3,10 +3,12 @@ class QJsonDocument; class QString; +class QByteArray; class FileHandler { public: static bool saveToFile(const QJsonDocument& doc, const QString& fileName); + static QByteArray loadJSONDataFromFile(const QString fileName); private: explicit FileHandler(); diff --git a/formats/jsonparser.cpp b/formats/jsonparser.cpp index 5ef95cb..10ba77a 100644 --- a/formats/jsonparser.cpp +++ b/formats/jsonparser.cpp @@ -51,7 +51,7 @@ QJsonArray JsonParser::extractItemArray(const QByteArray& jsonData, const QStrin } else { QJsonObject rootObject = doc.object(); - itemArray = rootObject.value(QString("items")).toArray(); + itemArray = rootObject.value(objectName).toArray(); } return itemArray; diff --git a/genericcore.cpp b/genericcore.cpp index 2f79c04..3893b9c 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -90,6 +90,10 @@ QUndoStack* GenericCore::getModelUndoStack() const { return m_modelUndoStack; } std::shared_ptr GenericCore::getModel() const { return m_mainModel; } +/** + * Save items to default file (in standard location). + * @brief GenericCore::saveItems Saves item fo file. + */ void GenericCore::saveItems() { qDebug() << "saving items..."; @@ -108,6 +112,29 @@ void GenericCore::saveItems() { void GenericCore::setupModels() { m_mainModel = make_shared(m_modelUndoStack, this); // TODO add QAbstractItemModelTester + initModelData(); +} + +/** + * Initializing model with data. Tries to read items from default file. Generating example items as + * fallback. + * @brief GenericCore::initModelData + */ +void GenericCore::initModelData() { + qInfo() << "Trying to read model data from file..."; + const QByteArray jsonDoc = FileHandler::loadJSONDataFromFile("items.json"); + // qDebug() << "jsonDoc:" << jsonDoc; + // TODO decide on lack of file(s) (config, data) if example items should be generated + // (see welcome wizard) + if (jsonDoc.isEmpty()) { + qDebug() << "No item content in file. Generating example items..."; + const QByteArray exampleItems = m_mainModel->generateExampleItems(); + m_mainModel->insertItems(0, exampleItems); + } else { + qDebug() << "Item in file found."; + m_mainModel->insertItems(0, jsonDoc); + } + m_modelUndoStack->clear(); } QString GenericCore::getMaintenanceToolFilePath() const { diff --git a/genericcore.h b/genericcore.h index 4ea4dd4..671ded1 100644 --- a/genericcore.h +++ b/genericcore.h @@ -35,6 +35,7 @@ class GenericCore : public QObject { std::shared_ptr m_mainModel; void setupModels(); + void initModelData(); QString getMaintenanceToolFilePath() const; }; diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 94a5ddd..ce706fa 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -17,21 +17,35 @@ QHash TableModel::ROLE_NAMES = {{NameRole, "Name"}, {FactorRole, "Factor"}}; QList TableModel::intColumns = {"Amount", "Factor"}; +QByteArray TableModel::generateExampleItems() { + QJsonDocument doc = QJsonDocument(); + QJsonObject rootObject; + QJsonArray array; + + for (int row = 0; row < 5; ++row) { + QJsonObject itemObject; + // itemObject.insert("uuid", m_uuid.toString()); + // itemObject.insert("entryDateUTC", m_entryDateUTC.toString(Qt::ISODate)); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::NameRole), + QString("Item %1").arg(row)); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::DescriptionRole), + QString("This is item %1").arg(row)); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::InfoRole), + QString("Info of item %1").arg(row)); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::AmountRole), row); + itemObject.insert(TableModel::ROLE_NAMES.value(TableModel::FactorRole), row * 1.1); + + array.append(itemObject); + } + rootObject.insert("items", array); + + doc.setObject(rootObject); + return doc.toJson(); +} + 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); - values[DescriptionRole] = QString("This is item %1").arg(row); - values[InfoRole] = QString("Info of item %1").arg(row); - values[AmountRole] = row; - values[FactorRole] = row * 1.1; - - shared_ptr item = make_unique(values); - m_items.append(std::move(item)); - } -} + , m_undoStack(undoStack) {} Qt::ItemFlags TableModel::flags(const QModelIndex& index) const { return Qt::ItemIsEditable | QAbstractTableModel::flags(index); @@ -115,6 +129,21 @@ bool TableModel::setItemData(const QModelIndex& index, const QMap return false; } +QJsonDocument TableModel::getAllItemsAsJsonDoc() const { + QJsonDocument doc = QJsonDocument(); + QJsonObject rootObject; + QJsonArray array; + + foreach (shared_ptr item, m_items) { + QJsonObject itemObject = item->toJsonObject(); + array.append(itemObject); + } + rootObject.insert("items", array); + + doc.setObject(rootObject); + return doc; +} + bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIndex) { if (parentIndex != QModelIndex()) { qWarning() << "Removing of child rows is not supported yet!"; @@ -147,7 +176,7 @@ void TableModel::insertItems(int startPosition, startPosition = m_items.size(); } - QList> valueList = JsonParser::toItemValuesList(jsonDoc); + QList> valueList = JsonParser::toItemValuesList(jsonDoc, "items"); InsertRowsCommand* insertCommand = new InsertRowsCommand(this, startPosition, valueList); m_undoStack->push(insertCommand); @@ -245,18 +274,3 @@ bool TableModel::isEmptyValueEqualToZero(const int role) const { } return false; } - -QJsonDocument TableModel::getAllItemsAsJsonDoc() const { - QJsonDocument doc = QJsonDocument(); - QJsonObject rootObject; - QJsonArray array; - - foreach (shared_ptr item, m_items) { - QJsonObject itemObject = item->toJsonObject(); - array.append(itemObject); - } - rootObject.insert("items", array); - - doc.setObject(rootObject); - return doc; -} diff --git a/model/tablemodel.h b/model/tablemodel.h index 539378d..2bb758a 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -19,6 +19,7 @@ class TableModel : public QAbstractTableModel { enum UserRoles { NameRole = Qt::UserRole + 1, DescriptionRole, InfoRole, AmountRole, FactorRole }; static QHash ROLE_NAMES; static QList intColumns; + static QByteArray generateExampleItems(); explicit TableModel(QUndoStack* undoStack, QObject* parent = nullptr); @@ -41,7 +42,9 @@ class TableModel : public QAbstractTableModel { // override; bool removeRows(int firstRow, int nRows, const QModelIndex& parentIndex = QModelIndex()) override; void appendItems(const QByteArray& jsonDoc); - void insertItems(int startPosition, const QByteArray& jsonDoc, const QModelIndex& parentIndex); + void insertItems(int startPosition, + const QByteArray& jsonDoc, + const QModelIndex& parentIndex = QModelIndex()); private: /// *** members ***