From 2702b9c83522f1616fa64508a6842157a4ec94c4 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Sun, 4 Jan 2026 13:38:54 +0100 Subject: [PATCH 1/4] Simple implementation of CSV file import. Successfully imported items are appended to the model. --- CMakeLists.txt | 3 + data/filehandler.cpp | 46 +++++++++++--- data/filehandler.h | 8 +++ formats/csvparser.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++ formats/csvparser.h | 29 +++++++++ genericcore.cpp | 16 ++++- genericcore.h | 1 + model/metadata.h | 8 +++ model/tablemodel.cpp | 16 +++-- model/tablemodel.h | 3 + 10 files changed, 258 insertions(+), 13 deletions(-) create mode 100644 formats/csvparser.cpp create mode 100644 formats/csvparser.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f599c2..b47cfca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,9 @@ add_library(${TARGET_APP} STATIC model/commands/edititemcommand.h model/commands/edititemcommand.cpp data/filehandler.h data/filehandler.cpp model/metadata.h + formats/csvparser.h formats/csvparser.cpp + # 3rd party libraries + ../3rdParty/rapidcsv/src/rapidcsv.h ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/data/filehandler.cpp b/data/filehandler.cpp index 5cd9bba..f7c3e13 100644 --- a/data/filehandler.cpp +++ b/data/filehandler.cpp @@ -5,6 +5,8 @@ #include #include +#include "../formats/csvparser.h" + bool FileHandler::saveToFile(const QJsonDocument& doc, const QString& fileName) { qDebug() << "saving file..."; QString path = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).at(0); @@ -29,25 +31,55 @@ bool FileHandler::saveToFile(const QJsonDocument& doc, const QString& fileName) } QByteArray FileHandler::loadJSONDataFromFile(const QString fileName) { - QByteArray jsonData; QFile file; QString path = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).at(0); file.setFileName(path + "/" + fileName); + + QPair fileContent = getFileContent(path + "/" + fileName); + + return fileContent.second; +} + +QList> FileHandler::getItemValuesFromCSVFile(const QString& filePath) { + QList> result; + QFile file; + file.setFileName(filePath); + if (file.exists()) { + result = CsvParser::getItemsFromCSVFile(filePath); + } + return result; +} + +FileHandler::FileHandler() {} + +/** Tries to open the file specified by the file path & returns the content + * @brief FileHandler::getFileContent + * @param filePath + * @return Returns an error string (empty if successful) and the file content + */ +QPair FileHandler::getFileContent(const QString& filePath) { + QString errorString = ""; + + QByteArray fileContent; + QFile file; + file.setFileName(filePath); 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(); + fileContent = file.readAll(); file.close(); } else { - qWarning() << "File could not be opened!"; + errorString = "File could not be opened!"; + qWarning() << errorString; } } else { - qInfo() << "File not found. Returning empty result..."; + errorString = "File not found. Returning empty result..."; + qInfo() << errorString; } - return jsonData; -} -FileHandler::FileHandler() {} + const QPair result(errorString, fileContent); + return result; +} diff --git a/data/filehandler.h b/data/filehandler.h index aecf072..9165def 100644 --- a/data/filehandler.h +++ b/data/filehandler.h @@ -1,17 +1,25 @@ #ifndef FILEHANDLER_H #define FILEHANDLER_H +#include + class QJsonDocument; class QString; class QByteArray; class FileHandler { public: + /// JSON static bool saveToFile(const QJsonDocument& doc, const QString& fileName); static QByteArray loadJSONDataFromFile(const QString fileName); + /// CSV + static QList> getItemValuesFromCSVFile(const QString& filePath); + private: explicit FileHandler(); + + static QPair getFileContent(const QString& filePath); }; #endif // FILEHANDLER_H diff --git a/formats/csvparser.cpp b/formats/csvparser.cpp new file mode 100644 index 0000000..814d151 --- /dev/null +++ b/formats/csvparser.cpp @@ -0,0 +1,141 @@ +#include "csvparser.h" + +#include +#include +#include + +#include "../../3rdParty/rapidcsv/src/rapidcsv.h" +#include "../model/metadata.h" + +using namespace rapidcsv; + +QList> CsvParser::getItemsFromCSVFile(const QString& fileName) { + Document doc(fileName.toStdString()); + + const bool isCompatible = isCsvCompatible(doc); + if (isCompatible) { + const QList> result = createListItemsFromCsvEntries(doc); + return result; + } else { + return QList>(); + } +} + +CsvParser::CsvParser() {} + +/** A CSV file is compatible if the following is true: + * - there is a CSV column for every column in the table model + * (except there are optional columns defined in the model) + * @brief CsvParser::isCsvCompatible + * @param doc + * @return + */ +bool CsvParser::isCsvCompatible(const rapidcsv::Document& doc) { + qInfo() << "Checking CSV document for compatiblity..."; + const std::vector columnNames = doc.GetColumnNames(); + for (const QString& headerName : GET_HEADER_NAMES()) { + bool isHeaderNameFound = false; + if (std::find(columnNames.begin(), columnNames.end(), headerName) != columnNames.end()) { + qDebug() << QString("Header found in column names: %1").arg(headerName); + } else { + const QString errorString = + QString("Couldn't find header name '%1' in CSV file. Aborting...").arg(headerName); + qWarning() << errorString; + return false; + } + } + return true; +} + +QList> CsvParser::createListItemsFromCsvEntries( + const rapidcsv::Document& doc) { + QList> result; + const int rowCount = doc.GetRowCount(); + const QList headerNames = GET_HEADER_NAMES(); + + /// get the values for all columns + QHash> columnValueMap = extractColumnValues(headerNames, doc); + + /// get item values for each row + for (int row = 0; row < rowCount; ++row) { + const QHash itemValues = getItemValuesForRow(headerNames, columnValueMap, row); + result.append(itemValues); + } + return result; +} + +QHash> CsvParser::extractColumnValues( + const QList headerNames, + const rapidcsv::Document& doc) { + QHash> columnValueMap; + for (const QString& columnName : headerNames) { + // NEXT add support for optional columns + // if (optionalCsvHeaderNames.contains(columnName)) { + // const std::vector columnNames = doc.GetColumnNames(); + // int columnIdx = doc.GetColumnIdx(columnName.toStdString()); + // if (columnIdx == -1) { + // continue; + // } + // } + const std::vector columnValues = + doc.GetColumn(columnName.toStdString()); + columnValueMap.insert(columnName, columnValues); + } + + return columnValueMap; +} + +QHash CsvParser::getItemValuesForRow( + const QList& headerNames, + const QHash>& columnValueMap, + const int row) { + QHash result; + for (const QString& columnName : headerNames) { + if (!columnValueMap.contains(columnName)) { + continue; + } + int role = ROLE_NAMES.key(columnName.toLatin1()); + std::string valueString = columnValueMap.value(columnName).at(row); + + QVariant value = parseItemValue(role, valueString); + if (value.isValid()) { + result[role] = value; + } + } + return result; +} + +QVariant CsvParser::parseItemValue(const int role, const std::string& valueString) { + QVariant result; + if (STRING_ROLES.contains(role)) { + /// string values + result = QString::fromStdString(valueString); + } else if (INT_ROLES.contains(role)) { + /// int values + + /// GetColumn crashed (probably because of the empty values) + /// so the strings will be processed later + const QString intAsString = QString::fromStdString(valueString); + result = intAsString.toInt(); + } else if (DOUBLE_ROLES.contains(role)) { + /// double values + const QString doubleAsString = QString::fromStdString(valueString); + double doubleValue; + if (doubleAsString.contains(',')) { + QLocale german(QLocale::German); + doubleValue = german.toDouble(doubleAsString); + } else { + doubleValue = doubleAsString.toDouble(); + } + result = doubleValue; + + // } else if (typeColumns.contains(columnName)) { + // // NEXT validate string is allowed + // values[role] = QString::fromStdString(columnValueMap.value(columnName).at(row)); + } else { + /// no type recognized for column + QString errorString = QString("Couldn't find value type for role '%1'").arg(role); + qCritical() << errorString; + } + return result; +} diff --git a/formats/csvparser.h b/formats/csvparser.h new file mode 100644 index 0000000..e4bb06f --- /dev/null +++ b/formats/csvparser.h @@ -0,0 +1,29 @@ +#ifndef CSVPARSER_H +#define CSVPARSER_H + +#include + +namespace rapidcsv { +class Document; +} + +class CsvParser { + public: + static QList> getItemsFromCSVFile(const QString& fileName); + + private: + explicit CsvParser(); + + static bool isCsvCompatible(const rapidcsv::Document& doc); + static QList> createListItemsFromCsvEntries(const rapidcsv::Document& doc); + static QHash> extractColumnValues( + const QList headerNames, + const rapidcsv::Document& doc); + static QHash getItemValuesForRow( + const QList& headerNames, + const QHash>& columnValueMap, + const int row); + static QVariant parseItemValue(const int role, const std::string& valueString); +}; + +#endif // CSVPARSER_H diff --git a/genericcore.cpp b/genericcore.cpp index 86459f5..9b86bfb 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -101,8 +101,6 @@ void GenericCore::saveItems() { const QJsonDocument doc = m_mainModel->getAllItemsAsJsonDoc(); const bool successfulSave = FileHandler::saveToFile(doc, ITEM_FILE_NAME); if (successfulSave) { - // QStringList completedTaskStrings = m_model->completedTasks(); - // appendCompletedTasksToFile(completedTaskStrings, "completed.txt"); m_modelUndoStack->setClean(); emit displayStatusMessage(QString("Items saved.")); } else { @@ -110,6 +108,20 @@ void GenericCore::saveItems() { } } +void GenericCore::importCSVFile(const QString& filePath) { + qInfo() << "importing items from CSV..."; + qDebug() << "filePath:" << filePath; + const QList> itemValuesList = + FileHandler::getItemValuesFromCSVFile(filePath); + // NEXT inform UI on errors + if (itemValuesList.isEmpty()) { + qDebug() << "No items found. Doing nothing..."; + return; + } + // qDebug() << "CSV file content:" << itemValuesList; + m_mainModel->insertItems(m_mainModel->rowCount(), itemValuesList); +} + void GenericCore::setupModels() { m_mainModel = make_shared(m_modelUndoStack, this); // TODO add QAbstractItemModelTester diff --git a/genericcore.h b/genericcore.h index 671ded1..b4a9776 100644 --- a/genericcore.h +++ b/genericcore.h @@ -26,6 +26,7 @@ class GenericCore : public QObject { std::shared_ptr getModel() const; void saveItems(); + void importCSVFile(const QString& filePath); signals: void displayStatusMessage(QString message); diff --git a/model/metadata.h b/model/metadata.h index 580d931..97f7d4d 100644 --- a/model/metadata.h +++ b/model/metadata.h @@ -50,5 +50,13 @@ static int GET_ROLE_FOR_COLUMN(const int column) { break; } } +static QList GET_HEADER_NAMES() { + QList result; + for (const UserRoles& role : USER_FACING_ROLES) { + const QString headerName = ROLE_NAMES.value(role); + result.append(headerName); + } + return result; +} #endif // METADATA_H diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 2ae6cdf..dd0dae0 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -169,18 +169,26 @@ void TableModel::appendItems(const QByteArray& jsonDoc) { insertItems(-1, jsonDo void TableModel::insertItems(int startPosition, const QByteArray& jsonDoc, const QModelIndex& parentIndex) { + const QList> valueList = + JsonParser::toItemValuesList(jsonDoc, ITEM_KEY_STRING); + + insertItems(startPosition, valueList, parentIndex); +} + +void TableModel::insertItems(int startPosition, + const QList>& itemValuesList, + const QModelIndex& parentIndex) { qInfo() << "Inserting item(s) into model..."; if (parentIndex != QModelIndex()) { - qWarning() << "Using invalid parent index (no child support for now)"; + qWarning() + << "Using invalid parent index (no child support for now)! Using root index as parent..."; } if (startPosition == -1 || startPosition > m_items.size()) { /// Appending item(s) startPosition = m_items.size(); } - QList> valueList = JsonParser::toItemValuesList(jsonDoc, ITEM_KEY_STRING); - - InsertRowsCommand* insertCommand = new InsertRowsCommand(this, startPosition, valueList); + InsertRowsCommand* insertCommand = new InsertRowsCommand(this, startPosition, itemValuesList); m_undoStack->push(insertCommand); } diff --git a/model/tablemodel.h b/model/tablemodel.h index c7650f2..75f7bbd 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -43,6 +43,9 @@ class TableModel : public QAbstractTableModel { void insertItems(int startPosition, const QByteArray& jsonDoc, const QModelIndex& parentIndex = QModelIndex()); + void insertItems(int startPosition, + const QList>& itemValuesList, + const QModelIndex& parentIndex = QModelIndex()); private: /// *** members *** From 3e6273cb7d0de76851f5d3efc026cc23b54f8039 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Sun, 4 Jan 2026 17:48:27 +0100 Subject: [PATCH 2/4] Using the typedef ModelItemValues for "QHash". --- data/filehandler.cpp | 4 ++-- data/filehandler.h | 4 +++- formats/csvparser.cpp | 17 ++++++++--------- formats/csvparser.h | 8 +++++--- formats/jsonparser.cpp | 16 ++++++++-------- formats/jsonparser.h | 10 ++++++---- genericcore.cpp | 3 +-- model/commands/insertrowscommand.cpp | 6 +++--- model/commands/insertrowscommand.h | 10 ++++++---- model/commands/removerowscommand.cpp | 2 +- model/commands/removerowscommand.h | 4 +++- model/modelitem.cpp | 2 +- model/modelitem.h | 4 +++- model/tablemodel.cpp | 11 +++++------ model/tablemodel.h | 8 +++++--- 15 files changed, 60 insertions(+), 49 deletions(-) diff --git a/data/filehandler.cpp b/data/filehandler.cpp index f7c3e13..dc99a9b 100644 --- a/data/filehandler.cpp +++ b/data/filehandler.cpp @@ -40,8 +40,8 @@ QByteArray FileHandler::loadJSONDataFromFile(const QString fileName) { return fileContent.second; } -QList> FileHandler::getItemValuesFromCSVFile(const QString& filePath) { - QList> result; +QList FileHandler::getItemValuesFromCSVFile(const QString& filePath) { + QList result; QFile file; file.setFileName(filePath); if (file.exists()) { diff --git a/data/filehandler.h b/data/filehandler.h index 9165def..32ba140 100644 --- a/data/filehandler.h +++ b/data/filehandler.h @@ -3,6 +3,8 @@ #include +typedef QHash ModelItemValues; + class QJsonDocument; class QString; class QByteArray; @@ -14,7 +16,7 @@ class FileHandler { static QByteArray loadJSONDataFromFile(const QString fileName); /// CSV - static QList> getItemValuesFromCSVFile(const QString& filePath); + static QList getItemValuesFromCSVFile(const QString& filePath); private: explicit FileHandler(); diff --git a/formats/csvparser.cpp b/formats/csvparser.cpp index 814d151..1b6c943 100644 --- a/formats/csvparser.cpp +++ b/formats/csvparser.cpp @@ -9,15 +9,15 @@ using namespace rapidcsv; -QList> CsvParser::getItemsFromCSVFile(const QString& fileName) { +QList CsvParser::getItemsFromCSVFile(const QString& fileName) { Document doc(fileName.toStdString()); const bool isCompatible = isCsvCompatible(doc); if (isCompatible) { - const QList> result = createListItemsFromCsvEntries(doc); + const QList result = createListItemsFromCsvEntries(doc); return result; } else { - return QList>(); + return QList(); } } @@ -47,9 +47,8 @@ bool CsvParser::isCsvCompatible(const rapidcsv::Document& doc) { return true; } -QList> CsvParser::createListItemsFromCsvEntries( - const rapidcsv::Document& doc) { - QList> result; +QList CsvParser::createListItemsFromCsvEntries(const rapidcsv::Document& doc) { + QList result; const int rowCount = doc.GetRowCount(); const QList headerNames = GET_HEADER_NAMES(); @@ -58,7 +57,7 @@ QList> CsvParser::createListItemsFromCsvEntries( /// get item values for each row for (int row = 0; row < rowCount; ++row) { - const QHash itemValues = getItemValuesForRow(headerNames, columnValueMap, row); + const ModelItemValues itemValues = getItemValuesForRow(headerNames, columnValueMap, row); result.append(itemValues); } return result; @@ -85,11 +84,11 @@ QHash> CsvParser::extractColumnValues( return columnValueMap; } -QHash CsvParser::getItemValuesForRow( +ModelItemValues CsvParser::getItemValuesForRow( const QList& headerNames, const QHash>& columnValueMap, const int row) { - QHash result; + ModelItemValues result; for (const QString& columnName : headerNames) { if (!columnValueMap.contains(columnName)) { continue; diff --git a/formats/csvparser.h b/formats/csvparser.h index e4bb06f..71986c9 100644 --- a/formats/csvparser.h +++ b/formats/csvparser.h @@ -3,23 +3,25 @@ #include +typedef QHash ModelItemValues; + namespace rapidcsv { class Document; } class CsvParser { public: - static QList> getItemsFromCSVFile(const QString& fileName); + static QList getItemsFromCSVFile(const QString& fileName); private: explicit CsvParser(); static bool isCsvCompatible(const rapidcsv::Document& doc); - static QList> createListItemsFromCsvEntries(const rapidcsv::Document& doc); + static QList createListItemsFromCsvEntries(const rapidcsv::Document& doc); static QHash> extractColumnValues( const QList headerNames, const rapidcsv::Document& doc); - static QHash getItemValuesForRow( + static ModelItemValues getItemValuesForRow( const QList& headerNames, const QHash>& columnValueMap, const int row); diff --git a/formats/jsonparser.cpp b/formats/jsonparser.cpp index 075f607..aa24722 100644 --- a/formats/jsonparser.cpp +++ b/formats/jsonparser.cpp @@ -5,9 +5,9 @@ #include "../model/metadata.h" -QList> JsonParser::toItemValuesList(const QByteArray& jsonData, - const QString& objectName) { - QList> result; +QList JsonParser::toItemValuesList(const QByteArray& jsonData, + const QString& objectName) { + QList result; if (jsonData.isEmpty()) { return result; @@ -16,14 +16,14 @@ QList> JsonParser::toItemValuesList(const QByteArray& jsonD QJsonArray itemArray = extractItemArray(jsonData, objectName); foreach (QJsonValue value, itemArray) { - QJsonObject itemJsonObject = value.toObject(); - QHash values = jsonObjectToItemValues(itemJsonObject); + QJsonObject itemJsonObject = value.toObject(); + ModelItemValues values = jsonObjectToItemValues(itemJsonObject); result.append(values); } return result; } -QByteArray JsonParser::itemValuesListToJson(const QList>& itemValuesList, +QByteArray JsonParser::itemValuesListToJson(const QList& itemValuesList, const QString& objectName) { QJsonDocument jsonDoc; QJsonObject rootObject; @@ -72,8 +72,8 @@ QJsonArray JsonParser::extractItemArray(const QByteArray& jsonData, const QStrin return itemArray; } -QHash JsonParser::jsonObjectToItemValues(const QJsonObject& itemJsonObject) { - QHash values; +ModelItemValues JsonParser::jsonObjectToItemValues(const QJsonObject& itemJsonObject) { + ModelItemValues values; QListIterator i(USER_FACING_ROLES); while (i.hasNext()) { diff --git a/formats/jsonparser.h b/formats/jsonparser.h index 702414d..be0fc6a 100644 --- a/formats/jsonparser.h +++ b/formats/jsonparser.h @@ -8,20 +8,22 @@ class QString; class QByteArray; class QJsonArray; +typedef QHash ModelItemValues; + using namespace std; class JsonParser { public: - static QList> toItemValuesList(const QByteArray& jsonData, - const QString& objectName = ""); - static QByteArray itemValuesListToJson(const QList>& itemValuesList, + static QList toItemValuesList(const QByteArray& jsonData, + const QString& objectName = ""); + static QByteArray itemValuesListToJson(const QList& itemValuesList, const QString& objectName = ""); private: explicit JsonParser(); static QJsonArray extractItemArray(const QByteArray& jsonData, const QString& objectName); - static QHash jsonObjectToItemValues(const QJsonObject& itemJsonObject); + static ModelItemValues jsonObjectToItemValues(const QJsonObject& itemJsonObject); static pair getKeyValuePair(const QJsonObject& itemJsonObject, const int role); }; diff --git a/genericcore.cpp b/genericcore.cpp index 9b86bfb..b75a225 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -111,8 +111,7 @@ void GenericCore::saveItems() { void GenericCore::importCSVFile(const QString& filePath) { qInfo() << "importing items from CSV..."; qDebug() << "filePath:" << filePath; - const QList> itemValuesList = - FileHandler::getItemValuesFromCSVFile(filePath); + const QList itemValuesList = FileHandler::getItemValuesFromCSVFile(filePath); // NEXT inform UI on errors if (itemValuesList.isEmpty()) { qDebug() << "No items found. Doing nothing..."; diff --git a/model/commands/insertrowscommand.cpp b/model/commands/insertrowscommand.cpp index 231ce29..99f25d7 100644 --- a/model/commands/insertrowscommand.cpp +++ b/model/commands/insertrowscommand.cpp @@ -5,9 +5,9 @@ #include "../tablemodel.h" InsertRowsCommand::InsertRowsCommand(TableModel* model, - int startRow, - QList > valueList, - QUndoCommand* parent) + int startRow, + QList valueList, + QUndoCommand* parent) : QUndoCommand(parent) , m_tableModel(model) , m_startRow(startRow) diff --git a/model/commands/insertrowscommand.h b/model/commands/insertrowscommand.h index 0341226..ddc5617 100644 --- a/model/commands/insertrowscommand.h +++ b/model/commands/insertrowscommand.h @@ -3,6 +3,8 @@ #include +typedef QHash ModelItemValues; + class TableModel; class InsertRowsCommand : public QUndoCommand { @@ -11,9 +13,9 @@ class InsertRowsCommand : public QUndoCommand { /// 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); + int startRow, + QList valueList, + QUndoCommand* parent = nullptr); /// QUndoCommand interface void undo() override; @@ -22,7 +24,7 @@ class InsertRowsCommand : public QUndoCommand { private: TableModel* m_tableModel; const int m_startRow; - const QList > m_valueList; + const QList m_valueList; }; #endif // INSERTROWSCOMMAND_H diff --git a/model/commands/removerowscommand.cpp b/model/commands/removerowscommand.cpp index 487a916..2e2a026 100644 --- a/model/commands/removerowscommand.cpp +++ b/model/commands/removerowscommand.cpp @@ -20,7 +20,7 @@ RemoveRowsCommand::RemoveRowsCommand(TableModel* model, const int rowPosition = startRow + row; QModelIndex index = m_tableModel->index(rowPosition, 0); - QHash values = m_tableModel->getItemValues(index); + ModelItemValues values = m_tableModel->getItemValues(index); m_valueList.append(values); } diff --git a/model/commands/removerowscommand.h b/model/commands/removerowscommand.h index 308f78b..6703aab 100644 --- a/model/commands/removerowscommand.h +++ b/model/commands/removerowscommand.h @@ -5,6 +5,8 @@ class TableModel; +typedef QHash ModelItemValues; + class RemoveRowsCommand : public QUndoCommand { public: // TODO don't use simple pointer to model @@ -22,7 +24,7 @@ class RemoveRowsCommand : public QUndoCommand { private: TableModel* m_tableModel; const int m_startRow; - QList> m_valueList; + QList m_valueList; }; #endif // REMOVEROWSCOMMAND_H diff --git a/model/modelitem.cpp b/model/modelitem.cpp index 7abf6bf..32bc401 100644 --- a/model/modelitem.cpp +++ b/model/modelitem.cpp @@ -5,7 +5,7 @@ #include #include -ModelItem::ModelItem(const QHash values) +ModelItem::ModelItem(const ModelItemValues values) : m_values(values) {} QVariant ModelItem::data(int role) const { return m_values.value(role); } diff --git a/model/modelitem.h b/model/modelitem.h index 2f3e037..9078c33 100644 --- a/model/modelitem.h +++ b/model/modelitem.h @@ -3,9 +3,11 @@ #include +typedef QHash ModelItemValues; + class ModelItem { public: - ModelItem(const QHash values); + ModelItem(const ModelItemValues values); QVariant data(int role) const; bool setData(const QVariant& value, int role); diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index dd0dae0..09011c8 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -120,8 +120,8 @@ bool TableModel::setItemData(const QModelIndex& index, const QMap return false; } -QHash TableModel::getItemValues(const QModelIndex& index) const { - QHash values; +ModelItemValues TableModel::getItemValues(const QModelIndex& index) const { + ModelItemValues values; QListIterator i(USER_FACING_ROLES); while (i.hasNext()) { @@ -169,14 +169,13 @@ void TableModel::appendItems(const QByteArray& jsonDoc) { insertItems(-1, jsonDo void TableModel::insertItems(int startPosition, const QByteArray& jsonDoc, const QModelIndex& parentIndex) { - const QList> valueList = - JsonParser::toItemValuesList(jsonDoc, ITEM_KEY_STRING); + const QList valueList = JsonParser::toItemValuesList(jsonDoc, ITEM_KEY_STRING); insertItems(startPosition, valueList, parentIndex); } void TableModel::insertItems(int startPosition, - const QList>& itemValuesList, + const QList& itemValuesList, const QModelIndex& parentIndex) { qInfo() << "Inserting item(s) into model..."; if (parentIndex != QModelIndex()) { @@ -192,7 +191,7 @@ void TableModel::insertItems(int startPosition, m_undoStack->push(insertCommand); } -void TableModel::execInsertItems(const int firstRow, const QList> valueList) { +void TableModel::execInsertItems(const int firstRow, const QList valueList) { const int nRows = valueList.size(); qDebug() << "Inserting" << nRows << "items..."; diff --git a/model/tablemodel.h b/model/tablemodel.h index 75f7bbd..0627d13 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -8,6 +8,8 @@ class ModelItem; using namespace std; +typedef QHash ModelItemValues; + class TableModel : public QAbstractTableModel { Q_OBJECT @@ -32,7 +34,7 @@ class TableModel : public QAbstractTableModel { bool setData(const QModelIndex& index, const QVariant& value, int role) override; bool setItemData(const QModelIndex& index, const QMap& roles) override; - QHash getItemValues(const QModelIndex& index) const; + ModelItemValues getItemValues(const QModelIndex& index) const; QJsonDocument getAllItemsAsJsonDoc() const; public slots: @@ -44,7 +46,7 @@ class TableModel : public QAbstractTableModel { const QByteArray& jsonDoc, const QModelIndex& parentIndex = QModelIndex()); void insertItems(int startPosition, - const QList>& itemValuesList, + const QList& itemValuesList, const QModelIndex& parentIndex = QModelIndex()); private: @@ -55,7 +57,7 @@ class TableModel : public QAbstractTableModel { /// *** functions *** /// undo/redo functions - void execInsertItems(const int firstRow, const QList> valueList); + void execInsertItems(const int firstRow, const QList valueList); void execRemoveItems(const int firstRow, const int nRows); void execEditItemData(const int row, const QMap& changedValues); From 99ed398c2fecbd016dacedd99b33deb398937ee7 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Tue, 6 Jan 2026 10:04:26 +0100 Subject: [PATCH 3/4] Simple implementation of CSV export. --- data/filehandler.cpp | 4 ++++ data/filehandler.h | 1 + formats/csvparser.cpp | 18 ++++++++++++++++++ formats/csvparser.h | 1 + genericcore.cpp | 7 +++++++ genericcore.h | 1 + model/tablemodel.cpp | 13 +++++++++++++ model/tablemodel.h | 1 + 8 files changed, 46 insertions(+) diff --git a/data/filehandler.cpp b/data/filehandler.cpp index dc99a9b..c405e55 100644 --- a/data/filehandler.cpp +++ b/data/filehandler.cpp @@ -50,6 +50,10 @@ QList FileHandler::getItemValuesFromCSVFile(const QString& file return result; } +bool FileHandler::exportToCSVFile(const QList& rows, const QString& filePath) { + return CsvParser::exportToCSVFile(rows, filePath); +} + FileHandler::FileHandler() {} /** Tries to open the file specified by the file path & returns the content diff --git a/data/filehandler.h b/data/filehandler.h index 32ba140..6fd6019 100644 --- a/data/filehandler.h +++ b/data/filehandler.h @@ -17,6 +17,7 @@ class FileHandler { /// CSV static QList getItemValuesFromCSVFile(const QString& filePath); + static bool exportToCSVFile(const QList& rows, const QString& filePath); private: explicit FileHandler(); diff --git a/formats/csvparser.cpp b/formats/csvparser.cpp index 1b6c943..365de1d 100644 --- a/formats/csvparser.cpp +++ b/formats/csvparser.cpp @@ -21,6 +21,24 @@ QList CsvParser::getItemsFromCSVFile(const QString& fileName) { } } +bool CsvParser::exportToCSVFile(const QList& rows, const QString& filePath) { + Document doc(std::string(), LabelParams(0, -1)); + const QList headerNames = GET_HEADER_NAMES(); + for (int column = 0; column < headerNames.size(); ++column) { + doc.SetColumnName(column, headerNames.at(column).toStdString()); + } + for (int row = 0; row < rows.size(); ++row) { + QStringList rowValues = rows.at(row); + std::vector rowValueStrings; + for (int column = 0; column < rowValues.size(); ++column) { + rowValueStrings.push_back(rowValues.at(column).toStdString()); + } + doc.InsertRow(row, rowValueStrings); + } + doc.Save(filePath.toStdString()); + return true; +} + CsvParser::CsvParser() {} /** A CSV file is compatible if the following is true: diff --git a/formats/csvparser.h b/formats/csvparser.h index 71986c9..7695156 100644 --- a/formats/csvparser.h +++ b/formats/csvparser.h @@ -12,6 +12,7 @@ class Document; class CsvParser { public: static QList getItemsFromCSVFile(const QString& fileName); + static bool exportToCSVFile(const QList& rows, const QString& filePath); private: explicit CsvParser(); diff --git a/genericcore.cpp b/genericcore.cpp index b75a225..96f07a3 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -121,6 +121,13 @@ void GenericCore::importCSVFile(const QString& filePath) { m_mainModel->insertItems(m_mainModel->rowCount(), itemValuesList); } +bool GenericCore::exportCSVFile(const QString& filePath) { + qInfo() << "exporting items to CSV..."; + qDebug() << "filePath:" << filePath; + const QList itemsAsStringLists = m_mainModel->getItemsAsStringLists(); + return FileHandler::exportToCSVFile(itemsAsStringLists, filePath); +} + void GenericCore::setupModels() { m_mainModel = make_shared(m_modelUndoStack, this); // TODO add QAbstractItemModelTester diff --git a/genericcore.h b/genericcore.h index b4a9776..e3c1e5f 100644 --- a/genericcore.h +++ b/genericcore.h @@ -27,6 +27,7 @@ class GenericCore : public QObject { void saveItems(); void importCSVFile(const QString& filePath); + bool exportCSVFile(const QString& filePath); signals: void displayStatusMessage(QString message); diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 09011c8..4895e9a 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -146,6 +146,19 @@ QJsonDocument TableModel::getAllItemsAsJsonDoc() const { return doc; } +QList TableModel::getItemsAsStringLists() const { + QList result; + foreach (shared_ptr item, m_items) { + QStringList valueList; + for (int column = 0; column < columnCount(); ++column) { + QString value = item->data(GET_ROLE_FOR_COLUMN(column)).toString(); + valueList.append(value); + } + result.append(valueList); + } + return result; +} + bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIndex) { if (parentIndex != QModelIndex()) { qWarning() << "Removing of child rows is not supported yet!"; diff --git a/model/tablemodel.h b/model/tablemodel.h index 0627d13..4bd2dcd 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -36,6 +36,7 @@ class TableModel : public QAbstractTableModel { ModelItemValues getItemValues(const QModelIndex& index) const; QJsonDocument getAllItemsAsJsonDoc() const; + QList getItemsAsStringLists() const; public slots: // bool insertRows(int position, int rows, const QModelIndex& parentIndex = QModelIndex()) From 8d4260b18de859ac94f10aa610784f0bdd5c895b Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Tue, 6 Jan 2026 10:15:01 +0100 Subject: [PATCH 4/4] Items can be saved before starting the updater. --- genericcore.cpp | 10 ++++------ genericcore.h | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/genericcore.cpp b/genericcore.cpp index 96f07a3..34ef9dd 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -76,12 +76,10 @@ bool GenericCore::isApplicationUpdateAvailable() { return true; } -void GenericCore::triggerApplicationUpdate() { - // TODO include cleaness of undo stack - // if (!m_undoStack->isClean()) { - // saveItems(); - // } - // QStringList args("update componentA componentB"); +void GenericCore::triggerApplicationUpdate(const bool saveChanges) { + if (saveChanges && !m_modelUndoStack->isClean()) { + saveItems(); + } QStringList args("--start-updater"); QString toolFilePath = getMaintenanceToolFilePath(); QProcess::startDetached(toolFilePath, args); diff --git a/genericcore.h b/genericcore.h index e3c1e5f..b25135c 100644 --- a/genericcore.h +++ b/genericcore.h @@ -20,7 +20,7 @@ class GenericCore : public QObject { void sayHello() const; bool isApplicationUpdateAvailable(); - void triggerApplicationUpdate(); + void triggerApplicationUpdate(const bool saveChanges); QUndoStack* getModelUndoStack() const; std::shared_ptr getModel() const;