Compare commits

...

10 Commits

65 changed files with 825 additions and 369 deletions

View File

@ -1,32 +1,5 @@
# Changelog # Changelog
## 0.3.0 - 2026-02-04 ## 0.1.0 - 2026-02-05
### Added Initial release of BeetRound. Based on GenericQtClient v0.3.0 and adjusted the project name to the use case.
- Basic JSON RESTful client (compatible with the GenericRestServer)
- No editing of existing items on the server yet
### Fixed
- Save changes when closing the EditItemDialog
## 0.2.1 - 2026-01-15
### Added
- Displaying QR code of current item in edit item dialog
## 0.2 - 2026-01-14
### Added
- Displaying editable table model (sortable by column)
- Modifying model data can be un-/redone
- Data is stored in JSON file and automatically loaded on application start
- Data can be imported/exported from/into CSV file
- Model rows containing specific data can be selected via "Find item(s)" dialog
## 0.1 - 2025-11-01
A simple Qt application separated into an UI frontend and backend core. With installer (for Linux for now) and option to trigger updater from within the application.

View File

@ -8,8 +8,8 @@ enable_testing()
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(libs/GenericCore) add_subdirectory(libs/BeetRoundCore)
set (CORE_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/GenericCore) set (CORE_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/BeetRoundCore)
### 3rd party libraries ### 3rd party libraries
add_subdirectory(libs/3rdParty/Qt-QrCodeGenerator) add_subdirectory(libs/3rdParty/Qt-QrCodeGenerator)
@ -18,7 +18,7 @@ set (QR_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/3rdParty/Qt-QrCodeGenerator)
configure_file(ApplicationConfig.h.in ApplicationConfig.h) configure_file(ApplicationConfig.h.in ApplicationConfig.h)
#Frontend applications #Frontend applications
add_subdirectory(UIs/GenericWidgets) add_subdirectory(UIs/BeetRoundWidgets)
### Tests ### Tests
add_subdirectory(tests/GenericCoreTests) add_subdirectory(tests/GenericCoreTests)

View File

@ -1,19 +1,9 @@
# GenericQtClient # BeetRound
This is a Qt application which can be used as a starting point for new software projects. BeetRound is a software project to manage the yearly crop share auction (german: Bietrunde) of a Community-supported agriculture (CSA, german: Solidarische Landwirtschaft (SoLaWi)).
Common features most Qt software clients need will be already implemented and can be easily configured for the specific needs. This is the management application. The project also includes a web server where the participants can submit their biddings.
## Implemented features: Further information will follow…
- Separated UI frontend and backend core (in its own git submodules)
- Using Qt model/view framework with QT undo framework
- Saving/Loading JSON files
- CSV import/export
- installable and updateable via Qt updater framework
- only linux for now
- Qt 6 libraries must be installed on the machine to run
## Coming features: This project is currently tailored for one specific CSA and will be expanded to broader use after the coming crop share auction in february.
- REST client
- Extensive use of sorting and filtering models to display data in different ways
- ...

View File

@ -34,6 +34,8 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
dialogs/edititemdialog.h dialogs/edititemdialog.cpp dialogs/edititemdialog.h dialogs/edititemdialog.cpp
dialogs/settingsdialog.h dialogs/settingsdialog.cpp dialogs/settingsdialog.h dialogs/settingsdialog.cpp
views/itemdetailmapper.h views/itemdetailmapper.cpp views/itemdetailmapper.h views/itemdetailmapper.cpp
widgets/comboboxdelegate.h widgets/comboboxdelegate.cpp
widgets/spinboxdelegate.h widgets/spinboxdelegate.cpp
) )
# Define target properties for Android with Qt 6 as: # Define target properties for Android with Qt 6 as:
# set_property(TARGET ${TARGET_APP} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR # set_property(TARGET ${TARGET_APP} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
@ -63,7 +65,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/) target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/)
target_link_libraries(${TARGET_APP} PRIVATE GenericCore) target_link_libraries(${TARGET_APP} PRIVATE BeetRoundCore)
target_include_directories(${TARGET_APP} PRIVATE ${QR_LIB_DIR}/src) target_include_directories(${TARGET_APP} PRIVATE ${QR_LIB_DIR}/src)
target_link_libraries(${TARGET_APP} PRIVATE qrcode) target_link_libraries(${TARGET_APP} PRIVATE qrcode)

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,149 @@
#include "newitemdialog.h"
#include <QGridLayout>
#include <QJsonArray>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include <QStringListModel>
#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_numberLabel = new QLabel(GET_HEADER_FOR_COLUMN(0));
m_numberBox = new QSpinBox();
m_numberBox->setMaximum(1000);
m_lastNameLabel = new QLabel(GET_HEADER_FOR_COLUMN(1));
m_lastNameEdit = new QLineEdit();
m_lastNameLabel->setBuddy(m_lastNameEdit);
m_firstNameLabel = new QLabel(GET_HEADER_FOR_COLUMN(2));
m_firstNameEdit = new QLineEdit();
m_firstNameLabel->setBuddy(m_firstNameEdit);
m_shareTypeLabel = new QLabel(GET_HEADER_FOR_COLUMN(3));
m_shareTypeBox = new QComboBox();
m_shareTypeLabel->setBuddy(m_shareTypeBox);
m_shareTypeModel = new QStringListModel(SHARE_TYPES, this);
m_shareTypeBox->setModel(m_shareTypeModel);
m_amountLabel = new QLabel(GET_HEADER_FOR_COLUMN(4));
m_amountSpinBox = new QDoubleSpinBox();
m_amountLabel->setBuddy(m_amountSpinBox);
m_amountSpinBox->setValue(1.0);
m_biddingTypeLabel = new QLabel(GET_HEADER_FOR_COLUMN(5));
m_biddingTypeBox = new QComboBox();
m_biddingTypeLabel->setBuddy(m_biddingTypeBox);
m_biddingTypeModel = new QStringListModel(BIDDING_TYPES, this);
m_biddingTypeBox->setModel(m_biddingTypeModel);
m_bidding1Label = new QLabel(GET_HEADER_FOR_COLUMN(6));
m_bidding1SpinBox = new QSpinBox();
m_bidding1SpinBox->setMaximum(500);
m_bidding2Label = new QLabel(GET_HEADER_FOR_COLUMN(7));
m_bidding2SpinBox = new QSpinBox();
m_bidding2SpinBox->setMaximum(500);
m_bidding3Label = new QLabel(GET_HEADER_FOR_COLUMN(8));
m_bidding3SpinBox = new QSpinBox();
m_bidding3SpinBox->setMaximum(500);
m_depotWish1Label = new QLabel(GET_HEADER_FOR_COLUMN(9));
m_depotWish1Edit = new QLineEdit();
m_depotWish1Label->setBuddy(m_depotWish1Edit);
m_depotWish2Label = new QLabel(GET_HEADER_FOR_COLUMN(10));
m_depotWish2Edit = new QLineEdit();
m_depotWish2Label->setBuddy(m_depotWish2Edit);
m_mailLabel = new QLabel(GET_HEADER_FOR_COLUMN(11));
m_mailEdit = new QLineEdit();
m_mailEdit->setMinimumWidth(200);
m_mailLabel->setBuddy(m_mailEdit);
/// layouting
QGridLayout* layout = new QGridLayout();
layout->addWidget(m_numberLabel, 0, 0, 1, 1);
layout->addWidget(m_numberBox, 0, 1, 1, 1);
layout->addWidget(m_lastNameLabel, 1, 0, 1, 1);
layout->addWidget(m_lastNameEdit, 1, 1, 1, 1);
layout->addWidget(m_firstNameLabel, 2, 0, 1, 1);
layout->addWidget(m_firstNameEdit, 2, 1, 1, 1);
layout->addWidget(m_shareTypeLabel, 3, 0, 1, 1);
layout->addWidget(m_shareTypeBox, 3, 1, 1, 1);
layout->addWidget(m_amountLabel, 4, 0, 1, 1);
layout->addWidget(m_amountSpinBox, 4, 1, 1, 1);
layout->addWidget(m_biddingTypeLabel, 5, 0, 1, 1);
layout->addWidget(m_biddingTypeBox, 5, 1, 1, 1);
layout->addWidget(m_bidding1Label, 6, 0, 1, 1);
layout->addWidget(m_bidding1SpinBox, 6, 1, 1, 1);
layout->addWidget(m_bidding2Label, 7, 0, 1, 1);
layout->addWidget(m_bidding2SpinBox, 7, 1, 1, 1);
layout->addWidget(m_bidding3Label, 8, 0, 1, 1);
layout->addWidget(m_bidding3SpinBox, 8, 1, 1, 1);
layout->addWidget(m_depotWish1Label, 9, 0, 1, 1);
layout->addWidget(m_depotWish1Edit, 9, 1, 1, 1);
layout->addWidget(m_depotWish2Label, 10, 0, 1, 1);
layout->addWidget(m_depotWish2Edit, 10, 1, 1, 1);
layout->addWidget(m_mailLabel, 11, 0, 1, 1);
layout->addWidget(m_mailEdit, 11, 1, 1, 1);
m_contentContainer->setLayout(layout);
m_outerLayout->insertWidget(0, m_contentContainer);
}
void NewItemDialog::accept() {
ModelItemValues itemValues;
// TODO (after refactoring data structure for input widgets) use iteration through the relevant
// roles and their input widgets
// itemValues.insert(LastNameRole, m_nameEdit->text());
itemValues.insert(GET_ROLE_FOR_COLUMN(0), m_numberBox->value());
itemValues.insert(GET_ROLE_FOR_COLUMN(1), m_lastNameEdit->text());
itemValues.insert(GET_ROLE_FOR_COLUMN(2), m_firstNameEdit->text());
itemValues.insert(GET_ROLE_FOR_COLUMN(3), m_shareTypeBox->currentText());
itemValues.insert(GET_ROLE_FOR_COLUMN(4), m_amountSpinBox->value());
itemValues.insert(GET_ROLE_FOR_COLUMN(5), m_biddingTypeBox->currentText());
itemValues.insert(GET_ROLE_FOR_COLUMN(6), m_bidding1SpinBox->value());
itemValues.insert(GET_ROLE_FOR_COLUMN(7), m_bidding2SpinBox->value());
itemValues.insert(GET_ROLE_FOR_COLUMN(8), m_bidding3SpinBox->value());
itemValues.insert(GET_ROLE_FOR_COLUMN(9), m_depotWish1Edit->text());
itemValues.insert(GET_ROLE_FOR_COLUMN(10), m_depotWish2Edit->text());
itemValues.insert(GET_ROLE_FOR_COLUMN(11), m_mailEdit->text());
const QByteArray jsonDoc = JsonParser::itemValuesListToJson({itemValues}, ITEMS_KEY_STRING);
emit addItems(jsonDoc);
resetContent();
AbstractDialog::accept();
}
void NewItemDialog::resetContent() {
m_numberBox->setValue(0);
m_lastNameEdit->setText("");
m_firstNameEdit->setText("");
m_shareTypeBox->setCurrentIndex(0);
m_amountSpinBox->setValue(1);
m_biddingTypeBox->setCurrentIndex(0);
m_bidding1SpinBox->setValue(0);
m_bidding2SpinBox->setValue(0);
m_bidding3SpinBox->setValue(0);
m_depotWish1Edit->setText("");
m_depotWish2Edit->setText("");
m_mailEdit->setText("");
}

View File

@ -0,0 +1,68 @@
#ifndef NEWITEMDIALOG_H
#define NEWITEMDIALOG_H
#include "abstractdialog.h"
#include <QComboBox>
class QStringListModel;
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_numberLabel;
QSpinBox* m_numberBox;
QLabel* m_lastNameLabel;
QLineEdit* m_lastNameEdit;
QLabel* m_firstNameLabel;
QLineEdit* m_firstNameEdit;
QLabel* m_shareTypeLabel;
QComboBox* m_shareTypeBox;
QStringListModel* m_shareTypeModel = nullptr;
QLabel* m_amountLabel;
QDoubleSpinBox* m_amountSpinBox;
QLabel* m_biddingTypeLabel;
QComboBox* m_biddingTypeBox;
QStringListModel* m_biddingTypeModel = nullptr;
QLabel* m_bidding1Label;
QSpinBox* m_bidding1SpinBox;
QLabel* m_bidding2Label;
QSpinBox* m_bidding2SpinBox;
QLabel* m_bidding3Label;
QSpinBox* m_bidding3SpinBox;
QLabel* m_depotWish1Label;
QLineEdit* m_depotWish1Edit;
QLabel* m_depotWish2Label;
QLineEdit* m_depotWish2Edit;
QLabel* m_mailLabel;
QLineEdit* m_mailEdit;
void resetContent();
};
#endif // NEWITEMDIALOG_H

View File

@ -15,7 +15,10 @@
#include "dialogs/settingsdialog.h" #include "dialogs/settingsdialog.h"
#include "genericcore.h" #include "genericcore.h"
#include "model/generalsortfiltermodel.h" #include "model/generalsortfiltermodel.h"
#include "model/metadata.h"
#include "model/tablemodel.h" #include "model/tablemodel.h"
#include "widgets/comboboxdelegate.h"
#include "widgets/spinboxdelegate.h"
static QStandardPaths::StandardLocation standardLocation = QStandardPaths::HomeLocation; static QStandardPaths::StandardLocation standardLocation = QStandardPaths::HomeLocation;
static QString updateTextClean = "Do you want to update the application now?"; static QString updateTextClean = "Do you want to update the application now?";
@ -46,11 +49,7 @@ MainWindow::MainWindow(QWidget* parent)
restoreGeometry(settings.value("geometry").toByteArray()); restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowState").toByteArray()); restoreState(settings.value("windowState").toByteArray());
// m_tableModel = m_core->getModel(); setupModelViews();
// ui->tableView->setModel(m_tableModel.get());
m_proxyModel = m_core->getSortFilterModel();
ui->tableView->setModel((QAbstractItemModel*)m_proxyModel.get());
ui->tableView->setSortingEnabled(true);
createActions(); createActions();
createHelpMenu(); createHelpMenu();
@ -147,8 +146,11 @@ void MainWindow::onSelectionChanged(const QItemSelection& selected,
void MainWindow::onAboutClicked() { void MainWindow::onAboutClicked() {
const QString applicationName = APPLICATION_NAME; const QString applicationName = APPLICATION_NAME;
const QString titlePrefix = tr("About "); const QString titlePrefix = tr("About ");
// TODO read the about text from a rich text / markdown somewhere else.
const QString aboutText = const QString aboutText =
tr(QString("<b>%1 v%2</b> is a template for Qt applications." tr(QString("<b>%1 v%2</b> is a software project to manage the yearly crop share "
"auction (german: Bietrunde) of a Community-supported agriculture (CSA, german: "
"Solidarische Landwirtschaft (SoLaWi))."
"<br><br><a href=\"https://working-copy.org/\">Working-Copy_Collective website</a>" "<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><a href=\"mailto:support@working-copy.org\">Mail to support</a>"
"<br><br>It uses the <a href=\"https://qt.io\">Qt Framework</a>.") "<br><br>It uses the <a href=\"https://qt.io\">Qt Framework</a>.")
@ -340,6 +342,29 @@ void MainWindow::execSettingsDialog() {
delete settingsDialog; delete settingsDialog;
} }
void MainWindow::setupModelViews() {
// m_tableModel = m_core->getModel();
// ui->tableView->setModel(m_tableModel.get());
m_proxyModel = m_core->getSortFilterModel();
/// setting number delegates to combo boxes
SpinboxDelegate* spinboxDelegate = new SpinboxDelegate(this);
ui->tableView->setItemDelegateForColumn(0, spinboxDelegate);
ui->tableView->setItemDelegateForColumn(4, spinboxDelegate);
ui->tableView->setItemDelegateForColumn(6, spinboxDelegate);
ui->tableView->setItemDelegateForColumn(7, spinboxDelegate);
ui->tableView->setItemDelegateForColumn(8, spinboxDelegate);
/// setting type delegates to combo boxes
ComboboxDelegate* shareTypeDelegate = new ComboboxDelegate(SHARE_TYPES, this);
ComboboxDelegate* biddingTypeDelegate = new ComboboxDelegate(BIDDING_TYPES, this);
ui->tableView->setItemDelegateForColumn(3, shareTypeDelegate);
ui->tableView->setItemDelegateForColumn(5, biddingTypeDelegate);
ui->tableView->setModel((QAbstractItemModel*)m_proxyModel.get());
ui->tableView->setSortingEnabled(true);
}
void MainWindow::createActions() { void MainWindow::createActions() {
// TODO add generic menu actions (file/new, edit/cut, ...) // TODO add generic menu actions (file/new, edit/cut, ...)
createFileActions(); createFileActions();

View File

@ -7,8 +7,8 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QUndoStack; class QUndoStack;
class QUndoView; class QUndoView;
namespace Ui { namespace Ui {
class MainWindow; class MainWindow;
} }
@ -110,6 +110,7 @@ class MainWindow : public QMainWindow {
unique_ptr<EditItemDialog> m_editItemDialog; unique_ptr<EditItemDialog> m_editItemDialog;
/// Setup functions /// Setup functions
void setupModelViews();
void createActions(); void createActions();
void createFileActions(); void createFileActions();
void createUndoActions(); void createUndoActions();

View File

@ -1,6 +1,7 @@
#include "itemdetailmapper.h" #include "itemdetailmapper.h"
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QComboBox>
#include <QDataWidgetMapper> #include <QDataWidgetMapper>
#include <QGridLayout> #include <QGridLayout>
#include <QJsonArray> #include <QJsonArray>
@ -9,6 +10,7 @@
#include <QLineEdit> #include <QLineEdit>
#include <QPushButton> #include <QPushButton>
#include <QSpinBox> #include <QSpinBox>
#include <QStringListModel>
#include <QTableView> #include <QTableView>
#include "model/metadata.h" #include "model/metadata.h"
@ -34,42 +36,84 @@ ItemDetailMapper::ItemDetailMapper(QWidget* parent)
/// the individual widgets /// the individual widgets
// REFACTOR deduce label names and types from meta data & use a factory // REFACTOR deduce label names and types from meta data & use a factory
m_nameLabel = new QLabel("&Name"); m_numberLabel = new QLabel(GET_HEADER_FOR_COLUMN(0));
m_nameEdit = new QLineEdit(); m_numberBox = new QSpinBox();
m_nameLabel->setBuddy(m_nameEdit); m_numberBox->setMaximum(1000);
m_descriptionLabel = new QLabel("&Description"); m_lastNameLabel = new QLabel(GET_HEADER_FOR_COLUMN(1));
m_descriptionEdit = new QLineEdit(); m_lastNameEdit = new QLineEdit();
m_descriptionLabel->setBuddy(m_descriptionEdit); m_lastNameLabel->setBuddy(m_lastNameEdit);
m_infoLabel = new QLabel("&Info"); m_firstNameLabel = new QLabel(GET_HEADER_FOR_COLUMN(2));
m_infoEdit = new QLineEdit(); m_firstNameEdit = new QLineEdit();
m_infoLabel->setBuddy(m_infoEdit); m_firstNameLabel->setBuddy(m_firstNameEdit);
m_amountLabel = new QLabel("&Amount"); m_shareTypeLabel = new QLabel(GET_HEADER_FOR_COLUMN(3));
m_amountBox = new QSpinBox(); m_shareTypeBox = new QComboBox();
m_amountBox->setMaximum(1000); m_shareTypeLabel->setBuddy(m_shareTypeBox);
m_amountLabel->setBuddy(m_amountBox); m_shareTypeModel = new QStringListModel(SHARE_TYPES, this);
m_shareTypeBox->setModel(m_shareTypeModel);
m_factorLabel = new QLabel("&Factor"); m_amountLabel = new QLabel(GET_HEADER_FOR_COLUMN(4));
m_factorBox = new QDoubleSpinBox(); m_amountSpinBox = new QDoubleSpinBox();
m_factorBox->setMaximum(1000); m_amountLabel->setBuddy(m_amountSpinBox);
m_factorLabel->setBuddy(m_factorBox);
m_biddingTypeLabel = new QLabel(GET_HEADER_FOR_COLUMN(5));
m_biddingTypeBox = new QComboBox();
m_biddingTypeLabel->setBuddy(m_biddingTypeBox);
m_biddingTypeModel = new QStringListModel(BIDDING_TYPES, this);
m_biddingTypeBox->setModel(m_biddingTypeModel);
m_bidding1Label = new QLabel(GET_HEADER_FOR_COLUMN(6));
m_bidding1SpinBox = new QSpinBox();
m_bidding1SpinBox->setMaximum(500);
m_bidding2Label = new QLabel(GET_HEADER_FOR_COLUMN(7));
m_bidding2SpinBox = new QSpinBox();
m_bidding2SpinBox->setMaximum(500);
m_bidding3Label = new QLabel(GET_HEADER_FOR_COLUMN(8));
m_bidding3SpinBox = new QSpinBox();
m_bidding3SpinBox->setMaximum(500);
m_depotWish1Label = new QLabel(GET_HEADER_FOR_COLUMN(9));
m_depotWish1Edit = new QLineEdit();
m_depotWish1Label->setBuddy(m_depotWish1Edit);
m_depotWish2Label = new QLabel(GET_HEADER_FOR_COLUMN(10));
m_depotWish2Edit = new QLineEdit();
m_depotWish2Label->setBuddy(m_depotWish2Edit);
m_mailLabel = new QLabel(GET_HEADER_FOR_COLUMN(11));
m_mailEdit = new QLineEdit();
m_mailEdit->setMinimumWidth(200);
m_mailLabel->setBuddy(m_mailEdit);
QGridLayout* layout = new QGridLayout(); QGridLayout* layout = new QGridLayout();
layout->addWidget(m_nameLabel, 0, 0, 1, 1); layout->addWidget(m_numberLabel, 0, 0, 1, 1);
layout->addWidget(m_nameEdit, 0, 1, 1, 1); layout->addWidget(m_numberBox, 0, 1, 1, 1);
layout->addWidget(m_descriptionLabel, 1, 0, 1, 1); layout->addWidget(m_lastNameLabel, 1, 0, 1, 1);
layout->addWidget(m_descriptionEdit, 1, 1, 1, 1); layout->addWidget(m_lastNameEdit, 1, 1, 1, 1);
layout->addWidget(m_infoLabel, 2, 0, 1, 1); layout->addWidget(m_firstNameLabel, 2, 0, 1, 1);
layout->addWidget(m_infoEdit, 2, 1, 1, 1); layout->addWidget(m_firstNameEdit, 2, 1, 1, 1);
layout->addWidget(m_amountLabel, 3, 0, 1, 1); layout->addWidget(m_shareTypeLabel, 3, 0, 1, 1);
layout->addWidget(m_amountBox, 3, 1, 1, 1); layout->addWidget(m_shareTypeBox, 3, 1, 1, 1);
layout->addWidget(m_factorLabel, 4, 0, 1, 1); layout->addWidget(m_amountLabel, 4, 0, 1, 1);
layout->addWidget(m_factorBox, 4, 1, 1, 1); layout->addWidget(m_amountSpinBox, 4, 1, 1, 1);
layout->addWidget(m_biddingTypeLabel, 5, 0, 1, 1);
layout->addWidget(m_biddingTypeBox, 5, 1, 1, 1);
layout->addWidget(m_bidding1Label, 6, 0, 1, 1);
layout->addWidget(m_bidding1SpinBox, 6, 1, 1, 1);
layout->addWidget(m_bidding2Label, 7, 0, 1, 1);
layout->addWidget(m_bidding2SpinBox, 7, 1, 1, 1);
layout->addWidget(m_bidding3Label, 8, 0, 1, 1);
layout->addWidget(m_bidding3SpinBox, 8, 1, 1, 1);
layout->addWidget(m_depotWish1Label, 9, 0, 1, 1);
layout->addWidget(m_depotWish1Edit, 9, 1, 1, 1);
layout->addWidget(m_depotWish2Label, 10, 0, 1, 1);
layout->addWidget(m_depotWish2Edit, 10, 1, 1, 1);
layout->addWidget(m_mailLabel, 11, 0, 1, 1);
layout->addWidget(m_mailEdit, 11, 1, 1, 1);
layout->addWidget(m_previousButton, 5, 0, 1, 1); layout->addWidget(m_previousButton, 12, 0, 1, 1);
layout->addWidget(m_nextButton, 5, 1, 1, 1); layout->addWidget(m_nextButton, 12, 1, 1, 1);
setLayout(layout); setLayout(layout);
} }
@ -81,11 +125,18 @@ void ItemDetailMapper::setModelMappings(QTableView* tableView) {
m_selectionModel = tableView->selectionModel(); m_selectionModel = tableView->selectionModel();
m_mapper->setModel(m_model); m_mapper->setModel(m_model);
m_mapper->addMapping(m_nameEdit, 0); m_mapper->addMapping(m_numberBox, 0);
m_mapper->addMapping(m_descriptionEdit, 1); m_mapper->addMapping(m_lastNameEdit, 1);
m_mapper->addMapping(m_infoEdit, 2); m_mapper->addMapping(m_firstNameEdit, 2);
m_mapper->addMapping(m_amountBox, 3); m_mapper->addMapping(m_shareTypeBox, 3, "currentText");
m_mapper->addMapping(m_factorBox, 4); m_mapper->addMapping(m_amountSpinBox, 4);
m_mapper->addMapping(m_biddingTypeBox, 5, "currentText");
m_mapper->addMapping(m_bidding1SpinBox, 6);
m_mapper->addMapping(m_bidding2SpinBox, 7);
m_mapper->addMapping(m_bidding3SpinBox, 8);
m_mapper->addMapping(m_depotWish1Edit, 9);
m_mapper->addMapping(m_depotWish2Edit, 10);
m_mapper->addMapping(m_mailEdit, 11);
m_mapper->setCurrentIndex(m_selectionModel->currentIndex().row()); m_mapper->setCurrentIndex(m_selectionModel->currentIndex().row());
@ -119,11 +170,11 @@ void ItemDetailMapper::rowsRemoved(const QModelIndex& parent, int start, int end
if (m_model->rowCount() == 0) { if (m_model->rowCount() == 0) {
setEnabled(false); setEnabled(false);
m_nameEdit->clear(); // m_nameEdit->clear();
m_descriptionEdit->clear(); // m_descriptionEdit->clear();
m_infoEdit->clear(); // m_infoEdit->clear();
m_amountBox->clear(); // m_amountBox->clear();
m_factorBox->clear(); // m_factorBox->clear();
} }
updateButtons(m_mapper->currentIndex()); updateButtons(m_mapper->currentIndex());

View File

@ -6,11 +6,13 @@
class QLabel; class QLabel;
class QLineEdit; class QLineEdit;
class QComboBox;
class QDoubleSpinBox; class QDoubleSpinBox;
class QSpinBox; class QSpinBox;
class QPushButton; class QPushButton;
class QAbstractItemModel; class QAbstractItemModel;
class QItemSelectionModel; class QItemSelectionModel;
class QStringListModel;
class QTableView; class QTableView;
class ItemDetailMapper : public QWidget { class ItemDetailMapper : public QWidget {
@ -45,21 +47,42 @@ class ItemDetailMapper : public QWidget {
std::unique_ptr<QDataWidgetMapper> m_mapper; std::unique_ptr<QDataWidgetMapper> m_mapper;
/// GUI elements /// GUI elements
QLabel* m_nameLabel = nullptr; QLabel* m_numberLabel;
QLineEdit* m_nameEdit = nullptr; QSpinBox* m_numberBox;
QLabel* m_descriptionLabel = nullptr; QLabel* m_lastNameLabel;
QLineEdit* m_descriptionEdit = nullptr; QLineEdit* m_lastNameEdit;
QLabel* m_infoLabel = nullptr; QLabel* m_firstNameLabel;
QLineEdit* m_infoEdit = nullptr; QLineEdit* m_firstNameEdit;
QLabel* m_amountLabel = nullptr; QLabel* m_shareTypeLabel;
QSpinBox* m_amountBox = nullptr; QComboBox* m_shareTypeBox;
QStringListModel* m_shareTypeModel = nullptr;
QLabel* m_factorLabel = nullptr; QLabel* m_amountLabel;
QDoubleSpinBox* m_factorBox = nullptr; QDoubleSpinBox* m_amountSpinBox;
QLabel* m_biddingTypeLabel;
QComboBox* m_biddingTypeBox;
QStringListModel* m_biddingTypeModel = nullptr;
QLabel* m_bidding1Label;
QSpinBox* m_bidding1SpinBox;
QLabel* m_bidding2Label;
QSpinBox* m_bidding2SpinBox;
QLabel* m_bidding3Label;
QSpinBox* m_bidding3SpinBox;
QLabel* m_depotWish1Label;
QLineEdit* m_depotWish1Edit;
QLabel* m_depotWish2Label;
QLineEdit* m_depotWish2Edit;
QLabel* m_mailLabel;
QLineEdit* m_mailEdit;
/// Model mapper stuff
QPushButton* m_nextButton; QPushButton* m_nextButton;
QPushButton* m_previousButton; QPushButton* m_previousButton;
}; };

View File

@ -0,0 +1,66 @@
#include "comboboxdelegate.h"
#include <QComboBox>
#include <QStringListModel>
#include "model/metadata.h"
ComboboxDelegate::ComboboxDelegate(const QStringList items, QObject* parent)
: QStyledItemDelegate(parent)
, m_types(new QStringListModel(items)) {}
void ComboboxDelegate::paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const {
QStyledItemDelegate::paint(painter, option, index);
}
QSize ComboboxDelegate::sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const {
return QStyledItemDelegate::sizeHint(option, index);
}
QWidget* ComboboxDelegate::createEditor(QWidget* parent,
const QStyleOptionViewItem& /*option*/,
const QModelIndex& /*index*/) const {
QComboBox* editor = new QComboBox(parent);
editor->setModel(m_types);
return editor;
// return QStyledItemDelegate::createEditor(parent, option, index);
}
void ComboboxDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const {
/// Get the value via index of the Model
const QAbstractItemModel* localModel = index.model();
const QString headerText = localModel->headerData(index.column(), Qt::Horizontal).toString();
const UserRoles role = GET_ROLE_FOR_COLUMN(index.column());
const bool isShareType = SHARE_TYPE_ROLES.contains(role);
const bool isBiddingType = BIDDING_TYPE_ROLES.contains(role);
/// Put the value into the SpinBox
if (isShareType) {
const QString valueString = index.model()->data(index, ShareTypeRole).toString();
int value = SHARE_TYPES.indexOf(valueString);
QComboBox* combobox = static_cast<QComboBox*>(editor);
combobox->setCurrentIndex(value);
// QStyledItemDelegate::setEditorData(editor, index);
} else if (isBiddingType) {
const QString valueString = index.model()->data(index, BiddingTypeRole).toString();
int value = BIDDING_TYPES.indexOf(valueString);
// Put the value into the SpinBox
QComboBox* combobox = static_cast<QComboBox*>(editor);
combobox->setCurrentIndex(value);
// QStyledItemDelegate::setEditorData(editor, index);
} else {
qCritical() << "Could not find the correct type role for index:" << index << "!!!";
QComboBox* combobox = static_cast<QComboBox*>(editor);
combobox->setCurrentIndex(-1);
}
}
void ComboboxDelegate::setModelData(QWidget* editor,
QAbstractItemModel* model,
const QModelIndex& index) const {
QStyledItemDelegate::setModelData(editor, model, index);
}

View File

@ -0,0 +1,30 @@
#ifndef COMBOBOXDELEGATE_H
#define COMBOBOXDELEGATE_H
#include <QStyledItemDelegate>
class QStringListModel;
class ComboboxDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit ComboboxDelegate(const QStringList items, QObject* parent = nullptr);
/// QAbstractItemDelegate interface
public:
void paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
QWidget* createEditor(QWidget* parent,
const QStyleOptionViewItem& option,
const QModelIndex&) const override;
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
void setModelData(QWidget* editor,
QAbstractItemModel* model,
const QModelIndex& index) const override;
private:
QStringListModel* m_types = nullptr;
};
#endif // COMBOBOXDELEGATE_H

View File

@ -0,0 +1,69 @@
#include "spinboxdelegate.h"
#include <QSpinBox>
#include "model/metadata.h"
SpinboxDelegate::SpinboxDelegate(QObject* parent)
: QStyledItemDelegate(parent) {}
void SpinboxDelegate::paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const {
QStyledItemDelegate::paint(painter, option, index);
}
QSize SpinboxDelegate::sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const {
return QStyledItemDelegate::sizeHint(option, index);
}
QWidget* SpinboxDelegate::createEditor(QWidget* parent,
const QStyleOptionViewItem& option,
const QModelIndex& index) const {
const QAbstractItemModel* localModel = index.model();
QString headerText = localModel->headerData(index.column(), Qt::Horizontal).toString();
const UserRoles role = GET_ROLE_FOR_COLUMN(index.column());
const bool isInt = INT_ROLES.contains(role);
if (isInt) {
QSpinBox* editor = new QSpinBox(parent);
editor->setMinimum(0);
editor->setMaximum(23000);
return editor;
} else {
QDoubleSpinBox* editor = new QDoubleSpinBox(parent);
editor->setMinimum(0);
editor->setMaximum(23000);
return editor;
}
// return QStyledItemDelegate::createEditor(parent, option, index);
}
void SpinboxDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const {
// Get the value via index of the Model
const QAbstractItemModel* localModel = index.model();
QString headerText = localModel->headerData(index.column(), Qt::Horizontal).toString();
const UserRoles role = GET_ROLE_FOR_COLUMN(index.column());
const bool isInt = INT_ROLES.contains(role);
if (isInt) {
int value = index.model()->data(index, Qt::EditRole).toInt();
// Put the value into the SpinBox
QSpinBox* spinbox = static_cast<QSpinBox*>(editor);
spinbox->setValue(value);
} else {
// Put the value into the SpinBox
qreal value = index.model()->data(index, Qt::EditRole).toReal();
QDoubleSpinBox* spinbox = static_cast<QDoubleSpinBox*>(editor);
spinbox->setValue(value);
}
// QStyledItemDelegate::setEditorData(editor, index);
}
void SpinboxDelegate::setModelData(QWidget* editor,
QAbstractItemModel* model,
const QModelIndex& index) const {
QStyledItemDelegate::setModelData(editor, model, index);
}

View File

@ -0,0 +1,26 @@
#ifndef SPINBOXDELEGATE_H
#define SPINBOXDELEGATE_H
#include <QStyledItemDelegate>
class SpinboxDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit SpinboxDelegate(QObject* parent = nullptr);
/// QAbstractItemDelegate interface
public:
void paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
QWidget* createEditor(QWidget* parent,
const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
void setModelData(QWidget* editor,
QAbstractItemModel* model,
const QModelIndex& index) const override;
};
#endif // SPINBOXDELEGATE_H

View File

@ -1,81 +0,0 @@
#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() {
ModelItemValues 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}, ITEMS_KEY_STRING);
emit addItems(jsonDoc);
// resetContent();
AbstractDialog::accept();
}

View File

@ -1,43 +0,0 @@
#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

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(TARGET_APP "GenericCore") set(TARGET_APP "BeetRoundCore")
project(${TARGET_APP} VERSION 0.1.0 LANGUAGES CXX) project(${TARGET_APP} VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
@ -43,8 +43,8 @@ add_library(${TARGET_APP} STATIC
include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui) target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui)
target_link_libraries(GenericCore PRIVATE Qt${QT_VERSION_MAJOR}::Test) target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Test)
target_link_libraries(GenericCore PRIVATE Qt${QT_VERSION_MAJOR}::Network) target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Network)
target_compile_definitions(${TARGET_APP} PRIVATE ${TARGET_APP}_LIBRARY) target_compile_definitions(${TARGET_APP} PRIVATE ${TARGET_APP}_LIBRARY)

View File

@ -52,6 +52,11 @@ bool CsvParser::isCsvCompatible(const rapidcsv::Document& doc) {
qInfo() << "Checking CSV document for compatiblity..."; qInfo() << "Checking CSV document for compatiblity...";
const std::vector<std::string> columnNames = doc.GetColumnNames(); const std::vector<std::string> columnNames = doc.GetColumnNames();
for (const QString& headerName : GET_HEADER_NAMES()) { for (const QString& headerName : GET_HEADER_NAMES()) {
if (OPTIONAL_CSV_HEADERS.contains(headerName)) {
/// no need to have a column for the optional values
continue;
}
/// these column must be found in CSV document
bool isHeaderNameFound = false; bool isHeaderNameFound = false;
if (std::find(columnNames.begin(), columnNames.end(), headerName) != columnNames.end()) { if (std::find(columnNames.begin(), columnNames.end(), headerName) != columnNames.end()) {
qDebug() << QString("Header found in column names: %1").arg(headerName); qDebug() << QString("Header found in column names: %1").arg(headerName);
@ -86,14 +91,13 @@ QHash<QString, std::vector<std::string>> CsvParser::extractColumnValues(
const rapidcsv::Document& doc) { const rapidcsv::Document& doc) {
QHash<QString, std::vector<std::string>> columnValueMap; QHash<QString, std::vector<std::string>> columnValueMap;
for (const QString& columnName : headerNames) { for (const QString& columnName : headerNames) {
// TODO add support for optional columns if (OPTIONAL_CSV_HEADERS.contains(columnName)) {
// if (optionalCsvHeaderNames.contains(columnName)) { const std::vector<std::string> columnNames = doc.GetColumnNames();
// const std::vector<std::string> columnNames = doc.GetColumnNames(); int columnIdx = doc.GetColumnIdx(columnName.toStdString());
// int columnIdx = doc.GetColumnIdx(columnName.toStdString()); if (columnIdx == -1) {
// if (columnIdx == -1) { continue;
// continue; }
// } }
// }
const std::vector<std::string> columnValues = const std::vector<std::string> columnValues =
doc.GetColumn<std::string>(columnName.toStdString()); doc.GetColumn<std::string>(columnName.toStdString());
columnValueMap.insert(columnName, columnValues); columnValueMap.insert(columnName, columnValues);

View File

@ -86,14 +86,6 @@ QJsonArray JsonParser::extractItemArray(const QJsonDocument& doc, const QString&
ModelItemValues JsonParser::jsonObjectToItemValues(const QJsonObject& itemJsonObject) { ModelItemValues JsonParser::jsonObjectToItemValues(const QJsonObject& itemJsonObject) {
ModelItemValues values; ModelItemValues values;
const UserRoles idRole = IdRole;
const QString idRoleName = ROLE_NAMES.value(idRole);
// QVariant idValue = data(idRole);
if (itemJsonObject.contains(idRoleName)) {
std::pair<int, QVariant> keyValuePair = getKeyValuePair(itemJsonObject, idRole);
values.insert(keyValuePair.first, keyValuePair.second);
}
QListIterator<UserRoles> i(USER_FACING_ROLES); QListIterator<UserRoles> i(USER_FACING_ROLES);
while (i.hasNext()) { while (i.hasNext()) {
const UserRoles role = i.next(); const UserRoles role = i.next();

View File

@ -26,11 +26,6 @@ EditItemCommand::EditItemCommand(TableModel* model,
.arg(roleName) .arg(roleName)
.arg(index.data(DEFAULT_ROLE).toString()) .arg(index.data(DEFAULT_ROLE).toString())
.arg(value.toString()); .arg(value.toString());
} else if (role == IdRole) {
commandText = QString("Setting '%1' of item '%2' to '%3'")
.arg(roleName)
.arg(index.data(DEFAULT_ROLE).toString())
.arg(value.toString());
} else { } else {
qWarning() << "Role didn't match! Using a generic command text..."; qWarning() << "Role didn't match! Using a generic command text...";
commandText = QString("Edit item '%1'").arg(index.data(DEFAULT_ROLE).toString()); commandText = QString("Edit item '%1'").arg(index.data(DEFAULT_ROLE).toString());

View File

@ -24,7 +24,7 @@ QItemSelection GeneralSortFilterModel::findItems(const QString& text) const {
} }
QString GeneralSortFilterModel::getUuid(const QModelIndex& itemIndex) const { QString GeneralSortFilterModel::getUuid(const QModelIndex& itemIndex) const {
return data(itemIndex, IdRole).toString(); return data(itemIndex, OnlineIdRole).toString();
} }
QByteArray GeneralSortFilterModel::jsonDataForServer(const QModelIndex& proxyIndex) { QByteArray GeneralSortFilterModel::jsonDataForServer(const QModelIndex& proxyIndex) {

View File

@ -0,0 +1,151 @@
#ifndef METADATA_H
#define METADATA_H
#include <QDebug>
#include <QHash>
#include <QList>
#include <QString>
// TODO add namespace
/// model data
enum UserRoles {
LastNameRole = Qt::UserRole + 1,
FirstNameRole,
MembershipNumberRole,
Bidding1Role,
Bidding2Role,
Bidding3Role,
DepotWish1Role,
DepotWish2Role,
ShareAmountRole,
MailRole,
ShareTypeRole,
BiddingTypeRole,
OnlineIdRole,
AccessCodeRole,
// read-only (calculated)
FullNameRole,
FactoredBiddingRole,
ToStringRole,
JsonObjectRole
};
static UserRoles DEFAULT_ROLE = FullNameRole;
// TODO ?rename USER_FACING_ROLES -> MAIN_ROLES ?
static QList<UserRoles> USER_FACING_ROLES = {
LastNameRole, FirstNameRole, MembershipNumberRole, Bidding1Role, Bidding2Role,
Bidding3Role, DepotWish1Role, DepotWish2Role, ShareAmountRole, MailRole,
ShareTypeRole, BiddingTypeRole, OnlineIdRole, AccessCodeRole};
static QHash<int, QByteArray> ROLE_NAMES = {{MembershipNumberRole, "Mitglieds-nr."},
{LastNameRole, "Name"},
{FirstNameRole, "Vorname"},
{Bidding1Role, "Gebot 1"},
{Bidding2Role, "Gebot 2"},
{Bidding3Role, "Gebot 3"},
{DepotWish1Role, "Wunsch 1"},
{DepotWish2Role, "Wunsch 2"},
{ShareAmountRole, "Anzahl"},
{MailRole, "Mail"},
{ShareTypeRole, "Art"},
{BiddingTypeRole, "Bietart"},
{OnlineIdRole, "Online ID"},
{AccessCodeRole, "Access Code"},
{FactoredBiddingRole, "factoredBidding"}};
static const QList<QString> SHARE_TYPES = {"bezahlt", "teils/teils", "erarbeitet", ""};
static const QList<QString> BIDDING_TYPES = {"paper", "digital", "online", "offline", ""};
static QList<UserRoles> STRING_ROLES = {LastNameRole, FirstNameRole, DepotWish1Role,
DepotWish2Role, MailRole, ShareTypeRole,
BiddingTypeRole, OnlineIdRole, AccessCodeRole};
static QList<UserRoles> INT_ROLES = {
MembershipNumberRole,
Bidding1Role,
Bidding2Role,
Bidding3Role,
};
static QList<UserRoles> DOUBLE_ROLES = {
ShareAmountRole,
};
static const QList<UserRoles> TYPE_ROLES = {ShareTypeRole, BiddingTypeRole};
static const QList<UserRoles> SHARE_TYPE_ROLES = {ShareTypeRole};
static const QList<UserRoles> BIDDING_TYPE_ROLES = {BiddingTypeRole};
static const QStringList OPTIONAL_CSV_HEADERS = {"Bietart", "Online ID", "Access Code"};
/// JSON keys
static const QString ITEMS_KEY_STRING = "items";
static const QString ITEM_KEY_STRING = "item";
/// file naming
static const QString ITEMS_FILE_NAME = ITEMS_KEY_STRING + ".json";
/// functions
static UserRoles GET_ROLE_FOR_COLUMN(const int column) {
switch (column) {
case 0:
return MembershipNumberRole;
break;
case 1:
return LastNameRole;
break;
case 2:
return FirstNameRole;
break;
case 3:
return ShareTypeRole;
break;
case 4:
return ShareAmountRole;
break;
case 5:
return BiddingTypeRole;
break;
case 6:
return Bidding1Role;
break;
case 7:
return Bidding2Role;
break;
case 8:
return Bidding3Role;
break;
case 9:
return DepotWish1Role;
break;
case 10:
return DepotWish2Role;
break;
case 11:
return MailRole;
break;
case 12:
return OnlineIdRole;
break;
case 13:
return AccessCodeRole;
break;
default:
qWarning() << QString("No role found for column %1! Returning 'FullNameRole'...").arg(column);
return FullNameRole;
break;
}
}
static QList<QString> GET_HEADER_NAMES() {
QList<QString> result;
for (const UserRoles& role : USER_FACING_ROLES) {
const QString headerName = ROLE_NAMES.value(role);
result.append(headerName);
}
return result;
}
static QString GET_HEADER_FOR_COLUMN(const int column) {
const UserRoles role = GET_ROLE_FOR_COLUMN(column);
const QString headerName = ROLE_NAMES.value(role);
return headerName;
}
#endif // METADATA_H

View File

@ -8,8 +8,28 @@
ModelItem::ModelItem(const ModelItemValues values) ModelItem::ModelItem(const ModelItemValues values)
: m_values(values) {} : m_values(values) {}
QVariant ModelItem::data(int role) const { return m_values.value(role); } QVariant ModelItem::data(int role) const {
switch (role) {
case FullNameRole:
return fullName();
break;
case ToStringRole:
return toString();
break;
case JsonObjectRole:
return toJsonObject();
break;
case MembershipNumberRole:
case ShareAmountRole:
case Bidding1Role:
case Bidding2Role:
case Bidding3Role:
return getValueButReplaceZeroValueWithEmptyString(role);
break;
default:
return m_values.value(role);
}
}
bool ModelItem::setData(const QVariant& value, int role) { bool ModelItem::setData(const QVariant& value, int role) {
bool valueChanged = false; bool valueChanged = false;
if (m_values.contains(role)) { if (m_values.contains(role)) {
@ -44,6 +64,10 @@ bool ModelItem::setItemData(const QMap<int, QVariant>& changedValues) {
return valueChanged; return valueChanged;
} }
QString ModelItem::fullName() const {
return QString("%1 %2").arg(data(FirstNameRole).toString(), data(LastNameRole).toString());
}
QString ModelItem::toString() const { QString ModelItem::toString() const {
QString result; QString result;
@ -52,15 +76,7 @@ QString ModelItem::toString() const {
const UserRoles role = i.next(); const UserRoles role = i.next();
const QString roleName = ROLE_NAMES.value(role); const QString roleName = ROLE_NAMES.value(role);
const QVariant value = data(role); const QVariant value = data(role);
// result.append(value.toString()); result.append(QString("%1: %2\n").arg(roleName, value.toString()));
result.append(QString("%1: %2\n").arg(roleName, data(role).toString()));
}
const UserRoles idRole = IdRole;
QVariant idValue = data(idRole);
if (!idValue.isNull()) {
const QString idRoleName = ROLE_NAMES.value(idRole);
result.append(QString("%1: %2\n").arg(idRoleName, idValue.toString()));
} }
return result; return result;
} }
@ -68,12 +84,6 @@ QString ModelItem::toString() const {
QJsonObject ModelItem::toJsonObject() const { QJsonObject ModelItem::toJsonObject() const {
QJsonObject itemObject; QJsonObject itemObject;
// TODO add UUID and dates (entry, modification, end) // TODO add UUID and dates (entry, modification, end)
const UserRoles idRole = IdRole;
QVariant idValue = data(idRole);
if (!idValue.isNull()) {
const QString idRoleName = ROLE_NAMES.value(idRole);
itemObject.insert(idRoleName, idValue.toString());
}
QListIterator<UserRoles> i(USER_FACING_ROLES); QListIterator<UserRoles> i(USER_FACING_ROLES);
while (i.hasNext()) { while (i.hasNext()) {
@ -92,3 +102,12 @@ QJsonObject ModelItem::toJsonObject() const {
} }
return itemObject; return itemObject;
} }
QVariant ModelItem::getValueButReplaceZeroValueWithEmptyString(const int role) const {
QVariant localValue = m_values.value(role, QVariant());
if (localValue == 0) {
return QVariant();
} else {
return localValue;
}
}

View File

@ -14,11 +14,14 @@ class ModelItem {
// TODO change return value to list of changed roles // TODO change return value to list of changed roles
bool setItemData(const QMap<int, QVariant>& changedValues); bool setItemData(const QMap<int, QVariant>& changedValues);
QString fullName() const;
QString toString() const; QString toString() const;
QJsonObject toJsonObject() const; QJsonObject toJsonObject() const;
private: private:
ModelItemValues m_values; ModelItemValues m_values;
QVariant getValueButReplaceZeroValueWithEmptyString(const int role) const;
}; };
#endif // MODELITEM_H #endif // MODELITEM_H

View File

@ -21,11 +21,20 @@ QByteArray TableModel::generateExampleItems() {
QJsonObject itemObject; QJsonObject itemObject;
// itemObject.insert("uuid", m_uuid.toString()); // itemObject.insert("uuid", m_uuid.toString());
// itemObject.insert("entryDateUTC", m_entryDateUTC.toString(Qt::ISODate)); // itemObject.insert("entryDateUTC", m_entryDateUTC.toString(Qt::ISODate));
itemObject.insert(ROLE_NAMES.value(NameRole), QString("Item %1").arg(row)); itemObject.insert(ROLE_NAMES.value(MembershipNumberRole), row);
itemObject.insert(ROLE_NAMES.value(DescriptionRole), QString("This is item %1").arg(row)); itemObject.insert(ROLE_NAMES.value(LastNameRole), QString("Nachname%1").arg(row));
itemObject.insert(ROLE_NAMES.value(InfoRole), QString("Info of item %1").arg(row)); itemObject.insert(ROLE_NAMES.value(FirstNameRole), QString("Vorname%1").arg(row));
itemObject.insert(ROLE_NAMES.value(AmountRole), row); itemObject.insert(ROLE_NAMES.value(Bidding1Role), 100 + row);
itemObject.insert(ROLE_NAMES.value(FactorRole), row * 1.1); itemObject.insert(ROLE_NAMES.value(DepotWish1Role), QString("Depot X%1").arg(row));
itemObject.insert(ROLE_NAMES.value(DepotWish2Role), QString("Depot Y%1").arg(row));
itemObject.insert(ROLE_NAMES.value(MailRole),
QString("%1@%2.com")
.arg(itemObject.value(ROLE_NAMES.value(FirstNameRole)).toString(),
itemObject.value(ROLE_NAMES.value(LastNameRole)).toString()));
itemObject.insert(ROLE_NAMES.value(ShareAmountRole), 1);
itemObject.insert(ROLE_NAMES.value(ShareTypeRole), SHARE_TYPES.at(row % 3));
itemObject.insert(ROLE_NAMES.value(BiddingTypeRole), BIDDING_TYPES.at(row % 4));
// itemObject.insert(ROLE_NAMES.value(FactorRole), row * 1.1);
array.append(itemObject); array.append(itemObject);
} }
@ -73,22 +82,32 @@ QVariant TableModel::data(const QModelIndex& index, int role) const {
return QVariant(); return QVariant();
} }
int roleForColumn = GET_ROLE_FOR_COLUMN(column); const int roleForColumn = GET_ROLE_FOR_COLUMN(column);
switch (role) { switch (role) {
case Qt::DisplayRole: case Qt::DisplayRole:
case Qt::EditRole: case Qt::EditRole:
return m_items.at(row)->data(roleForColumn); return m_items.at(row)->data(roleForColumn);
case NameRole: case Qt::ToolTipRole:
case DescriptionRole: return m_items.at(index.row())->data(ToStringRole);
case InfoRole: break;
case AmountRole: case MembershipNumberRole:
case FactorRole: case LastNameRole:
case IdRole: case FirstNameRole:
return m_items.at(row)->data(role); case FullNameRole:
case Bidding1Role:
case Bidding2Role:
case Bidding3Role:
case DepotWish1Role:
case DepotWish2Role:
case ShareAmountRole:
case MailRole:
case ShareTypeRole:
case BiddingTypeRole:
case OnlineIdRole:
case AccessCodeRole:
case ToStringRole: case ToStringRole:
return m_items.at(row)->toString(); case JsonObjectRole:
case ToJsonRole: return m_items.at(row)->data(role);
return m_items.at(row)->toJsonObject();
} }
return QVariant(); return QVariant();
@ -174,7 +193,7 @@ QList<QStringList> TableModel::getItemsAsStringLists() const {
// TODO use item selection as parameter to wrap multiple items into JSON data structure // TODO use item selection as parameter to wrap multiple items into JSON data structure
QByteArray TableModel::jsonDataForServer(const QModelIndex& currentIndex) const { QByteArray TableModel::jsonDataForServer(const QModelIndex& currentIndex) const {
const QJsonObject itemObject = data(currentIndex, ToJsonRole).toJsonObject(); const QJsonObject itemObject = data(currentIndex, JsonObjectRole).toJsonObject();
QJsonObject rootObject; QJsonObject rootObject;
rootObject.insert(ITEM_KEY_STRING, itemObject); rootObject.insert(ITEM_KEY_STRING, itemObject);
const QJsonDocument jsonDoc(rootObject); const QJsonDocument jsonDoc(rootObject);
@ -350,8 +369,9 @@ QModelIndex TableModel::searchItemIndex(const ModelItemValues givenItemValues) c
bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex, bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex,
const ModelItemValues givenItemValues) const { const ModelItemValues givenItemValues) const {
/// do both have a UUID? /// do both have a UUID?
QVariant idOfItem = data(itemIndex, IdRole); const UserRoles idRole = OnlineIdRole;
QVariant given = givenItemValues.value(IdRole); QVariant idOfItem = data(itemIndex, idRole);
QVariant given = givenItemValues.value(idRole);
if (idOfItem.isValid() && given.isValid()) { if (idOfItem.isValid() && given.isValid()) {
/// are the UUIDs the same? /// are the UUIDs the same?
if (idOfItem.toString() == given.toString()) { if (idOfItem.toString() == given.toString()) {

View File

@ -1,77 +0,0 @@
#ifndef METADATA_H
#define METADATA_H
#include <QDebug>
#include <QHash>
#include <QList>
#include <QString>
// TODO add namespace
/// model data
enum UserRoles {
NameRole = Qt::UserRole + 1,
DescriptionRole,
InfoRole,
AmountRole,
FactorRole,
/// Non user facing
IdRole,
/// read only roles
ToStringRole,
ToJsonRole
};
static UserRoles DEFAULT_ROLE = NameRole;
// TODO ?rename USER_FACING_ROLES -> MAIN_ROLES ?
static QList<UserRoles> USER_FACING_ROLES = {NameRole, DescriptionRole, InfoRole, AmountRole,
FactorRole};
static QHash<int, QByteArray> ROLE_NAMES = {
{NameRole, "name"}, {DescriptionRole, "description"}, {InfoRole, "info"},
{AmountRole, "amount"}, {FactorRole, "factor"}, {ToStringRole, "ToString"},
{IdRole, "id"}};
static QList<UserRoles> STRING_ROLES = {NameRole, DescriptionRole, InfoRole, IdRole};
static QList<UserRoles> INT_ROLES = {AmountRole};
static QList<UserRoles> DOUBLE_ROLES = {FactorRole};
/// JSON keys
static const QString ITEMS_KEY_STRING = "items";
static const QString ITEM_KEY_STRING = "item";
/// file naming
static const QString ITEMS_FILE_NAME = ITEMS_KEY_STRING + ".json";
/// functions
static int GET_ROLE_FOR_COLUMN(const int column) {
switch (column) {
case 0:
return NameRole;
break;
case 1:
return DescriptionRole;
break;
case 2:
return InfoRole;
break;
case 3:
return AmountRole;
break;
case 4:
return FactorRole;
break;
default:
qWarning() << QString("No role found for column %1! Returning 'NameRole'...").arg(column);
return NameRole;
break;
}
}
static QList<QString> GET_HEADER_NAMES() {
QList<QString> result;
for (const UserRoles& role : USER_FACING_ROLES) {
const QString headerName = ROLE_NAMES.value(role);
result.append(headerName);
}
return result;
}
#endif // METADATA_H

View File

@ -24,7 +24,7 @@ target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/include)
target_link_libraries(${TARGET_APP} target_link_libraries(${TARGET_APP}
PRIVATE PRIVATE
GTest::GTest GTest::GTest
GenericCore) BeetRoundCore)
target_link_libraries(${TARGET_APP} PUBLIC Qt${QT_VERSION_MAJOR}::Core) target_link_libraries(${TARGET_APP} PUBLIC Qt${QT_VERSION_MAJOR}::Core)
add_test(core_gtests ${TARGET_APP}) add_test(core_gtests ${TARGET_APP})

View File

@ -2,7 +2,7 @@
#include <QString> #include <QString>
#include "../../libs/GenericCore/genericcore.h" #include "../../libs/BeetRoundCore/genericcore.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
inline void PrintTo(const QString& qString, ::std::ostream* os) { *os << qUtf8Printable(qString); } inline void PrintTo(const QString& qString, ::std::ostream* os) { *os << qUtf8Printable(qString); }
@ -10,7 +10,7 @@ QT_END_NAMESPACE
TEST(CoreTests, TestEqualString) { TEST(CoreTests, TestEqualString) {
const QString coreName("GenericCore"); const QString coreName("GenericCore");
const QString coreVersion("0.2.0"); const QString coreVersion("0.1.0");
const auto expected = QString("%1 (Version %2)").arg(coreName).arg(coreVersion); const auto expected = QString("%1 (Version %2)").arg(coreName).arg(coreVersion);
auto core = std::make_unique<GenericCore>(); auto core = std::make_unique<GenericCore>();
const auto actual = core->toString(); const auto actual = core->toString();