Compare commits
49 Commits
3146eceec2
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| a6847f2661 | |||
| 3431e281c3 | |||
| d800ecfed7 | |||
| 6b383b8f55 | |||
| 85afc9b329 | |||
| f3e2dbf309 | |||
| 2021830239 | |||
| 74470ba605 | |||
| 6cd205506f | |||
| 064da850c4 | |||
| 06589e5a09 | |||
| d381e1ab8c | |||
| 440333b589 | |||
| 21b8de96d8 | |||
| 4519a2de3f | |||
| 369addac44 | |||
| f34e1521c4 | |||
| 7d31ac8806 | |||
| faf01d6e15 | |||
| 7a8859843e | |||
| f1a436ead0 | |||
| 25bfc196a1 | |||
| bfb8e43c34 | |||
| c38bacc387 | |||
| 546d6ea3ef | |||
| 06e96916ed | |||
| 1e64dda701 | |||
| f8201ead71 | |||
| f200fff99e | |||
| dac9ac46f2 | |||
| b28a35280c | |||
| 5afdbd6dd9 | |||
| 29f913c532 | |||
| bb5e894557 | |||
| 6e51aee3a5 | |||
| cfd3031cf9 | |||
| ba4f95eb2d | |||
| a4d2815947 | |||
| bea89a2a16 | |||
| f148ef9cba | |||
| bdc8075324 | |||
| e157d4399d | |||
| 572f0f266e | |||
| 40a0815501 | |||
| c6d6b18ab3 | |||
| 2fcd69df5f | |||
| 455c6ef3bd | |||
| 22adf36aa9 | |||
| 7edd6b9aa0 |
31
CHANGELOG.md
31
CHANGELOG.md
@ -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.
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
20
README.md
20
README.md
@ -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
|
|
||||||
- ...
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ set(CMAKE_CXX_STANDARD 17)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
|
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools Charts)
|
||||||
|
|
||||||
set(TS_FILES ${TARGET_APP}_en_US.ts)
|
set(TS_FILES ${TARGET_APP}_en_US.ts)
|
||||||
|
|
||||||
@ -34,6 +34,12 @@ 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
|
||||||
|
widgets/biddingroundcontrol.h widgets/biddingroundcontrol.cpp
|
||||||
|
widgets/summarywidget.h widgets/summarywidget.cpp
|
||||||
|
widgets/biddingroundstatuswidget.h widgets/biddingroundstatuswidget.cpp
|
||||||
|
widgets/biddingroundprogresslayout.h widgets/biddingroundprogresslayout.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
|
||||||
@ -60,10 +66,10 @@ endif()
|
|||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
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 Qt${QT_VERSION_MAJOR}::Charts)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
EditItemDialog::EditItemDialog(QTableView* tableView, QWidget* parent)
|
EditItemDialog::EditItemDialog(QTableView* tableView, QWidget* parent)
|
||||||
: AbstractDialog(QDialogButtonBox::Ok, parent)
|
: AbstractDialog(QDialogButtonBox::Ok, parent)
|
||||||
, m_tableView(tableView)
|
, m_tableView(tableView) {}
|
||||||
, m_qrCodeDisplay(new QLabel("QR Code")) {}
|
|
||||||
|
|
||||||
void EditItemDialog::createContent() {
|
void EditItemDialog::createContent() {
|
||||||
if (m_contentContainer) {
|
if (m_contentContainer) {
|
||||||
@ -26,11 +25,13 @@ void EditItemDialog::createContent() {
|
|||||||
m_detailMapper->setModelMappings(m_tableView);
|
m_detailMapper->setModelMappings(m_tableView);
|
||||||
innerLayout->addWidget(m_detailMapper);
|
innerLayout->addWidget(m_detailMapper);
|
||||||
|
|
||||||
updateQRCode();
|
|
||||||
connect(m_detailMapper, &ItemDetailMapper::contentChanged, this, &EditItemDialog::updateQRCode);
|
|
||||||
innerLayout->addWidget(m_qrCodeDisplay);
|
|
||||||
|
|
||||||
m_outerLayout->insertWidget(0, m_contentContainer);
|
m_outerLayout->insertWidget(0, m_contentContainer);
|
||||||
|
|
||||||
|
/// online user stuff
|
||||||
|
connect(m_detailMapper, &ItemDetailMapper::createOnlineAccountTriggered, this,
|
||||||
|
&EditItemDialog::createOnlineAccountTriggered);
|
||||||
|
connect(m_detailMapper, &ItemDetailMapper::sendInviteMailTriggered, this,
|
||||||
|
&EditItemDialog::sendInviteMailTriggered);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditItemDialog::accept() {
|
void EditItemDialog::accept() {
|
||||||
@ -42,16 +43,3 @@ void EditItemDialog::reject() {
|
|||||||
m_detailMapper->revert();
|
m_detailMapper->revert();
|
||||||
QDialog::reject();
|
QDialog::reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditItemDialog::updateQRCode(const QString text) {
|
|
||||||
QImage unscaledImage;
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
unscaledImage = QImage("://no-picture-taking.png");
|
|
||||||
} else {
|
|
||||||
unscaledImage = m_generator.generateQr(text);
|
|
||||||
}
|
|
||||||
QImage image = unscaledImage.scaled(250, 250);
|
|
||||||
|
|
||||||
m_qrCodeDisplay->setPixmap(QPixmap::fromImage(image));
|
|
||||||
m_qrCodeDisplay->setToolTip(text);
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
#ifndef EDITITEMDIALOG_H
|
#ifndef EDITITEMDIALOG_H
|
||||||
#define EDITITEMDIALOG_H
|
#define EDITITEMDIALOG_H
|
||||||
|
|
||||||
#include "QrCodeGenerator.h"
|
|
||||||
#include "abstractdialog.h"
|
#include "abstractdialog.h"
|
||||||
|
|
||||||
class QDoubleSpinBox;
|
class QDoubleSpinBox;
|
||||||
@ -20,34 +19,17 @@ class EditItemDialog : public AbstractDialog {
|
|||||||
/// AbstractDialog interface
|
/// AbstractDialog interface
|
||||||
void createContent() override;
|
void createContent() override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void createOnlineAccountTriggered(const QString& mailAddress);
|
||||||
|
void sendInviteMailTriggered(const QString& mailAddress);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void accept() override;
|
void accept() override;
|
||||||
void reject() override;
|
void reject() override;
|
||||||
|
|
||||||
private slots:
|
|
||||||
void updateQRCode(const QString text = "");
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTableView* m_tableView = nullptr;
|
QTableView* m_tableView = nullptr;
|
||||||
ItemDetailMapper* m_detailMapper;
|
ItemDetailMapper* m_detailMapper;
|
||||||
|
|
||||||
QLabel* m_nameLabel = nullptr;
|
|
||||||
QLineEdit* m_nameEdit = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_descriptionLabel = nullptr;
|
|
||||||
QLineEdit* m_descriptionEdit = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_infoLabel = nullptr;
|
|
||||||
QLineEdit* m_infoEdit = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_amountLabel = nullptr;
|
|
||||||
QSpinBox* m_amountBox = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_factorLabel = nullptr;
|
|
||||||
QDoubleSpinBox* m_factorBox = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_qrCodeDisplay = nullptr;
|
|
||||||
QrCodeGenerator m_generator;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // EDITITEMDIALOG_H
|
#endif // EDITITEMDIALOG_H
|
||||||
149
UIs/BeetRoundWidgets/dialogs/newitemdialog.cpp
Normal file
149
UIs/BeetRoundWidgets/dialogs/newitemdialog.cpp
Normal 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(4);
|
||||||
|
m_bidding1SpinBox->setValue(0);
|
||||||
|
m_bidding2SpinBox->setValue(0);
|
||||||
|
m_bidding3SpinBox->setValue(0);
|
||||||
|
m_depotWish1Edit->setText("");
|
||||||
|
m_depotWish2Edit->setText("");
|
||||||
|
m_mailEdit->setText("");
|
||||||
|
}
|
||||||
68
UIs/BeetRoundWidgets/dialogs/newitemdialog.h
Normal file
68
UIs/BeetRoundWidgets/dialogs/newitemdialog.h
Normal 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
|
||||||
@ -27,12 +27,12 @@ void SettingsDialog::createContent() {
|
|||||||
serverLayout->addWidget(m_urlEdit, 0, 1);
|
serverLayout->addWidget(m_urlEdit, 0, 1);
|
||||||
QLabel* emailLabel = new QLabel("Email:");
|
QLabel* emailLabel = new QLabel("Email:");
|
||||||
m_emailEdit = new QLineEdit();
|
m_emailEdit = new QLineEdit();
|
||||||
m_emailEdit->setEnabled(false);
|
// m_emailEdit->setEnabled(false);
|
||||||
serverLayout->addWidget(emailLabel, 1, 0);
|
serverLayout->addWidget(emailLabel, 1, 0);
|
||||||
serverLayout->addWidget(m_emailEdit, 1, 1);
|
serverLayout->addWidget(m_emailEdit, 1, 1);
|
||||||
QLabel* passwordLabel = new QLabel("Password:");
|
QLabel* passwordLabel = new QLabel("Password:");
|
||||||
m_passwordEdit = new QLineEdit();
|
m_passwordEdit = new QLineEdit();
|
||||||
m_passwordEdit->setEnabled(false);
|
// m_passwordEdit->setEnabled(false);
|
||||||
m_passwordEdit->setEchoMode(QLineEdit::Password);
|
m_passwordEdit->setEchoMode(QLineEdit::Password);
|
||||||
serverLayout->addWidget(passwordLabel, 2, 0);
|
serverLayout->addWidget(passwordLabel, 2, 0);
|
||||||
serverLayout->addWidget(m_passwordEdit, 2, 1);
|
serverLayout->addWidget(m_passwordEdit, 2, 1);
|
||||||
@ -15,7 +15,14 @@
|
|||||||
#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/biddingroundcontrol.h"
|
||||||
|
#include "widgets/comboboxdelegate.h"
|
||||||
|
#include "widgets/spinboxdelegate.h"
|
||||||
|
#include "widgets/summarywidget.h"
|
||||||
|
|
||||||
|
static int intColumnWidth = 30;
|
||||||
|
|
||||||
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 +53,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();
|
||||||
@ -69,6 +72,12 @@ MainWindow::MainWindow(QWidget* parent)
|
|||||||
|
|
||||||
onSelectionChanged(QItemSelection(), QItemSelection());
|
onSelectionChanged(QItemSelection(), QItemSelection());
|
||||||
onCurrentChanged(QModelIndex(), QModelIndex());
|
onCurrentChanged(QModelIndex(), QModelIndex());
|
||||||
|
|
||||||
|
setupEventTab();
|
||||||
|
|
||||||
|
// #ifndef QT_DEBUG
|
||||||
|
initServerConnection();
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow() { delete ui; }
|
MainWindow::~MainWindow() { delete ui; }
|
||||||
@ -147,8 +156,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>.")
|
||||||
@ -172,11 +184,6 @@ void MainWindow::on_actionCheck_for_update_triggered() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_pushButton_clicked() {
|
|
||||||
const QString prefix("Backend provided by: ");
|
|
||||||
ui->label->setText(prefix + m_core->toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::openNewItemDialog() {
|
void MainWindow::openNewItemDialog() {
|
||||||
showStatusMessage(tr("Invoked 'Edit|New Item'"));
|
showStatusMessage(tr("Invoked 'Edit|New Item'"));
|
||||||
m_newItemDialog->show();
|
m_newItemDialog->show();
|
||||||
@ -296,24 +303,9 @@ void MainWindow::findItems() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::fetchItems() {
|
void MainWindow::fetchCurrentBiddings() {
|
||||||
showStatusMessage(tr("Invoked 'Server|Fetch items'"));
|
showStatusMessage(tr("Invoked 'Server|Fetch current biddings'"));
|
||||||
emit m_core->fetchItemsFromServer();
|
emit m_core->sendGetRequest(GetBiddingsOfHighestRound);
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::postItems() {
|
|
||||||
showStatusMessage(tr("Invoked 'Server|Post items'"));
|
|
||||||
const QModelIndex currentIndex = ui->tableView->currentIndex();
|
|
||||||
const QByteArray jsonData = m_proxyModel->jsonDataForServer(currentIndex);
|
|
||||||
emit m_core->postItemToServer(jsonData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::deleteItem() {
|
|
||||||
showStatusMessage(tr("Invoked 'Server|Delete items'"));
|
|
||||||
const QModelIndex currentIndex = ui->tableView->currentIndex();
|
|
||||||
// const QByteArray jsonData = m_proxyModel->jsonDataForServer(currentIndex);
|
|
||||||
const QString currentId = m_proxyModel->getUuid(currentIndex);
|
|
||||||
emit m_core->deleteItemFromServer(currentId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::execSettingsDialog() {
|
void MainWindow::execSettingsDialog() {
|
||||||
@ -340,6 +332,32 @@ 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);
|
||||||
|
ui->tableView->setColumnWidth(0, intColumnWidth);
|
||||||
|
|
||||||
|
m_modelSummary = m_core->getModelSummary();
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
@ -494,25 +512,12 @@ void MainWindow::createEditActions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::createServerActions() {
|
void MainWindow::createServerActions() {
|
||||||
m_fetchItemsAct = make_unique<QAction>(tr("&Fetch item(s)"), this);
|
m_fetchCurrentBiddingsAct = make_unique<QAction>(tr("&Fetch biddings"), this);
|
||||||
m_fetchItemsAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Down));
|
m_fetchCurrentBiddingsAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Down));
|
||||||
m_fetchItemsAct->setStatusTip(tr("Fetches all item on configured server"));
|
m_fetchCurrentBiddingsAct->setStatusTip(tr("Fetches all biddings of the current round"));
|
||||||
connect(m_fetchItemsAct.get(), &QAction::triggered, this, &MainWindow::fetchItems);
|
connect(m_fetchCurrentBiddingsAct.get(), &QAction::triggered, this,
|
||||||
ui->menu_Server->addAction(m_fetchItemsAct.get());
|
&MainWindow::fetchCurrentBiddings);
|
||||||
|
ui->menu_Server->addAction(m_fetchCurrentBiddingsAct.get());
|
||||||
m_postItemsAct = make_unique<QAction>(tr("&Post item(s)"), this);
|
|
||||||
m_postItemsAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Up));
|
|
||||||
// m_postItemsAct->setStatusTip(tr("Posts the selected items on configured server"));
|
|
||||||
m_postItemsAct->setStatusTip(tr("Posts the current item on configured server"));
|
|
||||||
connect(m_postItemsAct.get(), &QAction::triggered, this, &MainWindow::postItems);
|
|
||||||
ui->menu_Server->addAction(m_postItemsAct.get());
|
|
||||||
|
|
||||||
m_deleteItemsAct = make_unique<QAction>(tr("&Delete item"), this);
|
|
||||||
m_deleteItemsAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Backspace));
|
|
||||||
// m_deleteItemsAct->setStatusTip(tr("Deletes the selected items on configured server"));
|
|
||||||
m_deleteItemsAct->setStatusTip(tr("Deletes the current item on configured server"));
|
|
||||||
connect(m_deleteItemsAct.get(), &QAction::triggered, this, &MainWindow::deleteItem);
|
|
||||||
ui->menu_Server->addAction(m_deleteItemsAct.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::createToolsActions() {
|
void MainWindow::createToolsActions() {
|
||||||
@ -541,3 +546,31 @@ void MainWindow::createGuiDialogs() {
|
|||||||
m_editItemDialog = make_unique<EditItemDialog>(ui->tableView, this);
|
m_editItemDialog = make_unique<EditItemDialog>(ui->tableView, this);
|
||||||
m_editItemDialog->createContent();
|
m_editItemDialog->createContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::setupEventTab() {
|
||||||
|
QWidget* containerWidget = new QWidget();
|
||||||
|
QVBoxLayout* containerLayout = new QVBoxLayout(containerWidget);
|
||||||
|
m_biddingRoundControl = make_unique<BiddingRoundControl>();
|
||||||
|
containerLayout->addWidget(m_biddingRoundControl.get());
|
||||||
|
|
||||||
|
/// requests
|
||||||
|
connect(m_biddingRoundControl.get(), &BiddingRoundControl::sendGetRequest, m_core.get(),
|
||||||
|
&GenericCore::sendGetRequest);
|
||||||
|
|
||||||
|
/// responses
|
||||||
|
connect(m_core.get(), &GenericCore::currentBiddingRoundChanged, m_biddingRoundControl.get(),
|
||||||
|
&BiddingRoundControl::onCurrentBiddingRoundChanged);
|
||||||
|
|
||||||
|
SummaryWidget* summaryWidget = new SummaryWidget(m_modelSummary, this);
|
||||||
|
containerLayout->addWidget(summaryWidget);
|
||||||
|
|
||||||
|
ui->tabWidget->insertTab(0, containerWidget, "Event (&1)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::initServerConnection() {
|
||||||
|
connect(m_editItemDialog.get(), &EditItemDialog::createOnlineAccountTriggered, m_core.get(),
|
||||||
|
&GenericCore::onCreateOnlineAccountTriggered);
|
||||||
|
connect(m_editItemDialog.get(), &EditItemDialog::sendInviteMailTriggered, m_core.get(),
|
||||||
|
&GenericCore::onSendInviteMailTriggered);
|
||||||
|
emit m_core->loginAndStoreAuthToken();
|
||||||
|
}
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
@ -17,8 +17,10 @@ QT_END_NAMESPACE
|
|||||||
class GenericCore;
|
class GenericCore;
|
||||||
class TableModel;
|
class TableModel;
|
||||||
class GeneralSortFilterModel;
|
class GeneralSortFilterModel;
|
||||||
|
class ModelSummary;
|
||||||
class NewItemDialog;
|
class NewItemDialog;
|
||||||
class EditItemDialog;
|
class EditItemDialog;
|
||||||
|
class BiddingRoundControl;
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@ -44,8 +46,6 @@ class MainWindow : public QMainWindow {
|
|||||||
void onAboutClicked();
|
void onAboutClicked();
|
||||||
void on_actionCheck_for_update_triggered();
|
void on_actionCheck_for_update_triggered();
|
||||||
|
|
||||||
void on_pushButton_clicked();
|
|
||||||
|
|
||||||
/// slots for menu actions
|
/// slots for menu actions
|
||||||
void openNewItemDialog();
|
void openNewItemDialog();
|
||||||
void openEditItemDialog();
|
void openEditItemDialog();
|
||||||
@ -64,9 +64,7 @@ class MainWindow : public QMainWindow {
|
|||||||
void findItems();
|
void findItems();
|
||||||
|
|
||||||
/// 'Server' slots
|
/// 'Server' slots
|
||||||
void fetchItems();
|
void fetchCurrentBiddings();
|
||||||
void postItems();
|
|
||||||
void deleteItem();
|
|
||||||
|
|
||||||
/// 'Tools' slots
|
/// 'Tools' slots
|
||||||
void execSettingsDialog();
|
void execSettingsDialog();
|
||||||
@ -76,8 +74,11 @@ class MainWindow : public QMainWindow {
|
|||||||
|
|
||||||
unique_ptr<GenericCore> m_core;
|
unique_ptr<GenericCore> m_core;
|
||||||
shared_ptr<GeneralSortFilterModel> m_proxyModel;
|
shared_ptr<GeneralSortFilterModel> m_proxyModel;
|
||||||
|
shared_ptr<ModelSummary> m_modelSummary;
|
||||||
|
|
||||||
QUndoStack* m_modelUndoStack;
|
QUndoStack* m_modelUndoStack;
|
||||||
unique_ptr<QUndoView> m_modelUndoView;
|
unique_ptr<QUndoView> m_modelUndoView;
|
||||||
|
unique_ptr<BiddingRoundControl> m_biddingRoundControl;
|
||||||
|
|
||||||
/// File actions
|
/// File actions
|
||||||
unique_ptr<QAction> m_newFileAct;
|
unique_ptr<QAction> m_newFileAct;
|
||||||
@ -98,9 +99,7 @@ class MainWindow : public QMainWindow {
|
|||||||
unique_ptr<QAction> m_deleteItemAct;
|
unique_ptr<QAction> m_deleteItemAct;
|
||||||
unique_ptr<QAction> m_findItemAct;
|
unique_ptr<QAction> m_findItemAct;
|
||||||
/// Server actions
|
/// Server actions
|
||||||
unique_ptr<QAction> m_fetchItemsAct;
|
unique_ptr<QAction> m_fetchCurrentBiddingsAct;
|
||||||
unique_ptr<QAction> m_postItemsAct;
|
|
||||||
unique_ptr<QAction> m_deleteItemsAct;
|
|
||||||
|
|
||||||
/// View actions
|
/// View actions
|
||||||
unique_ptr<QAction> m_showModelUndoViewAct;
|
unique_ptr<QAction> m_showModelUndoViewAct;
|
||||||
@ -110,6 +109,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();
|
||||||
@ -118,5 +118,9 @@ class MainWindow : public QMainWindow {
|
|||||||
void createToolsActions();
|
void createToolsActions();
|
||||||
void createHelpMenu();
|
void createHelpMenu();
|
||||||
void createGuiDialogs();
|
void createGuiDialogs();
|
||||||
|
|
||||||
|
void setupEventTab();
|
||||||
|
|
||||||
|
void initServerConnection();
|
||||||
};
|
};
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
@ -14,22 +14,22 @@
|
|||||||
<string>GenericQtClient</string>
|
<string>GenericQtClient</string>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
<item>
|
<item row="0" column="0">
|
||||||
<widget class="QTableView" name="tableView"/>
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
</item>
|
<property name="currentIndex">
|
||||||
<item>
|
<number>0</number>
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string>Push the button!</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="pushButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Button</string>
|
|
||||||
</property>
|
</property>
|
||||||
|
<widget class="QWidget" name="tab_user">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Mitglieder (&2)</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QTableView" name="tableView"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
315
UIs/BeetRoundWidgets/views/itemdetailmapper.cpp
Normal file
315
UIs/BeetRoundWidgets/views/itemdetailmapper.cpp
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
#include "itemdetailmapper.h"
|
||||||
|
|
||||||
|
#include <data/settingshandler.h>
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDataWidgetMapper>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QStringListModel>
|
||||||
|
#include <QTableView>
|
||||||
|
#include "model/metadata.h"
|
||||||
|
|
||||||
|
ItemDetailMapper::ItemDetailMapper(QWidget* parent)
|
||||||
|
: QWidget{parent}
|
||||||
|
, m_qrCodeDisplay(new QLabel("QR Code")) {
|
||||||
|
/// model mapping
|
||||||
|
m_mapper = std::make_unique<QDataWidgetMapper>(this);
|
||||||
|
/// BUG: If multiple columns are changed not all changes are applied.
|
||||||
|
/// Multiple changes are set individually by calling setData().
|
||||||
|
/// Probably due to a conflicting dataChanged signal for too many roles&columns the data of
|
||||||
|
/// the remaining columns is reset before setData is called.
|
||||||
|
/// And a manual submit would also create multiple undo steps anyway.
|
||||||
|
/// Workaround: ManualSubmit -> AutoSubmit
|
||||||
|
// m_mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
|
||||||
|
m_mapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);
|
||||||
|
|
||||||
|
/// model mapping buttons
|
||||||
|
m_nextButton = new QPushButton(tr("Ne&xt"));
|
||||||
|
m_previousButton = new QPushButton(tr("&Previous"));
|
||||||
|
|
||||||
|
connect(m_previousButton, &QAbstractButton::clicked, this, &ItemDetailMapper::toPrevious);
|
||||||
|
connect(m_nextButton, &QAbstractButton::clicked, this, &ItemDetailMapper::toNext);
|
||||||
|
|
||||||
|
/// the individual widgets
|
||||||
|
// REFACTOR deduce types from meta data & use a factory
|
||||||
|
/// left layout
|
||||||
|
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_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);
|
||||||
|
|
||||||
|
/// right layout
|
||||||
|
m_createOnlineAccountButton = new QPushButton(tr("&Create account"));
|
||||||
|
m_sendInviteMailButton = new QPushButton(tr("&Send invite"));
|
||||||
|
|
||||||
|
m_onlineIdLabel = new QLabel(GET_HEADER_FOR_COLUMN(12));
|
||||||
|
m_onlineIdDisplay = new QLineEdit();
|
||||||
|
m_onlineIdDisplay->setReadOnly(true);
|
||||||
|
m_onlineIdLabel->setBuddy(m_onlineIdDisplay);
|
||||||
|
|
||||||
|
m_accessCodeLabel = new QLabel(GET_HEADER_FOR_COLUMN(13));
|
||||||
|
m_accessCodeDisplay = new QLineEdit();
|
||||||
|
m_accessCodeDisplay->setReadOnly(true);
|
||||||
|
m_accessCodeLabel->setBuddy(m_onlineIdDisplay);
|
||||||
|
|
||||||
|
updateQRCode();
|
||||||
|
connect(this, &ItemDetailMapper::contentChanged, this, &ItemDetailMapper::updateQRCode);
|
||||||
|
|
||||||
|
m_accessUrlDisplay = new QLineEdit();
|
||||||
|
m_accessUrlDisplay->setReadOnly(true);
|
||||||
|
|
||||||
|
QGridLayout* leftLayout = new QGridLayout();
|
||||||
|
leftLayout->addWidget(m_numberLabel, 0, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_numberBox, 0, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_lastNameLabel, 1, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_lastNameEdit, 1, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_firstNameLabel, 2, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_firstNameEdit, 2, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_shareTypeLabel, 3, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_shareTypeBox, 3, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_amountLabel, 4, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_amountSpinBox, 4, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_biddingTypeLabel, 5, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_biddingTypeBox, 5, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_bidding1Label, 6, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_bidding1SpinBox, 6, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_bidding2Label, 7, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_bidding2SpinBox, 7, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_bidding3Label, 8, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_bidding3SpinBox, 8, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_depotWish1Label, 9, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_depotWish1Edit, 9, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_depotWish2Label, 10, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_depotWish2Edit, 10, 1, 1, 1);
|
||||||
|
leftLayout->addWidget(m_mailLabel, 11, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_mailEdit, 11, 1, 1, 1);
|
||||||
|
|
||||||
|
leftLayout->addWidget(m_previousButton, 12, 0, 1, 1);
|
||||||
|
leftLayout->addWidget(m_nextButton, 12, 1, 1, 1);
|
||||||
|
|
||||||
|
/// right layout
|
||||||
|
QVBoxLayout* rightLayout = new QVBoxLayout;
|
||||||
|
QHBoxLayout* onlineAccountControlLayout = new QHBoxLayout;
|
||||||
|
onlineAccountControlLayout->addWidget(m_createOnlineAccountButton);
|
||||||
|
onlineAccountControlLayout->addWidget(m_sendInviteMailButton);
|
||||||
|
rightLayout->addLayout(onlineAccountControlLayout);
|
||||||
|
QGridLayout* rightGridLayout = new QGridLayout();
|
||||||
|
rightGridLayout->addWidget(m_onlineIdLabel, 0, 0, 1, 1);
|
||||||
|
rightGridLayout->addWidget(m_onlineIdDisplay, 0, 1, 1, 1);
|
||||||
|
rightGridLayout->addWidget(m_accessCodeLabel, 1, 0, 1, 1);
|
||||||
|
rightGridLayout->addWidget(m_accessCodeDisplay, 1, 1, 1, 1);
|
||||||
|
rightGridLayout->addWidget(m_qrCodeDisplay, 2, 0, 6, 2);
|
||||||
|
rightGridLayout->addWidget(m_accessUrlDisplay, 9, 0, 1, 2);
|
||||||
|
rightLayout->addLayout(rightGridLayout);
|
||||||
|
|
||||||
|
QHBoxLayout* outerLayout = new QHBoxLayout;
|
||||||
|
outerLayout->addLayout(leftLayout);
|
||||||
|
outerLayout->addLayout(rightLayout);
|
||||||
|
setLayout(outerLayout);
|
||||||
|
|
||||||
|
const QVariantMap serverSettings = SettingsHandler::getSettings("Server");
|
||||||
|
const QString urlValue = serverSettings.value("url").toString();
|
||||||
|
m_serverUrl = urlValue;
|
||||||
|
|
||||||
|
/// online user account
|
||||||
|
connect(m_mailEdit, &QLineEdit::textChanged, this, &ItemDetailMapper::onMailEditChanged);
|
||||||
|
connect(m_onlineIdDisplay, &QLineEdit::textChanged, this, &ItemDetailMapper::onOnlineIDChanged);
|
||||||
|
connect(m_accessCodeDisplay, &QLineEdit::textChanged, this,
|
||||||
|
&ItemDetailMapper::onAccessCodeChanged);
|
||||||
|
|
||||||
|
connect(m_createOnlineAccountButton, &QAbstractButton::clicked, this,
|
||||||
|
&ItemDetailMapper::onCreateOnlineAccountTriggered);
|
||||||
|
connect(m_sendInviteMailButton, &QAbstractButton::clicked, this,
|
||||||
|
&ItemDetailMapper::onSendInviteMailTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::setModelMappings(QTableView* tableView) {
|
||||||
|
qDebug() << "Setting model...";
|
||||||
|
m_tableView = tableView;
|
||||||
|
m_model = tableView->model();
|
||||||
|
m_selectionModel = tableView->selectionModel();
|
||||||
|
|
||||||
|
m_mapper->setModel(m_model);
|
||||||
|
m_mapper->addMapping(m_numberBox, 0);
|
||||||
|
m_mapper->addMapping(m_lastNameEdit, 1);
|
||||||
|
m_mapper->addMapping(m_firstNameEdit, 2);
|
||||||
|
m_mapper->addMapping(m_shareTypeBox, 3, "currentText");
|
||||||
|
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->addMapping(m_onlineIdDisplay, 12);
|
||||||
|
m_mapper->addMapping(m_accessCodeDisplay, 13);
|
||||||
|
|
||||||
|
m_mapper->setCurrentIndex(m_selectionModel->currentIndex().row());
|
||||||
|
|
||||||
|
connect(m_model, &QAbstractItemModel::rowsInserted, this, &ItemDetailMapper::rowsInserted);
|
||||||
|
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ItemDetailMapper::rowsRemoved);
|
||||||
|
connect(m_selectionModel, &QItemSelectionModel::currentChanged, this,
|
||||||
|
&ItemDetailMapper::onCurrentIndexChanged);
|
||||||
|
|
||||||
|
updateButtons(m_selectionModel->currentIndex().row());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ItemDetailMapper::submit() { return m_mapper->submit(); }
|
||||||
|
|
||||||
|
void ItemDetailMapper::revert() { m_mapper->revert(); }
|
||||||
|
|
||||||
|
void ItemDetailMapper::rowsInserted(const QModelIndex& parent, int start, int end) {
|
||||||
|
updateButtons(m_mapper->currentIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::rowsRemoved(const QModelIndex& parent, int start, int end) {
|
||||||
|
if (m_model->rowCount() == 0) {
|
||||||
|
setEnabled(false);
|
||||||
|
|
||||||
|
// m_nameEdit->clear();
|
||||||
|
// m_descriptionEdit->clear();
|
||||||
|
// m_infoEdit->clear();
|
||||||
|
// m_amountBox->clear();
|
||||||
|
// m_factorBox->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateButtons(m_mapper->currentIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::toPrevious() {
|
||||||
|
int currentRow = m_selectionModel->currentIndex().row();
|
||||||
|
QModelIndex previousIndex = m_selectionModel->currentIndex().siblingAtRow(currentRow - 1);
|
||||||
|
if (previousIndex.isValid()) {
|
||||||
|
m_selectionModel->setCurrentIndex(previousIndex, QItemSelectionModel::ClearAndSelect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::toNext() {
|
||||||
|
int currentRow = m_selectionModel->currentIndex().row();
|
||||||
|
QModelIndex nextIndex = m_selectionModel->currentIndex().siblingAtRow(currentRow + 1);
|
||||||
|
if (nextIndex.isValid()) {
|
||||||
|
m_selectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::ClearAndSelect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::updateButtons(int row) {
|
||||||
|
m_previousButton->setEnabled(row > 0);
|
||||||
|
m_nextButton->setEnabled(row < m_model->rowCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::onCurrentIndexChanged(const QModelIndex& current,
|
||||||
|
const QModelIndex& /*previous*/) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
setEnabled(true);
|
||||||
|
}
|
||||||
|
m_mapper->setCurrentModelIndex(current);
|
||||||
|
updateButtons(current.row());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::updateQRCode(const QString text) {
|
||||||
|
QImage unscaledImage;
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
unscaledImage = QImage("://no-picture-taking.png");
|
||||||
|
} else {
|
||||||
|
unscaledImage = m_generator.generateQr(text);
|
||||||
|
}
|
||||||
|
QImage image = unscaledImage.scaled(250, 250);
|
||||||
|
|
||||||
|
m_qrCodeDisplay->setPixmap(QPixmap::fromImage(image));
|
||||||
|
m_qrCodeDisplay->setToolTip(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::onMailEditChanged(const QString& text) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
m_createOnlineAccountButton->setEnabled(false);
|
||||||
|
m_sendInviteMailButton->setEnabled(false);
|
||||||
|
} else {
|
||||||
|
onOnlineIDChanged(m_onlineIdDisplay->text());
|
||||||
|
onAccessCodeChanged(m_accessCodeDisplay->text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::onOnlineIDChanged(const QString& text) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
m_createOnlineAccountButton->setEnabled(true);
|
||||||
|
m_sendInviteMailButton->setEnabled(false);
|
||||||
|
} else {
|
||||||
|
m_createOnlineAccountButton->setEnabled(false);
|
||||||
|
m_sendInviteMailButton->setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::onAccessCodeChanged(const QString& accessCode) {
|
||||||
|
if (accessCode.isEmpty()) {
|
||||||
|
m_sendInviteMailButton->setEnabled(false);
|
||||||
|
m_accessUrlDisplay->setText("");
|
||||||
|
updateQRCode("");
|
||||||
|
} else {
|
||||||
|
m_sendInviteMailButton->setEnabled(true);
|
||||||
|
const QString accessUrl = m_serverUrl + "/log_in/" + accessCode;
|
||||||
|
// const QString accessUrl = "https://bietrunde.das-gruene-zebra.de/log_in/" + text;
|
||||||
|
m_accessUrlDisplay->setText(accessUrl);
|
||||||
|
updateQRCode(accessUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::onCreateOnlineAccountTriggered() {
|
||||||
|
emit createOnlineAccountTriggered(m_mailEdit->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemDetailMapper::onSendInviteMailTriggered() {
|
||||||
|
emit sendInviteMailTriggered(m_mailEdit->text());
|
||||||
|
}
|
||||||
117
UIs/BeetRoundWidgets/views/itemdetailmapper.h
Normal file
117
UIs/BeetRoundWidgets/views/itemdetailmapper.h
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#ifndef ITEMDETAILMAPPER_H
|
||||||
|
#define ITEMDETAILMAPPER_H
|
||||||
|
|
||||||
|
#include <QDataWidgetMapper>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QLabel;
|
||||||
|
class QLineEdit;
|
||||||
|
class QComboBox;
|
||||||
|
class QDoubleSpinBox;
|
||||||
|
class QSpinBox;
|
||||||
|
class QPushButton;
|
||||||
|
class QAbstractItemModel;
|
||||||
|
class QItemSelectionModel;
|
||||||
|
class QStringListModel;
|
||||||
|
class QTableView;
|
||||||
|
|
||||||
|
#include "QrCodeGenerator.h"
|
||||||
|
|
||||||
|
class ItemDetailMapper : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ItemDetailMapper(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
void setModelMappings(QTableView* tableView);
|
||||||
|
|
||||||
|
bool submit();
|
||||||
|
void revert();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void contentChanged(const QString text);
|
||||||
|
|
||||||
|
void createOnlineAccountTriggered(const QString& mailAddress);
|
||||||
|
void sendInviteMailTriggered(const QString& mailAddress);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void rowsInserted(const QModelIndex& parent, int start, int end);
|
||||||
|
void rowsRemoved(const QModelIndex& parent, int start, int end);
|
||||||
|
void toPrevious();
|
||||||
|
void toNext();
|
||||||
|
void updateButtons(int row);
|
||||||
|
void onCurrentIndexChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||||
|
|
||||||
|
void updateQRCode(const QString text = "");
|
||||||
|
void onMailEditChanged(const QString& text);
|
||||||
|
void onOnlineIDChanged(const QString& text);
|
||||||
|
void onAccessCodeChanged(const QString& accessCode);
|
||||||
|
void onCreateOnlineAccountTriggered();
|
||||||
|
void onSendInviteMailTriggered();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// *** members ***
|
||||||
|
/// Model stuff
|
||||||
|
QTableView* m_tableView = nullptr;
|
||||||
|
QAbstractItemModel* m_model = nullptr;
|
||||||
|
QItemSelectionModel* m_selectionModel = nullptr;
|
||||||
|
|
||||||
|
std::unique_ptr<QDataWidgetMapper> m_mapper;
|
||||||
|
|
||||||
|
QString m_serverUrl;
|
||||||
|
/// *** GUI elements ***
|
||||||
|
/// left layout
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// right layout
|
||||||
|
QPushButton* m_createOnlineAccountButton = nullptr;
|
||||||
|
QPushButton* m_sendInviteMailButton = nullptr;
|
||||||
|
|
||||||
|
QLabel* m_onlineIdLabel;
|
||||||
|
QLineEdit* m_onlineIdDisplay = nullptr;
|
||||||
|
QLabel* m_accessCodeLabel;
|
||||||
|
QLineEdit* m_accessCodeDisplay = nullptr;
|
||||||
|
|
||||||
|
QLabel* m_qrCodeDisplay = nullptr;
|
||||||
|
QrCodeGenerator m_generator;
|
||||||
|
|
||||||
|
QLineEdit* m_accessUrlDisplay = nullptr;
|
||||||
|
|
||||||
|
/// *** Model mapper stuff ***
|
||||||
|
QPushButton* m_nextButton;
|
||||||
|
QPushButton* m_previousButton;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ITEMDETAILMAPPER_H
|
||||||
67
UIs/BeetRoundWidgets/widgets/biddingroundcontrol.cpp
Normal file
67
UIs/BeetRoundWidgets/widgets/biddingroundcontrol.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "biddingroundcontrol.h"
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
BiddingRoundControl::BiddingRoundControl(QWidget* parent)
|
||||||
|
: QWidget{parent} {
|
||||||
|
m_layout = new QHBoxLayout(this);
|
||||||
|
m_title = new QLabel("Bidding round control:");
|
||||||
|
m_status = new QLabel("(No round started yet.)");
|
||||||
|
|
||||||
|
m_newRoundButton = new QPushButton("Start new round");
|
||||||
|
m_restartRoundButton = new QPushButton("Restart last round");
|
||||||
|
m_stopRoundButton = new QPushButton("Stop round");
|
||||||
|
m_refreshRoundButton = new QPushButton("Refresh");
|
||||||
|
|
||||||
|
m_newRoundButton->setEnabled(false);
|
||||||
|
m_restartRoundButton->setEnabled(false);
|
||||||
|
m_stopRoundButton->setEnabled(false);
|
||||||
|
|
||||||
|
m_layout->addWidget(m_title);
|
||||||
|
m_layout->addWidget(m_status);
|
||||||
|
m_layout->addWidget(m_newRoundButton);
|
||||||
|
m_layout->addWidget(m_restartRoundButton);
|
||||||
|
m_layout->addWidget(m_stopRoundButton);
|
||||||
|
m_layout->addWidget(m_refreshRoundButton);
|
||||||
|
|
||||||
|
connect(m_newRoundButton, &QPushButton::clicked, this,
|
||||||
|
&BiddingRoundControl::onStartNewRoundTriggered);
|
||||||
|
connect(m_restartRoundButton, &QPushButton::clicked, this,
|
||||||
|
&BiddingRoundControl::onRestartLastRoundTriggered);
|
||||||
|
connect(m_stopRoundButton, &QPushButton::clicked, this,
|
||||||
|
&BiddingRoundControl::onStopCurrentRoundTriggered);
|
||||||
|
connect(m_refreshRoundButton, &QPushButton::clicked, this,
|
||||||
|
&BiddingRoundControl::onRefreshCurrentRoundTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundControl::onRefreshCurrentRoundTriggered() {
|
||||||
|
emit sendGetRequest(GetCurrentBiddingRound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundControl::onStartNewRoundTriggered() { emit sendGetRequest(StartNewBiddingRound); }
|
||||||
|
|
||||||
|
void BiddingRoundControl::onRestartLastRoundTriggered() {
|
||||||
|
emit sendGetRequest(RestartLastBiddingRound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundControl::onStopCurrentRoundTriggered() {
|
||||||
|
emit sendGetRequest(StopCurrentBiddingRound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundControl::onCurrentBiddingRoundChanged(int roundNumber, bool isActive) {
|
||||||
|
QString text = QString::number(roundNumber);
|
||||||
|
if (isActive) {
|
||||||
|
text.append(" (active)");
|
||||||
|
m_newRoundButton->setEnabled(false);
|
||||||
|
m_restartRoundButton->setEnabled(false);
|
||||||
|
m_stopRoundButton->setEnabled(true);
|
||||||
|
} else {
|
||||||
|
text.append(" (stopped)");
|
||||||
|
m_newRoundButton->setEnabled(true);
|
||||||
|
m_restartRoundButton->setEnabled(roundNumber > 0);
|
||||||
|
m_stopRoundButton->setEnabled(false);
|
||||||
|
}
|
||||||
|
m_status->setText(text);
|
||||||
|
}
|
||||||
39
UIs/BeetRoundWidgets/widgets/biddingroundcontrol.h
Normal file
39
UIs/BeetRoundWidgets/widgets/biddingroundcontrol.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#ifndef BIDDINGROUNDCONTROL_H
|
||||||
|
#define BIDDINGROUNDCONTROL_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include "model/metadata.h"
|
||||||
|
|
||||||
|
class QPushButton;
|
||||||
|
class QLabel;
|
||||||
|
class QHBoxLayout;
|
||||||
|
class BiddingRoundControl : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit BiddingRoundControl(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void sendGetRequest(GetRequestTypes type, QVariant data = QVariant());
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/// button slots
|
||||||
|
void onRefreshCurrentRoundTriggered();
|
||||||
|
void onStartNewRoundTriggered();
|
||||||
|
void onRestartLastRoundTriggered();
|
||||||
|
void onStopCurrentRoundTriggered();
|
||||||
|
|
||||||
|
/// event slots
|
||||||
|
void onCurrentBiddingRoundChanged(int roundNumber, bool isActive);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHBoxLayout* m_layout = nullptr;
|
||||||
|
QLabel* m_title = nullptr;
|
||||||
|
QLabel* m_status = nullptr;
|
||||||
|
|
||||||
|
QPushButton* m_newRoundButton = nullptr;
|
||||||
|
QPushButton* m_restartRoundButton = nullptr;
|
||||||
|
QPushButton* m_stopRoundButton = nullptr;
|
||||||
|
QPushButton* m_refreshRoundButton = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BIDDINGROUNDCONTROL_H
|
||||||
56
UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.cpp
Normal file
56
UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include "biddingroundprogresslayout.h"
|
||||||
|
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QProgressBar>
|
||||||
|
|
||||||
|
BiddingRoundProgressLayout::BiddingRoundProgressLayout(const int nCurrentBiddings,
|
||||||
|
const int nExpectedBiddings,
|
||||||
|
QWidget* parent)
|
||||||
|
: QHBoxLayout(parent)
|
||||||
|
, m_nCurrentBiddings(nCurrentBiddings)
|
||||||
|
, m_nExpectedBiddings(nExpectedBiddings) {
|
||||||
|
QLabel* nBidsText = new QLabel("Abgegeben:");
|
||||||
|
addWidget(nBidsText);
|
||||||
|
|
||||||
|
m_nBiddingsLabel = new QLabel(QString::number(m_nCurrentBiddings));
|
||||||
|
addWidget(m_nBiddingsLabel);
|
||||||
|
|
||||||
|
QLabel* nExpectedText = new QLabel("Erwartet:");
|
||||||
|
addWidget(nExpectedText);
|
||||||
|
|
||||||
|
m_nExpectedBiddingLabel = new QLabel(QString::number(m_nExpectedBiddings));
|
||||||
|
addWidget(m_nExpectedBiddingLabel);
|
||||||
|
|
||||||
|
m_biddingRoundProgressBar = new QProgressBar(parent);
|
||||||
|
m_biddingRoundProgressBar->setMinimum(0);
|
||||||
|
m_biddingRoundProgressBar->setMaximum(m_nExpectedBiddings);
|
||||||
|
m_biddingRoundProgressBar->setValue(m_nCurrentBiddings);
|
||||||
|
|
||||||
|
addWidget(m_biddingRoundProgressBar);
|
||||||
|
|
||||||
|
updateEnabledStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundProgressLayout::setCurrentBiddings(const int value) {
|
||||||
|
m_nCurrentBiddings = value;
|
||||||
|
const QString bidCountString = QString::number(value);
|
||||||
|
m_nBiddingsLabel->setText(bidCountString);
|
||||||
|
m_biddingRoundProgressBar->setValue(value);
|
||||||
|
updateEnabledStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundProgressLayout::setExpectedBiddings(const int value) {
|
||||||
|
m_nExpectedBiddings = value;
|
||||||
|
const QString expectedBiddingsString = QString::number(value);
|
||||||
|
m_nExpectedBiddingLabel->setText(expectedBiddingsString);
|
||||||
|
m_biddingRoundProgressBar->setMaximum(value);
|
||||||
|
updateEnabledStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundProgressLayout::updateEnabledStatus() {
|
||||||
|
if (m_nCurrentBiddings > m_nExpectedBiddings) {
|
||||||
|
m_biddingRoundProgressBar->setEnabled(false);
|
||||||
|
} else {
|
||||||
|
m_biddingRoundProgressBar->setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.h
Normal file
30
UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#ifndef BIDDINGROUNDPROGRESSLAYOUT_H
|
||||||
|
#define BIDDINGROUNDPROGRESSLAYOUT_H
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
|
||||||
|
class QLabel;
|
||||||
|
class QProgressBar;
|
||||||
|
|
||||||
|
class BiddingRoundProgressLayout : public QHBoxLayout {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
BiddingRoundProgressLayout(const int nCurrentBiddings,
|
||||||
|
const int nExpectedBiddings,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
void setCurrentBiddings(const int value);
|
||||||
|
void setExpectedBiddings(const int value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_nCurrentBiddings;
|
||||||
|
int m_nExpectedBiddings;
|
||||||
|
|
||||||
|
QLabel* m_nBiddingsLabel = nullptr;
|
||||||
|
QLabel* m_nExpectedBiddingLabel = nullptr;
|
||||||
|
QProgressBar* m_biddingRoundProgressBar = nullptr;
|
||||||
|
|
||||||
|
void updateEnabledStatus();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BIDDINGROUNDPROGRESSLAYOUT_H
|
||||||
110
UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.cpp
Normal file
110
UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#include "biddingroundstatuswidget.h"
|
||||||
|
|
||||||
|
#include <QChartView>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPieSeries>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
BiddingRoundStatusWidget::BiddingRoundStatusWidget(const QString& title,
|
||||||
|
const int placedBiddings,
|
||||||
|
const int expectedBiddings,
|
||||||
|
const int financialNeed,
|
||||||
|
const int currentSum,
|
||||||
|
const qreal currentAverage,
|
||||||
|
QWidget* parent)
|
||||||
|
: QWidget{parent}
|
||||||
|
, m_title(title)
|
||||||
|
, m_financialNeed(financialNeed)
|
||||||
|
, m_currentSum(currentSum) {
|
||||||
|
m_moneyDonutSeries = new QPieSeries();
|
||||||
|
m_moneyDonutSeries->setHoleSize(0.35);
|
||||||
|
const QString sumText = QString("Summe: %1 €").arg(currentSum);
|
||||||
|
m_sumSlice = m_moneyDonutSeries->append(sumText, currentSum);
|
||||||
|
m_sumSlice->setLabelVisible();
|
||||||
|
|
||||||
|
const int currentGap = m_financialNeed - currentSum;
|
||||||
|
const QString gapText = QString("offen: %1 €").arg(currentGap);
|
||||||
|
m_gapSlice = m_moneyDonutSeries->append(gapText, currentGap);
|
||||||
|
m_gapSlice->setLabelVisible();
|
||||||
|
|
||||||
|
m_chartView = new QChartView();
|
||||||
|
m_chartView->setMinimumWidth(100);
|
||||||
|
// m_chartView->setMaximumWidth(500);
|
||||||
|
m_chartView->setRenderHint(QPainter::Antialiasing);
|
||||||
|
m_chartView->chart()->setTitle(m_title);
|
||||||
|
m_chartView->chart()->addSeries(m_moneyDonutSeries);
|
||||||
|
m_chartView->chart()->legend()->setAlignment(Qt::AlignBottom);
|
||||||
|
m_chartView->chart()->setTheme(QChart::ChartThemeBlueCerulean);
|
||||||
|
m_chartView->chart()->legend()->setFont(QFont("Arial", 8));
|
||||||
|
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout();
|
||||||
|
layout->addWidget(m_chartView, 5);
|
||||||
|
QBoxLayout* progressLayout = createProgressLayout(placedBiddings, expectedBiddings);
|
||||||
|
layout->addLayout(progressLayout);
|
||||||
|
|
||||||
|
/// bidding average
|
||||||
|
QHBoxLayout* averageLayout = new QHBoxLayout();
|
||||||
|
m_biddingAverageDescription = new QLabel("Durchschnittsgebot: ");
|
||||||
|
const QString currencyString = createCurrencyString(currentAverage);
|
||||||
|
m_biddingAverageValue = new QLabel(currencyString);
|
||||||
|
averageLayout->addWidget(m_biddingAverageDescription);
|
||||||
|
averageLayout->addWidget(m_biddingAverageValue);
|
||||||
|
layout->addLayout(averageLayout);
|
||||||
|
|
||||||
|
setLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundStatusWidget::onExpectedBiddingsChanged(const int nExpected) {
|
||||||
|
m_biddingRoundProgress->setExpectedBiddings(nExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundStatusWidget::onNPlacedBiddingsChanged(const int nPlaced) {
|
||||||
|
m_biddingRoundProgress->setCurrentBiddings(nPlaced);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundStatusWidget::onBiddingSumChanged(const int sum) {
|
||||||
|
m_currentSum = sum;
|
||||||
|
updateDonutChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundStatusWidget::onBiddingAverageChanged(const qreal average) {
|
||||||
|
if (m_biddingAverageValue) {
|
||||||
|
m_biddingAverageValue->setText(createCurrencyString(average));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundStatusWidget::onFinancialNeedChanged(const int financialNeed) {
|
||||||
|
m_financialNeed = financialNeed;
|
||||||
|
updateDonutChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
QBoxLayout* BiddingRoundStatusWidget::createProgressLayout(const int nPlaced, const int nExpected) {
|
||||||
|
QVBoxLayout* progressLayout = new QVBoxLayout();
|
||||||
|
|
||||||
|
m_biddingRoundProgress = new BiddingRoundProgressLayout(nPlaced, nExpected);
|
||||||
|
progressLayout->addLayout(m_biddingRoundProgress);
|
||||||
|
|
||||||
|
return progressLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiddingRoundStatusWidget::updateDonutChart() {
|
||||||
|
int currentGap;
|
||||||
|
if (m_currentSum < m_financialNeed) {
|
||||||
|
currentGap = m_financialNeed - m_currentSum;
|
||||||
|
} else {
|
||||||
|
currentGap = 0;
|
||||||
|
}
|
||||||
|
const QString sumText = QString("Summe: %1 €").arg(m_currentSum);
|
||||||
|
m_sumSlice->setValue(m_currentSum);
|
||||||
|
m_sumSlice->setLabel(sumText);
|
||||||
|
|
||||||
|
const QString gapText = QString("offen: %1 €").arg(currentGap);
|
||||||
|
m_gapSlice->setValue(currentGap);
|
||||||
|
m_gapSlice->setLabel(gapText);
|
||||||
|
const bool gapLabelVisible = currentGap != 0;
|
||||||
|
m_gapSlice->setLabelVisible(gapLabelVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BiddingRoundStatusWidget::createCurrencyString(const qreal value) const {
|
||||||
|
return QString::number(value) + " €";
|
||||||
|
}
|
||||||
53
UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.h
Normal file
53
UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#ifndef BIDDINGROUNDSTATUSWIDGET_H
|
||||||
|
#define BIDDINGROUNDSTATUSWIDGET_H
|
||||||
|
|
||||||
|
#include "biddingroundprogresslayout.h"
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QBoxLayout;
|
||||||
|
class QChartView;
|
||||||
|
class QPieSeries;
|
||||||
|
class QPieSlice;
|
||||||
|
|
||||||
|
class BiddingRoundStatusWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit BiddingRoundStatusWidget(const QString& title,
|
||||||
|
const int placedBiddings,
|
||||||
|
const int expectedBiddings,
|
||||||
|
const int financialNeed,
|
||||||
|
const int currentSum,
|
||||||
|
const qreal currentAverage,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onExpectedBiddingsChanged(const int nExpected);
|
||||||
|
void onNPlacedBiddingsChanged(const int nPlaced);
|
||||||
|
void onBiddingSumChanged(const int sum);
|
||||||
|
void onBiddingAverageChanged(const qreal average);
|
||||||
|
void onFinancialNeedChanged(const int financialNeed);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_title;
|
||||||
|
int m_financialNeed;
|
||||||
|
int m_currentSum;
|
||||||
|
|
||||||
|
QChartView* m_chartView = nullptr;
|
||||||
|
QPieSeries* m_moneyDonutSeries = nullptr;
|
||||||
|
QPieSlice* m_sumSlice = nullptr;
|
||||||
|
QPieSlice* m_gapSlice = nullptr;
|
||||||
|
QLabel* m_biddingAverageDescription = nullptr;
|
||||||
|
QLabel* m_biddingAverageValue = nullptr;
|
||||||
|
|
||||||
|
BiddingRoundProgressLayout* m_biddingRoundProgress = nullptr;
|
||||||
|
|
||||||
|
QBoxLayout* createProgressLayout(const int nPlaced, const int nExpected);
|
||||||
|
void updateDonutChart();
|
||||||
|
|
||||||
|
QString createCurrencyString(const qreal value) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BIDDINGROUNDSTATUSWIDGET_H
|
||||||
66
UIs/BeetRoundWidgets/widgets/comboboxdelegate.cpp
Normal file
66
UIs/BeetRoundWidgets/widgets/comboboxdelegate.cpp
Normal 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);
|
||||||
|
}
|
||||||
31
UIs/BeetRoundWidgets/widgets/comboboxdelegate.h
Normal file
31
UIs/BeetRoundWidgets/widgets/comboboxdelegate.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef COMBOBOXDELEGATE_H
|
||||||
|
#define COMBOBOXDELEGATE_H
|
||||||
|
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
class QStringListModel;
|
||||||
|
|
||||||
|
class ComboboxDelegate : public QStyledItemDelegate {
|
||||||
|
// TODO move source code files into subfolder "widgets/delegate"
|
||||||
|
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
|
||||||
69
UIs/BeetRoundWidgets/widgets/spinboxdelegate.cpp
Normal file
69
UIs/BeetRoundWidgets/widgets/spinboxdelegate.cpp
Normal 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);
|
||||||
|
}
|
||||||
27
UIs/BeetRoundWidgets/widgets/spinboxdelegate.h
Normal file
27
UIs/BeetRoundWidgets/widgets/spinboxdelegate.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef SPINBOXDELEGATE_H
|
||||||
|
#define SPINBOXDELEGATE_H
|
||||||
|
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
class SpinboxDelegate : public QStyledItemDelegate {
|
||||||
|
// TODO move source code files into subfolder "widgets/delegate"
|
||||||
|
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
|
||||||
166
UIs/BeetRoundWidgets/widgets/summarywidget.cpp
Normal file
166
UIs/BeetRoundWidgets/widgets/summarywidget.cpp
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
#include "summarywidget.h"
|
||||||
|
|
||||||
|
#include "biddingroundstatuswidget.h"
|
||||||
|
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QProperty>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <model/modelsummary.h>
|
||||||
|
|
||||||
|
SummaryWidget::SummaryWidget(std::shared_ptr<ModelSummary> modelSummary, QWidget* parent)
|
||||||
|
: QWidget{parent}
|
||||||
|
, m_modelSummary(modelSummary) {
|
||||||
|
/// Layouting
|
||||||
|
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
/// monthly need
|
||||||
|
QHBoxLayout* footerLayout = new QHBoxLayout();
|
||||||
|
|
||||||
|
QLabel* nTotalSharesLabel = new QLabel("Erwartete Ernteanteile:");
|
||||||
|
const qreal expectedShares = m_modelSummary->nTotalExpectedShares();
|
||||||
|
m_nTotalExpectedShares = new QLabel(QString::number(expectedShares));
|
||||||
|
|
||||||
|
QLabel* financialNeedLabel = new QLabel("monatlicher Finanzbedarf:");
|
||||||
|
m_financialNeedBox = new QSpinBox();
|
||||||
|
m_financialNeedBox->setMaximum(50000);
|
||||||
|
m_financialNeedBox->setValue(m_financialNeed);
|
||||||
|
|
||||||
|
connect(m_financialNeedBox, &QSpinBox::valueChanged, this,
|
||||||
|
&SummaryWidget::onFinancialNeedChanged);
|
||||||
|
|
||||||
|
footerLayout->addWidget(nTotalSharesLabel);
|
||||||
|
footerLayout->addWidget(m_nTotalExpectedShares);
|
||||||
|
footerLayout->addStretch(1);
|
||||||
|
footerLayout->addWidget(financialNeedLabel);
|
||||||
|
footerLayout->addWidget(m_financialNeedBox);
|
||||||
|
footerLayout->addStretch(1);
|
||||||
|
|
||||||
|
QBoxLayout* roundsLayout = createBiddingOverviewLayout();
|
||||||
|
mainLayout->addLayout(roundsLayout);
|
||||||
|
mainLayout->addLayout(footerLayout);
|
||||||
|
|
||||||
|
mainLayout->setSpacing(50);
|
||||||
|
setLayout(mainLayout);
|
||||||
|
|
||||||
|
setupConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SummaryWidget::onFinancialNeedChanged(int newFinancialNeed) {
|
||||||
|
m_biddingStatus1->onFinancialNeedChanged(newFinancialNeed);
|
||||||
|
m_biddingStatus2->onFinancialNeedChanged(newFinancialNeed);
|
||||||
|
m_biddingStatus3->onFinancialNeedChanged(newFinancialNeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SummaryWidget::onNExpectedBiddingChanged(int newNExpected) {
|
||||||
|
m_biddingStatus1->onExpectedBiddingsChanged(newNExpected);
|
||||||
|
m_biddingStatus2->onExpectedBiddingsChanged(newNExpected);
|
||||||
|
m_biddingStatus3->onExpectedBiddingsChanged(newNExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SummaryWidget::onNTotalExpectedSharesChanged(qreal newNExpected) {
|
||||||
|
const QString expectedSharesString = QString::number(newNExpected);
|
||||||
|
m_nTotalExpectedShares->setText(expectedSharesString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SummaryWidget::onNPlacedBiddingsChanged(const int roundNumber) {
|
||||||
|
switch (roundNumber) {
|
||||||
|
case 1:
|
||||||
|
m_biddingStatus1->onNPlacedBiddingsChanged(m_modelSummary->nPlacedBiddings1());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
m_biddingStatus2->onNPlacedBiddingsChanged(m_modelSummary->nPlacedBiddings2());
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
m_biddingStatus3->onNPlacedBiddingsChanged(m_modelSummary->nPlacedBiddings3());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qWarning() << "Unknown round number:" << roundNumber;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SummaryWidget::onBiddingSumChanged(const int roundNumber) {
|
||||||
|
switch (roundNumber) {
|
||||||
|
case 1:
|
||||||
|
m_biddingStatus1->onBiddingSumChanged(m_modelSummary->biddingSum1());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
m_biddingStatus2->onBiddingSumChanged(m_modelSummary->biddingSum2());
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
m_biddingStatus3->onBiddingSumChanged(m_modelSummary->biddingSum3());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qWarning() << "Unknown round number:" << roundNumber;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SummaryWidget::onBiddingAverageChanged(const int roundNumber) {
|
||||||
|
switch (roundNumber) {
|
||||||
|
case 1:
|
||||||
|
m_biddingStatus1->onBiddingAverageChanged(m_modelSummary->biddingAverage1());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
m_biddingStatus2->onBiddingAverageChanged(m_modelSummary->biddingAverage2());
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
m_biddingStatus3->onBiddingAverageChanged(m_modelSummary->biddingAverage3());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qWarning() << "Unknown round number:" << roundNumber;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QBoxLayout* SummaryWidget::createBiddingOverviewLayout() {
|
||||||
|
QHBoxLayout* layout = new QHBoxLayout();
|
||||||
|
|
||||||
|
const int expectedBiddings = m_modelSummary->nExpectedBiddings();
|
||||||
|
|
||||||
|
/// bidding round 1
|
||||||
|
const int placedBiddings1 = m_modelSummary->nPlacedBiddings1();
|
||||||
|
const int biddingSum1 = m_modelSummary->biddingSum1();
|
||||||
|
const qreal biddingAverage1 = m_modelSummary->biddingAverage1();
|
||||||
|
m_biddingStatus1 =
|
||||||
|
make_unique<BiddingRoundStatusWidget>("Bietrunde 1", placedBiddings1, expectedBiddings,
|
||||||
|
m_financialNeed, biddingSum1, biddingAverage1);
|
||||||
|
/// bidding round 2
|
||||||
|
const int placedBiddings2 = m_modelSummary->nPlacedBiddings2();
|
||||||
|
const int biddingSum2 = m_modelSummary->biddingSum2();
|
||||||
|
const qreal biddingAverage2 = m_modelSummary->biddingAverage2();
|
||||||
|
m_biddingStatus2 =
|
||||||
|
make_unique<BiddingRoundStatusWidget>("Bietrunde 2", placedBiddings2, expectedBiddings,
|
||||||
|
m_financialNeed, biddingSum2, biddingAverage2);
|
||||||
|
/// bidding round 3
|
||||||
|
const int placedBiddings3 = m_modelSummary->nPlacedBiddings3();
|
||||||
|
const int biddingSum3 = m_modelSummary->biddingSum3();
|
||||||
|
const qreal biddingAverage3 = m_modelSummary->biddingAverage3();
|
||||||
|
m_biddingStatus3 =
|
||||||
|
make_unique<BiddingRoundStatusWidget>("Bietrunde 3", placedBiddings3, expectedBiddings,
|
||||||
|
m_financialNeed, biddingSum3, biddingAverage3);
|
||||||
|
|
||||||
|
layout->addWidget(m_biddingStatus1.get());
|
||||||
|
layout->addWidget(m_biddingStatus2.get());
|
||||||
|
layout->addWidget(m_biddingStatus3.get());
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SummaryWidget::setupConnections() {
|
||||||
|
// TODO figure out how to encapsulate each property binding into a dedicated function:
|
||||||
|
// "bindProperty(&bindable, &signal, &getter, widget)"
|
||||||
|
QObject::connect(m_modelSummary.get(), &ModelSummary::nExpectedBiddingsChanged,
|
||||||
|
[&]() { onNExpectedBiddingChanged(m_modelSummary->nExpectedBiddings()); });
|
||||||
|
QObject::connect(m_modelSummary.get(), &ModelSummary::nTotalExpectedSharesChanged, this, [&]() {
|
||||||
|
onNTotalExpectedSharesChanged(m_modelSummary->nTotalExpectedShares());
|
||||||
|
});
|
||||||
|
QObject::connect(m_modelSummary.get(), &ModelSummary::nPlacedBiddingsChanged, this,
|
||||||
|
&SummaryWidget::onNPlacedBiddingsChanged);
|
||||||
|
QObject::connect(m_modelSummary.get(), &ModelSummary::biddingSumChanged, this,
|
||||||
|
&SummaryWidget::onBiddingSumChanged);
|
||||||
|
QObject::connect(m_modelSummary.get(), &ModelSummary::biddingAverageChanged, this,
|
||||||
|
&SummaryWidget::onBiddingAverageChanged);
|
||||||
|
}
|
||||||
47
UIs/BeetRoundWidgets/widgets/summarywidget.h
Normal file
47
UIs/BeetRoundWidgets/widgets/summarywidget.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#ifndef SUMMARYWIDGET_H
|
||||||
|
#define SUMMARYWIDGET_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class BiddingRoundStatusWidget;
|
||||||
|
class ModelSummary;
|
||||||
|
class QLabel;
|
||||||
|
class QSpinBox;
|
||||||
|
class QBoxLayout;
|
||||||
|
|
||||||
|
class SummaryWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SummaryWidget(std::shared_ptr<ModelSummary> modelSummary, QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onFinancialNeedChanged(int newFinancialNeed);
|
||||||
|
void onNExpectedBiddingChanged(int newNExpected);
|
||||||
|
void onNTotalExpectedSharesChanged(qreal newNExpected);
|
||||||
|
|
||||||
|
void onNPlacedBiddingsChanged(const int roundNumber);
|
||||||
|
void onBiddingSumChanged(const int roundNumber);
|
||||||
|
void onBiddingAverageChanged(const int roundNumber);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<ModelSummary> m_modelSummary;
|
||||||
|
|
||||||
|
std::unique_ptr<BiddingRoundStatusWidget> m_biddingStatus1;
|
||||||
|
std::unique_ptr<BiddingRoundStatusWidget> m_biddingStatus2;
|
||||||
|
std::unique_ptr<BiddingRoundStatusWidget> m_biddingStatus3;
|
||||||
|
|
||||||
|
// TODO read from settings (maybe via model/core; maybe set in constructor)
|
||||||
|
const int m_financialNeed = 13942;
|
||||||
|
QSpinBox* m_financialNeedBox = nullptr;
|
||||||
|
QLabel* m_nTotalExpectedShares = nullptr;
|
||||||
|
|
||||||
|
QLabel* m_rowCountValueLabel;
|
||||||
|
QLabel* m_nExpectedBiddingsValueLabel;
|
||||||
|
|
||||||
|
/// functions
|
||||||
|
QBoxLayout* createBiddingOverviewLayout();
|
||||||
|
void setupConnections();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SUMMARYWIDGET_H
|
||||||
83
UIs/GenericWidgets/.gitignore
vendored
83
UIs/GenericWidgets/.gitignore
vendored
@ -1,83 +0,0 @@
|
|||||||
# This file is used to ignore files which are generated
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
*~
|
|
||||||
*.autosave
|
|
||||||
*.a
|
|
||||||
*.core
|
|
||||||
*.moc
|
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
*.orig
|
|
||||||
*.rej
|
|
||||||
*.so
|
|
||||||
*.so.*
|
|
||||||
*_pch.h.cpp
|
|
||||||
*_resource.rc
|
|
||||||
*.qm
|
|
||||||
.#*
|
|
||||||
*.*#
|
|
||||||
core
|
|
||||||
!core/
|
|
||||||
tags
|
|
||||||
.DS_Store
|
|
||||||
.directory
|
|
||||||
*.debug
|
|
||||||
Makefile*
|
|
||||||
*.prl
|
|
||||||
*.app
|
|
||||||
moc_*.cpp
|
|
||||||
ui_*.h
|
|
||||||
qrc_*.cpp
|
|
||||||
Thumbs.db
|
|
||||||
*.res
|
|
||||||
*.rc
|
|
||||||
/.qmake.cache
|
|
||||||
/.qmake.stash
|
|
||||||
|
|
||||||
# qtcreator generated files
|
|
||||||
*.pro.user*
|
|
||||||
*.qbs.user*
|
|
||||||
CMakeLists.txt.user*
|
|
||||||
|
|
||||||
# xemacs temporary files
|
|
||||||
*.flc
|
|
||||||
|
|
||||||
# Vim temporary files
|
|
||||||
.*.swp
|
|
||||||
|
|
||||||
# Visual Studio generated files
|
|
||||||
*.ib_pdb_index
|
|
||||||
*.idb
|
|
||||||
*.ilk
|
|
||||||
*.pdb
|
|
||||||
*.sln
|
|
||||||
*.suo
|
|
||||||
*.vcproj
|
|
||||||
*vcproj.*.*.user
|
|
||||||
*.ncb
|
|
||||||
*.sdf
|
|
||||||
*.opensdf
|
|
||||||
*.vcxproj
|
|
||||||
*vcxproj.*
|
|
||||||
|
|
||||||
# MinGW generated files
|
|
||||||
*.Debug
|
|
||||||
*.Release
|
|
||||||
|
|
||||||
# Python byte code
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Binaries
|
|
||||||
# --------
|
|
||||||
*.dll
|
|
||||||
*.exe
|
|
||||||
|
|
||||||
# Directories with generated files
|
|
||||||
.moc/
|
|
||||||
.obj/
|
|
||||||
.pch/
|
|
||||||
.rcc/
|
|
||||||
.uic/
|
|
||||||
/build*/
|
|
||||||
_build*/
|
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
@ -1,160 +0,0 @@
|
|||||||
#include "itemdetailmapper.h"
|
|
||||||
|
|
||||||
#include <QAbstractItemModel>
|
|
||||||
#include <QDataWidgetMapper>
|
|
||||||
#include <QGridLayout>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QSpinBox>
|
|
||||||
#include <QTableView>
|
|
||||||
#include "model/metadata.h"
|
|
||||||
|
|
||||||
ItemDetailMapper::ItemDetailMapper(QWidget* parent)
|
|
||||||
: QWidget{parent} {
|
|
||||||
/// model mapping
|
|
||||||
m_mapper = std::make_unique<QDataWidgetMapper>(this);
|
|
||||||
/// BUG: If multiple columns are changed not all changes are applied.
|
|
||||||
/// Multiple changes are set individually by calling setData().
|
|
||||||
/// Probably due to a conflicting dataChanged signal for too many roles&columns the data of
|
|
||||||
/// the remaining columns is reset before setData is called.
|
|
||||||
/// And a manual submit would also create multiple undo steps anyway.
|
|
||||||
/// Workaround: ManualSubmit -> AutoSubmit
|
|
||||||
// m_mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
|
|
||||||
m_mapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);
|
|
||||||
|
|
||||||
/// model mapping buttons
|
|
||||||
m_nextButton = new QPushButton(tr("Ne&xt"));
|
|
||||||
m_previousButton = new QPushButton(tr("&Previous"));
|
|
||||||
|
|
||||||
connect(m_previousButton, &QAbstractButton::clicked, this, &ItemDetailMapper::toPrevious);
|
|
||||||
connect(m_nextButton, &QAbstractButton::clicked, this, &ItemDetailMapper::toNext);
|
|
||||||
|
|
||||||
/// the individual widgets
|
|
||||||
// REFACTOR deduce label names and types from meta data & use a factory
|
|
||||||
m_nameLabel = new QLabel("&Name");
|
|
||||||
m_nameEdit = new QLineEdit();
|
|
||||||
m_nameLabel->setBuddy(m_nameEdit);
|
|
||||||
|
|
||||||
m_descriptionLabel = new QLabel("&Description");
|
|
||||||
m_descriptionEdit = new QLineEdit();
|
|
||||||
m_descriptionLabel->setBuddy(m_descriptionEdit);
|
|
||||||
|
|
||||||
m_infoLabel = new QLabel("&Info");
|
|
||||||
m_infoEdit = new QLineEdit();
|
|
||||||
m_infoLabel->setBuddy(m_infoEdit);
|
|
||||||
|
|
||||||
m_amountLabel = new QLabel("&Amount");
|
|
||||||
m_amountBox = new QSpinBox();
|
|
||||||
m_amountBox->setMaximum(1000);
|
|
||||||
m_amountLabel->setBuddy(m_amountBox);
|
|
||||||
|
|
||||||
m_factorLabel = new QLabel("&Factor");
|
|
||||||
m_factorBox = new QDoubleSpinBox();
|
|
||||||
m_factorBox->setMaximum(1000);
|
|
||||||
m_factorLabel->setBuddy(m_factorBox);
|
|
||||||
|
|
||||||
QGridLayout* layout = new QGridLayout();
|
|
||||||
layout->addWidget(m_nameLabel, 0, 0, 1, 1);
|
|
||||||
layout->addWidget(m_nameEdit, 0, 1, 1, 1);
|
|
||||||
layout->addWidget(m_descriptionLabel, 1, 0, 1, 1);
|
|
||||||
layout->addWidget(m_descriptionEdit, 1, 1, 1, 1);
|
|
||||||
layout->addWidget(m_infoLabel, 2, 0, 1, 1);
|
|
||||||
layout->addWidget(m_infoEdit, 2, 1, 1, 1);
|
|
||||||
layout->addWidget(m_amountLabel, 3, 0, 1, 1);
|
|
||||||
layout->addWidget(m_amountBox, 3, 1, 1, 1);
|
|
||||||
layout->addWidget(m_factorLabel, 4, 0, 1, 1);
|
|
||||||
layout->addWidget(m_factorBox, 4, 1, 1, 1);
|
|
||||||
|
|
||||||
layout->addWidget(m_previousButton, 5, 0, 1, 1);
|
|
||||||
layout->addWidget(m_nextButton, 5, 1, 1, 1);
|
|
||||||
|
|
||||||
setLayout(layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemDetailMapper::setModelMappings(QTableView* tableView) {
|
|
||||||
qDebug() << "Setting model...";
|
|
||||||
m_tableView = tableView;
|
|
||||||
m_model = tableView->model();
|
|
||||||
m_selectionModel = tableView->selectionModel();
|
|
||||||
|
|
||||||
m_mapper->setModel(m_model);
|
|
||||||
m_mapper->addMapping(m_nameEdit, 0);
|
|
||||||
m_mapper->addMapping(m_descriptionEdit, 1);
|
|
||||||
m_mapper->addMapping(m_infoEdit, 2);
|
|
||||||
m_mapper->addMapping(m_amountBox, 3);
|
|
||||||
m_mapper->addMapping(m_factorBox, 4);
|
|
||||||
|
|
||||||
m_mapper->setCurrentIndex(m_selectionModel->currentIndex().row());
|
|
||||||
|
|
||||||
connect(m_model, &QAbstractItemModel::rowsInserted, this, &ItemDetailMapper::rowsInserted);
|
|
||||||
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ItemDetailMapper::rowsRemoved);
|
|
||||||
connect(m_selectionModel, &QItemSelectionModel::currentChanged, this,
|
|
||||||
&ItemDetailMapper::onCurrentIndexChanged);
|
|
||||||
|
|
||||||
updateButtons(m_selectionModel->currentIndex().row());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ItemDetailMapper::submit() { return m_mapper->submit(); }
|
|
||||||
|
|
||||||
void ItemDetailMapper::revert() { m_mapper->revert(); }
|
|
||||||
|
|
||||||
void ItemDetailMapper::onCurrentIndexChanged(const QModelIndex& current,
|
|
||||||
const QModelIndex& /*previous*/) {
|
|
||||||
if (!isEnabled()) {
|
|
||||||
setEnabled(true);
|
|
||||||
}
|
|
||||||
m_mapper->setCurrentModelIndex(current);
|
|
||||||
updateButtons(current.row());
|
|
||||||
emitContentChanged(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemDetailMapper::rowsInserted(const QModelIndex& parent, int start, int end) {
|
|
||||||
updateButtons(m_mapper->currentIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemDetailMapper::rowsRemoved(const QModelIndex& parent, int start, int end) {
|
|
||||||
if (m_model->rowCount() == 0) {
|
|
||||||
setEnabled(false);
|
|
||||||
|
|
||||||
m_nameEdit->clear();
|
|
||||||
m_descriptionEdit->clear();
|
|
||||||
m_infoEdit->clear();
|
|
||||||
m_amountBox->clear();
|
|
||||||
m_factorBox->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateButtons(m_mapper->currentIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemDetailMapper::toPrevious() {
|
|
||||||
int currentRow = m_selectionModel->currentIndex().row();
|
|
||||||
QModelIndex previousIndex = m_selectionModel->currentIndex().siblingAtRow(currentRow - 1);
|
|
||||||
if (previousIndex.isValid()) {
|
|
||||||
m_selectionModel->setCurrentIndex(previousIndex, QItemSelectionModel::ClearAndSelect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemDetailMapper::toNext() {
|
|
||||||
int currentRow = m_selectionModel->currentIndex().row();
|
|
||||||
QModelIndex nextIndex = m_selectionModel->currentIndex().siblingAtRow(currentRow + 1);
|
|
||||||
if (nextIndex.isValid()) {
|
|
||||||
m_selectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::ClearAndSelect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemDetailMapper::updateButtons(int row) {
|
|
||||||
m_previousButton->setEnabled(row > 0);
|
|
||||||
m_nextButton->setEnabled(row < m_model->rowCount() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemDetailMapper::emitContentChanged(const QModelIndex& currentIndex) {
|
|
||||||
// BUG QR-Code isn't updated after changes through the ItemDetailMapper #18
|
|
||||||
QString toStringText = "";
|
|
||||||
if (currentIndex.isValid()) {
|
|
||||||
toStringText = currentIndex.data(ToStringRole).toString();
|
|
||||||
}
|
|
||||||
emit contentChanged(toStringText);
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
#ifndef ITEMDETAILMAPPER_H
|
|
||||||
#define ITEMDETAILMAPPER_H
|
|
||||||
|
|
||||||
#include <QDataWidgetMapper>
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
class QLabel;
|
|
||||||
class QLineEdit;
|
|
||||||
class QDoubleSpinBox;
|
|
||||||
class QSpinBox;
|
|
||||||
class QPushButton;
|
|
||||||
class QAbstractItemModel;
|
|
||||||
class QItemSelectionModel;
|
|
||||||
class QTableView;
|
|
||||||
|
|
||||||
class ItemDetailMapper : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ItemDetailMapper(QWidget* parent = nullptr);
|
|
||||||
|
|
||||||
void setModelMappings(QTableView* tableView);
|
|
||||||
|
|
||||||
bool submit();
|
|
||||||
void revert();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void contentChanged(const QString text);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onCurrentIndexChanged(const QModelIndex& current, const QModelIndex& previous);
|
|
||||||
void rowsInserted(const QModelIndex& parent, int start, int end);
|
|
||||||
void rowsRemoved(const QModelIndex& parent, int start, int end);
|
|
||||||
void toPrevious();
|
|
||||||
void toNext();
|
|
||||||
void updateButtons(int row);
|
|
||||||
void emitContentChanged(const QModelIndex& currentIndex);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// *** members ***
|
|
||||||
/// Model stuff
|
|
||||||
QTableView* m_tableView = nullptr;
|
|
||||||
QAbstractItemModel* m_model = nullptr;
|
|
||||||
QItemSelectionModel* m_selectionModel = nullptr;
|
|
||||||
|
|
||||||
std::unique_ptr<QDataWidgetMapper> m_mapper;
|
|
||||||
|
|
||||||
/// GUI elements
|
|
||||||
QLabel* m_nameLabel = nullptr;
|
|
||||||
QLineEdit* m_nameEdit = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_descriptionLabel = nullptr;
|
|
||||||
QLineEdit* m_descriptionEdit = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_infoLabel = nullptr;
|
|
||||||
QLineEdit* m_infoEdit = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_amountLabel = nullptr;
|
|
||||||
QSpinBox* m_amountBox = nullptr;
|
|
||||||
|
|
||||||
QLabel* m_factorLabel = nullptr;
|
|
||||||
QDoubleSpinBox* m_factorBox = nullptr;
|
|
||||||
|
|
||||||
QPushButton* m_nextButton;
|
|
||||||
QPushButton* m_previousButton;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ITEMDETAILMAPPER_H
|
|
||||||
@ -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)
|
||||||
@ -23,19 +23,21 @@ add_library(${TARGET_APP} STATIC
|
|||||||
genericcore.h
|
genericcore.h
|
||||||
${TS_FILES}
|
${TS_FILES}
|
||||||
constants.h
|
constants.h
|
||||||
|
structs.h
|
||||||
data/settingshandler.h data/settingshandler.cpp
|
data/settingshandler.h data/settingshandler.cpp
|
||||||
|
data/filehandler.h data/filehandler.cpp
|
||||||
|
formats/jsonparser.h formats/jsonparser.cpp
|
||||||
|
formats/csvparser.h formats/csvparser.cpp
|
||||||
|
model/metadata.h
|
||||||
model/tablemodel.h model/tablemodel.cpp
|
model/tablemodel.h model/tablemodel.cpp
|
||||||
model/modelitem.h model/modelitem.cpp
|
model/modelitem.h model/modelitem.cpp
|
||||||
formats/jsonparser.h formats/jsonparser.cpp
|
model/generalsortfiltermodel.h model/generalsortfiltermodel.cpp
|
||||||
|
model/modelsummary.h model/modelsummary.cpp
|
||||||
model/commands/insertrowscommand.h model/commands/insertrowscommand.cpp
|
model/commands/insertrowscommand.h model/commands/insertrowscommand.cpp
|
||||||
model/commands/removerowscommand.h model/commands/removerowscommand.cpp
|
model/commands/removerowscommand.h model/commands/removerowscommand.cpp
|
||||||
model/commands/edititemcommand.h model/commands/edititemcommand.cpp
|
model/commands/edititemcommand.h model/commands/edititemcommand.cpp
|
||||||
data/filehandler.h data/filehandler.cpp
|
|
||||||
model/metadata.h
|
|
||||||
formats/csvparser.h formats/csvparser.cpp
|
|
||||||
model/generalsortfiltermodel.h model/generalsortfiltermodel.cpp
|
|
||||||
network/servercommunicator.h network/servercommunicator.cpp
|
|
||||||
network/apiroutes.h
|
network/apiroutes.h
|
||||||
|
network/servercommunicator.h network/servercommunicator.cpp
|
||||||
# 3rd party libraries
|
# 3rd party libraries
|
||||||
../3rdParty/rapidcsv/src/rapidcsv.h
|
../3rdParty/rapidcsv/src/rapidcsv.h
|
||||||
)
|
)
|
||||||
@ -43,8 +45,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)
|
||||||
|
|
||||||
@ -23,9 +23,12 @@ QList<ModelItemValues> CsvParser::getItemsFromCSVFile(const QString& fileName) {
|
|||||||
|
|
||||||
bool CsvParser::exportToCSVFile(const QList<QStringList>& rows, const QString& filePath) {
|
bool CsvParser::exportToCSVFile(const QList<QStringList>& rows, const QString& filePath) {
|
||||||
Document doc(std::string(), LabelParams(0, -1));
|
Document doc(std::string(), LabelParams(0, -1));
|
||||||
const QList<QString> headerNames = GET_HEADER_NAMES();
|
// const QList<QString> headerNames = GET_HEADER_NAMES();
|
||||||
for (int column = 0; column < headerNames.size(); ++column) {
|
const int columnCount = USER_FACING_ROLES.size();
|
||||||
doc.SetColumnName(column, headerNames.at(column).toStdString());
|
for (int column = 0; column < columnCount; ++column) {
|
||||||
|
const UserRoles role = GET_ROLE_FOR_COLUMN(column);
|
||||||
|
std::string columnName = ROLE_NAMES.value(role).toStdString();
|
||||||
|
doc.SetColumnName(column, columnName);
|
||||||
}
|
}
|
||||||
for (int row = 0; row < rows.size(); ++row) {
|
for (int row = 0; row < rows.size(); ++row) {
|
||||||
QStringList rowValues = rows.at(row);
|
QStringList rowValues = rows.at(row);
|
||||||
@ -52,6 +55,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 +94,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);
|
||||||
@ -125,8 +132,22 @@ ModelItemValues CsvParser::getItemValuesForRow(
|
|||||||
QVariant CsvParser::parseItemValue(const int role, const std::string& valueString) {
|
QVariant CsvParser::parseItemValue(const int role, const std::string& valueString) {
|
||||||
QVariant result;
|
QVariant result;
|
||||||
if (STRING_ROLES.contains(role)) {
|
if (STRING_ROLES.contains(role)) {
|
||||||
/// string values
|
if (role == ShareTypeRole) {
|
||||||
result = QString::fromStdString(valueString);
|
if (valueString == "AGA") {
|
||||||
|
result = "erarbeitet";
|
||||||
|
} else if (valueString == "ja") {
|
||||||
|
result = "bezahlt";
|
||||||
|
|
||||||
|
} else if (valueString == "teils") {
|
||||||
|
result = "teils/teils";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/// string values
|
||||||
|
result = QString::fromStdString(valueString);
|
||||||
|
}
|
||||||
} else if (INT_ROLES.contains(role)) {
|
} else if (INT_ROLES.contains(role)) {
|
||||||
/// int values
|
/// int values
|
||||||
|
|
||||||
@ -4,6 +4,7 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "../model/metadata.h"
|
#include "../model/metadata.h"
|
||||||
|
#include "../structs.h"
|
||||||
|
|
||||||
QList<ModelItemValues> JsonParser::toItemValuesList(const QByteArray& jsonData,
|
QList<ModelItemValues> JsonParser::toItemValuesList(const QByteArray& jsonData,
|
||||||
const QString& rootValueName) {
|
const QString& rootValueName) {
|
||||||
@ -70,7 +71,64 @@ QByteArray JsonParser::itemValuesListToJson(const QList<ModelItemValues>& itemVa
|
|||||||
return jsonDoc.toJson(QJsonDocument::Compact);
|
return jsonDoc.toJson(QJsonDocument::Compact);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonParser::JsonParser() {}
|
QByteArray JsonParser::toJsonDoc(const QHash<QString, QVariant>& values,
|
||||||
|
const QString& objectName) {
|
||||||
|
QJsonDocument jsonDoc;
|
||||||
|
QJsonObject rootObject;
|
||||||
|
QJsonObject itemObject;
|
||||||
|
|
||||||
|
QHashIterator<QString, QVariant> i(values);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
const QString key = i.key();
|
||||||
|
const QVariant value = i.value();
|
||||||
|
itemObject.insert(key, value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
rootObject.insert(objectName, itemObject);
|
||||||
|
jsonDoc.setObject(rootObject);
|
||||||
|
|
||||||
|
return jsonDoc.toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelItemValues JsonParser::serverUserCredentialsToItemValues(const QJsonDocument& jsonDoc) {
|
||||||
|
ModelItemValues values;
|
||||||
|
|
||||||
|
const QJsonObject rootObject = jsonDoc["data"].toObject();
|
||||||
|
|
||||||
|
const QString emailAddress = rootObject["email"].toString();
|
||||||
|
const QString uuid = rootObject["id"].toString();
|
||||||
|
const QString token = rootObject["token"].toString();
|
||||||
|
|
||||||
|
values.insert(MailRole, emailAddress);
|
||||||
|
values.insert(OnlineIdRole, uuid);
|
||||||
|
values.insert(AccessCodeRole, token);
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<bidding> JsonParser::extractBiddings(const QJsonDocument& jsonDoc) {
|
||||||
|
QList<bidding> result;
|
||||||
|
|
||||||
|
if (jsonDoc.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
QJsonArray itemArray = JsonParser::extractItemArray(jsonDoc, "data");
|
||||||
|
|
||||||
|
foreach (QJsonValue value, itemArray) {
|
||||||
|
// REFACTOR implement & use "JsonParser::parseServerResponse(const QJsonObject& object,
|
||||||
|
// QHash<QString, int> entries)"
|
||||||
|
QJsonObject itemJsonObject = value.toObject();
|
||||||
|
bidding values{.userId = itemJsonObject.value("user_id").toString(),
|
||||||
|
.biddingRound = itemJsonObject.value("bidding_round").toInt(),
|
||||||
|
.amount = itemJsonObject.value("amount").toInt(),
|
||||||
|
.depotWishOne = itemJsonObject.value("depot_wish_one").toString(),
|
||||||
|
.depotWishTwo = itemJsonObject.value("depot_wish_two").toString()};
|
||||||
|
result.append(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonArray JsonParser::extractItemArray(const QJsonDocument& doc, const QString& objectName) {
|
QJsonArray JsonParser::extractItemArray(const QJsonDocument& doc, const QString& objectName) {
|
||||||
QJsonArray itemArray;
|
QJsonArray itemArray;
|
||||||
@ -86,14 +144,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();
|
||||||
@ -103,6 +153,8 @@ ModelItemValues JsonParser::jsonObjectToItemValues(const QJsonObject& itemJsonOb
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonParser::JsonParser() {}
|
||||||
|
|
||||||
pair<int, QVariant> JsonParser::getKeyValuePair(const QJsonObject& itemJsonObject, const int role) {
|
pair<int, QVariant> JsonParser::getKeyValuePair(const QJsonObject& itemJsonObject, const int role) {
|
||||||
QVariant result;
|
QVariant result;
|
||||||
const QJsonValue jsonValue = itemJsonObject[ROLE_NAMES.value(role)];
|
const QJsonValue jsonValue = itemJsonObject[ROLE_NAMES.value(role)];
|
||||||
@ -9,6 +9,7 @@ class QByteArray;
|
|||||||
class QJsonArray;
|
class QJsonArray;
|
||||||
|
|
||||||
typedef QMap<int, QVariant> ModelItemValues;
|
typedef QMap<int, QVariant> ModelItemValues;
|
||||||
|
class bidding;
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@ -18,13 +19,22 @@ class JsonParser {
|
|||||||
const QString& rootValueName = "");
|
const QString& rootValueName = "");
|
||||||
static QByteArray itemValuesListToJson(const QList<ModelItemValues>& itemValuesList,
|
static QByteArray itemValuesListToJson(const QList<ModelItemValues>& itemValuesList,
|
||||||
const QString& objectName = "");
|
const QString& objectName = "");
|
||||||
|
static QByteArray toJsonDoc(const QHash<QString, QVariant>& Values,
|
||||||
|
const QString& objectName = "");
|
||||||
|
|
||||||
private:
|
static ModelItemValues serverUserCredentialsToItemValues(const QJsonDocument& jsonDoc);
|
||||||
explicit JsonParser();
|
static QList<bidding> extractBiddings(const QJsonDocument& jsonDoc);
|
||||||
|
// static ModelItemValues parseServerResponse(const QJsonDocument& jsonDoc,
|
||||||
|
// QHash<QString, int> entries);
|
||||||
|
// static ModelItemValues parseServerResponse(const QJsonObject& object,
|
||||||
|
// QHash<QString, int> entries);
|
||||||
|
|
||||||
static QJsonArray extractItemArray(const QJsonDocument& doc, const QString& objectName);
|
static QJsonArray extractItemArray(const QJsonDocument& doc, const QString& objectName);
|
||||||
static ModelItemValues jsonObjectToItemValues(const QJsonObject& itemJsonObject);
|
static ModelItemValues jsonObjectToItemValues(const QJsonObject& itemJsonObject);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit JsonParser();
|
||||||
|
|
||||||
static pair<int, QVariant> getKeyValuePair(const QJsonObject& itemJsonObject, const int role);
|
static pair<int, QVariant> getKeyValuePair(const QJsonObject& itemJsonObject, const int role);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -8,21 +8,20 @@
|
|||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QtGui/QUndoStack>
|
||||||
|
|
||||||
#include "../../ApplicationConfig.h"
|
#include "../../ApplicationConfig.h"
|
||||||
#include "CoreConfig.h"
|
#include "CoreConfig.h"
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
#include "data/filehandler.h"
|
#include "data/filehandler.h"
|
||||||
#include "data/settingshandler.h"
|
#include "data/settingshandler.h"
|
||||||
|
#include "formats/jsonparser.h"
|
||||||
#include "model/generalsortfiltermodel.h"
|
#include "model/generalsortfiltermodel.h"
|
||||||
#include "model/metadata.h"
|
#include "model/metadata.h"
|
||||||
|
#include "model/modelsummary.h"
|
||||||
#include "model/tablemodel.h"
|
#include "model/tablemodel.h"
|
||||||
#include "network/servercommunicator.h"
|
#include "network/servercommunicator.h"
|
||||||
|
|
||||||
#include <QtGui/QUndoStack>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
GenericCore::GenericCore() {
|
GenericCore::GenericCore() {
|
||||||
qDebug() << "Creating core...";
|
qDebug() << "Creating core...";
|
||||||
|
|
||||||
@ -98,6 +97,8 @@ std::shared_ptr<GeneralSortFilterModel> GenericCore::getSortFilterModel() const
|
|||||||
return m_sortFilterModel;
|
return m_sortFilterModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shared_ptr<ModelSummary> GenericCore::getModelSummary() const { return m_modelSummary; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save items to default file (in standard location).
|
* Save items to default file (in standard location).
|
||||||
* @brief GenericCore::saveItems Saves item fo file.
|
* @brief GenericCore::saveItems Saves item fo file.
|
||||||
@ -155,42 +156,48 @@ bool GenericCore::isSyncServerSetup() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericCore::onSendItemTriggered(const QByteArray& jsonData) {
|
void GenericCore::onLoginSuccessful() {
|
||||||
m_serverCommunicator->postItems(jsonData);
|
qInfo() << "Login successful.";
|
||||||
|
emit displayStatusMessage("Login successful.");
|
||||||
|
emit sendGetRequest(GetCurrentBiddingRound);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericCore::onItemsFetched(const QByteArray jsonData) {
|
void GenericCore::onBiddingsChanged(const QList<bidding> biddings) {
|
||||||
emit displayStatusMessage("New items fetched.");
|
qInfo() << "onBiddingsChanged: biddings:" << biddings.count();
|
||||||
// TODO ? check compability of JSON structure beforehand?
|
// NEXT merge biddings into model
|
||||||
// NEXT check if item already exists and apply changes (UUID,...) ? ;
|
m_mainModel->updateBiddings(biddings);
|
||||||
m_mainModel->appendItems(jsonData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericCore::onItemsFetchFailure(const QString errorString) {
|
void GenericCore::onCreateOnlineAccountTriggered(const QString& mailAddress) {
|
||||||
emit displayStatusMessage(QString("Error: %1").arg(errorString));
|
qInfo() << "Creating online account for:" << mailAddress;
|
||||||
|
QHash<QString, QVariant> hash;
|
||||||
|
hash.insert("email", mailAddress);
|
||||||
|
const QByteArray jsonDoc = JsonParser::toJsonDoc(hash, "user");
|
||||||
|
emit sendPostRequest(RegisterUser, jsonDoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericCore::onPostRequestSuccessful(const QByteArray responseData) {
|
void GenericCore::onOnlineUserAccountReceived(const QString mailAddress,
|
||||||
const QString message = m_mainModel->updateItemsFromJson(responseData);
|
const QString uuid,
|
||||||
|
const QString accessToken) {
|
||||||
|
m_mainModel->setOnlineCredentials(mailAddress, uuid, accessToken);
|
||||||
|
|
||||||
|
const QString message = QString("Online credentials received for: %1").arg(mailAddress);
|
||||||
emit displayStatusMessage(message);
|
emit displayStatusMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericCore::onPostRequestFailure(const QString errorString) {
|
void GenericCore::onSendInviteMailTriggered(const QString& mailAddress) {
|
||||||
emit displayStatusMessage(QString("Error: %1").arg(errorString));
|
qInfo() << "Sending invite mail to:" << mailAddress;
|
||||||
}
|
const QString serverUrl = m_serverCommunicator->getUserLoginUrl();
|
||||||
|
const QJsonDocument mailInviteJsonDoc = m_mainModel->getMailInviteJsonDoc(mailAddress, serverUrl);
|
||||||
void GenericCore::onDeleteRequestSuccessful(const QByteArray responseData) {
|
emit sendPostRequest(MailInvite, mailInviteJsonDoc.toJson());
|
||||||
qWarning() << "TODO: Process success response!!!";
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenericCore::onDeleteRequestFailure(const QString errorString) {
|
|
||||||
qWarning() << "TODO: Process error response!!!";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericCore::setupModels() {
|
void GenericCore::setupModels() {
|
||||||
m_mainModel = make_shared<TableModel>(m_modelUndoStack, this);
|
m_mainModel = make_shared<TableModel>(m_modelUndoStack);
|
||||||
m_sortFilterModel = make_shared<GeneralSortFilterModel>(m_mainModel);
|
m_sortFilterModel = make_shared<GeneralSortFilterModel>(m_mainModel);
|
||||||
|
|
||||||
|
m_modelSummary = make_shared<ModelSummary>(m_mainModel);
|
||||||
|
|
||||||
/// QAbstractItemModelTester
|
/// QAbstractItemModelTester
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
m_mainModelTester = make_unique<QAbstractItemModelTester>(
|
m_mainModelTester = make_unique<QAbstractItemModelTester>(
|
||||||
@ -246,27 +253,6 @@ QString GenericCore::getMaintenanceToolFilePath() const {
|
|||||||
|
|
||||||
void GenericCore::setupServerConfiguration() {
|
void GenericCore::setupServerConfiguration() {
|
||||||
m_serverCommunicator = make_unique<ServerCommunicator>(this);
|
m_serverCommunicator = make_unique<ServerCommunicator>(this);
|
||||||
/// request connections
|
|
||||||
connect(this, &GenericCore::fetchItemsFromServer, m_serverCommunicator.get(),
|
|
||||||
&ServerCommunicator::fetchItems);
|
|
||||||
connect(this, &GenericCore::postItemToServer, this, &GenericCore::onSendItemTriggered);
|
|
||||||
connect(this, &GenericCore::deleteItemFromServer, m_serverCommunicator.get(),
|
|
||||||
&ServerCommunicator::deleteItem);
|
|
||||||
|
|
||||||
/// response connections
|
|
||||||
connect(m_serverCommunicator.get(), &ServerCommunicator::itemsFetched, this,
|
|
||||||
&GenericCore::onItemsFetched);
|
|
||||||
connect(m_serverCommunicator.get(), &ServerCommunicator::itemsFetchFailure, this,
|
|
||||||
&GenericCore::onItemsFetchFailure);
|
|
||||||
connect(m_serverCommunicator.get(), &ServerCommunicator::postRequestSuccessful, this,
|
|
||||||
&GenericCore::onPostRequestSuccessful);
|
|
||||||
connect(m_serverCommunicator.get(), &ServerCommunicator::postRequestFailure, this,
|
|
||||||
&GenericCore::onPostRequestFailure);
|
|
||||||
connect(m_serverCommunicator.get(), &ServerCommunicator::deleteRequestSuccessful, this,
|
|
||||||
&GenericCore::onDeleteRequestSuccessful);
|
|
||||||
connect(m_serverCommunicator.get(), &ServerCommunicator::deleteRequestFailure, this,
|
|
||||||
&GenericCore::onDeleteRequestFailure);
|
|
||||||
|
|
||||||
applyServerConfiguration();
|
applyServerConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,6 +262,7 @@ void GenericCore::applyServerConfiguration() {
|
|||||||
if (!urlValue.isEmpty()) {
|
if (!urlValue.isEmpty()) {
|
||||||
const QString emailValue = serverSettings.value("email").toString();
|
const QString emailValue = serverSettings.value("email").toString();
|
||||||
const QString passwordValue = serverSettings.value("password").toString();
|
const QString passwordValue = serverSettings.value("password").toString();
|
||||||
m_serverCommunicator->setServerConfiguration(urlValue, emailValue, passwordValue);
|
const QString authToken = serverSettings.value("token").toString();
|
||||||
|
m_serverCommunicator->setServerConfiguration(urlValue, emailValue, passwordValue, authToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
95
libs/BeetRoundCore/genericcore.h
Normal file
95
libs/BeetRoundCore/genericcore.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#ifndef GENERICCORE_H
|
||||||
|
#define GENERICCORE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include "model/metadata.h"
|
||||||
|
#include "structs.h"
|
||||||
|
|
||||||
|
class QUndoStack;
|
||||||
|
class QAbstractItemModel;
|
||||||
|
class QAbstractItemModelTester;
|
||||||
|
class QString;
|
||||||
|
|
||||||
|
class TableModel;
|
||||||
|
class GeneralSortFilterModel;
|
||||||
|
class ModelSummary;
|
||||||
|
class ServerCommunicator;
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class GenericCore : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
GenericCore();
|
||||||
|
~GenericCore();
|
||||||
|
|
||||||
|
QString toString() const;
|
||||||
|
void sayHello() const;
|
||||||
|
|
||||||
|
bool isApplicationUpdateAvailable();
|
||||||
|
void triggerApplicationUpdate(const bool saveChanges);
|
||||||
|
|
||||||
|
QUndoStack* getModelUndoStack() const;
|
||||||
|
|
||||||
|
shared_ptr<TableModel> getModel() const;
|
||||||
|
shared_ptr<GeneralSortFilterModel> getSortFilterModel() const;
|
||||||
|
shared_ptr<ModelSummary> getModelSummary() const;
|
||||||
|
|
||||||
|
void saveItems();
|
||||||
|
void importCSVFile(const QString& filePath);
|
||||||
|
bool exportCSVFile(const QString& filePath);
|
||||||
|
|
||||||
|
QVariantMap getSettings(QString group = "") const;
|
||||||
|
void applySettings(QVariantMap settingMap, QString group = "");
|
||||||
|
bool isSyncServerSetup() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onLoginSuccessful();
|
||||||
|
|
||||||
|
void onBiddingsChanged(const QList<bidding> biddings);
|
||||||
|
|
||||||
|
void onCreateOnlineAccountTriggered(const QString& mailAddress);
|
||||||
|
void onOnlineUserAccountReceived(const QString mailAddress,
|
||||||
|
const QString uuid,
|
||||||
|
const QString accessToken);
|
||||||
|
void onSendInviteMailTriggered(const QString& mailAddress);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void displayStatusMessage(QString message);
|
||||||
|
|
||||||
|
/// *** server communication ***
|
||||||
|
void loginAndStoreAuthToken();
|
||||||
|
/// request signals
|
||||||
|
void sendGetRequest(GetRequestTypes type, QVariant data = QVariant());
|
||||||
|
void sendPostRequest(PostRequestTypes type, QByteArray data);
|
||||||
|
/// response signals
|
||||||
|
void currentBiddingRoundChanged(int round, bool isRunning);
|
||||||
|
|
||||||
|
/// deprecated signals
|
||||||
|
void fetchItemsFromServer();
|
||||||
|
void postItemToServer(const QByteArray& jsonData);
|
||||||
|
void deleteItemFromServer(const QString& id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QUndoStack* m_modelUndoStack;
|
||||||
|
shared_ptr<TableModel> m_mainModel;
|
||||||
|
shared_ptr<GeneralSortFilterModel> m_sortFilterModel;
|
||||||
|
shared_ptr<ModelSummary> m_modelSummary;
|
||||||
|
unique_ptr<QAbstractItemModelTester> m_mainModelTester;
|
||||||
|
unique_ptr<QAbstractItemModelTester> m_proxyModelTester;
|
||||||
|
|
||||||
|
void setupModels();
|
||||||
|
void initModelData();
|
||||||
|
|
||||||
|
QString getMaintenanceToolFilePath() const;
|
||||||
|
|
||||||
|
/// Network communication
|
||||||
|
unique_ptr<ServerCommunicator> m_serverCommunicator;
|
||||||
|
void setupServerConfiguration();
|
||||||
|
void applyServerConfiguration();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GENERICCORE_H
|
||||||
@ -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());
|
||||||
@ -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) {
|
||||||
162
libs/BeetRoundCore/model/metadata.h
Normal file
162
libs/BeetRoundCore/model/metadata.h
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
#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";
|
||||||
|
|
||||||
|
/// server communication
|
||||||
|
enum GetRequestTypes {
|
||||||
|
GetCurrentBiddingRound,
|
||||||
|
StartNewBiddingRound,
|
||||||
|
RestartLastBiddingRound,
|
||||||
|
StopCurrentBiddingRound,
|
||||||
|
GetBiddingsOfSpecificRound,
|
||||||
|
GetBiddingsOfHighestRound
|
||||||
|
};
|
||||||
|
enum PostRequestTypes { LogInAdmin, RegisterUser, MailInvite };
|
||||||
|
|
||||||
|
/// 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
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
48
libs/BeetRoundCore/model/modelsummary.cpp
Normal file
48
libs/BeetRoundCore/model/modelsummary.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include "modelsummary.h"
|
||||||
|
|
||||||
|
#include "tablemodel.h"
|
||||||
|
|
||||||
|
ModelSummary::ModelSummary(std::shared_ptr<TableModel> model, QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_model(model) {
|
||||||
|
Q_ASSERT(model);
|
||||||
|
connect(m_model.get(), &TableModel::rowCountChanged, this, &ModelSummary::rowCountChanged);
|
||||||
|
// TODO ? use existing model signals (dataChanged(role),...) instead of special signals
|
||||||
|
connect(m_model.get(), &TableModel::nExpectedBiddingsChanged, this,
|
||||||
|
&ModelSummary::nExpectedBiddingsChanged);
|
||||||
|
connect(m_model.get(), &TableModel::nTotalExpectedSharesChanged, this,
|
||||||
|
&ModelSummary::nTotalExpectedSharesChanged);
|
||||||
|
connect(m_model.get(), &TableModel::nPlacedBiddingsChanged, this,
|
||||||
|
&ModelSummary::nPlacedBiddingsChanged);
|
||||||
|
connect(m_model.get(), &TableModel::biddingSumChanged, this, &ModelSummary::biddingSumChanged);
|
||||||
|
connect(m_model.get(), &TableModel::biddingAverageChanged, this,
|
||||||
|
&ModelSummary::biddingAverageChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelSummary::~ModelSummary() {}
|
||||||
|
|
||||||
|
int ModelSummary::rowCount() const {
|
||||||
|
const int nRows = m_model->rowCount();
|
||||||
|
return nRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
QBindable<int> ModelSummary::bindableRowCount() {
|
||||||
|
m_rowCount = m_model->rowCount();
|
||||||
|
return &m_rowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal ModelSummary::nTotalExpectedShares() const { return m_model->nTotalExpectedShares(); }
|
||||||
|
|
||||||
|
int ModelSummary::nExpectedBiddings() const { return m_model->nExpectedBiddings(); }
|
||||||
|
|
||||||
|
int ModelSummary::nPlacedBiddings1() const { return m_model->nPlacedBiddings1(); }
|
||||||
|
int ModelSummary::nPlacedBiddings2() const { return m_model->nPlacedBiddings2(); }
|
||||||
|
int ModelSummary::nPlacedBiddings3() const { return m_model->nPlacedBiddings3(); }
|
||||||
|
|
||||||
|
int ModelSummary::biddingSum1() const { return m_model->biddingSum1(); }
|
||||||
|
int ModelSummary::biddingSum2() const { return m_model->biddingSum2(); }
|
||||||
|
int ModelSummary::biddingSum3() const { return m_model->biddingSum3(); }
|
||||||
|
|
||||||
|
qreal ModelSummary::biddingAverage1() const { return m_model->biddingAverage1(); }
|
||||||
|
qreal ModelSummary::biddingAverage2() const { return m_model->biddingAverage2(); }
|
||||||
|
qreal ModelSummary::biddingAverage3() const { return m_model->biddingAverage3(); }
|
||||||
52
libs/BeetRoundCore/model/modelsummary.h
Normal file
52
libs/BeetRoundCore/model/modelsummary.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#ifndef MODELSUMMARY_H
|
||||||
|
#define MODELSUMMARY_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QProperty>
|
||||||
|
|
||||||
|
class TableModel;
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class ModelSummary : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged BINDABLE bindableRowCount)
|
||||||
|
|
||||||
|
public:
|
||||||
|
ModelSummary(shared_ptr<TableModel> model, QObject* parent = nullptr);
|
||||||
|
~ModelSummary();
|
||||||
|
|
||||||
|
int rowCount() const;
|
||||||
|
QBindable<int> bindableRowCount();
|
||||||
|
|
||||||
|
qreal nTotalExpectedShares() const;
|
||||||
|
int nExpectedBiddings() const;
|
||||||
|
int nPlacedBiddings1() const;
|
||||||
|
int nPlacedBiddings2() const;
|
||||||
|
int nPlacedBiddings3() const;
|
||||||
|
|
||||||
|
int biddingSum1() const;
|
||||||
|
int biddingSum2() const;
|
||||||
|
int biddingSum3() const;
|
||||||
|
|
||||||
|
qreal biddingAverage1() const;
|
||||||
|
qreal biddingAverage2() const;
|
||||||
|
qreal biddingAverage3() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void rowCountChanged();
|
||||||
|
|
||||||
|
void nExpectedBiddingsChanged();
|
||||||
|
void nTotalExpectedSharesChanged();
|
||||||
|
void nPlacedBiddingsChanged(const int roundNumber);
|
||||||
|
void biddingSumChanged(const int roundNumber);
|
||||||
|
void biddingAverageChanged(const int roundNumber);
|
||||||
|
|
||||||
|
private:
|
||||||
|
shared_ptr<TableModel> m_model;
|
||||||
|
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(ModelSummary, int, m_rowCount, &ModelSummary::rowCountChanged);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MODELSUMMARY_H
|
||||||
739
libs/BeetRoundCore/model/tablemodel.cpp
Normal file
739
libs/BeetRoundCore/model/tablemodel.cpp
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
#include "tablemodel.h"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "../formats/jsonparser.h"
|
||||||
|
#include "../structs.h"
|
||||||
|
#include "commands/edititemcommand.h"
|
||||||
|
#include "commands/insertrowscommand.h"
|
||||||
|
#include "commands/removerowscommand.h"
|
||||||
|
#include "metadata.h"
|
||||||
|
#include "modelitem.h"
|
||||||
|
|
||||||
|
QByteArray TableModel::generateExampleItems() {
|
||||||
|
QJsonDocument doc = QJsonDocument();
|
||||||
|
QJsonObject rootObject;
|
||||||
|
QJsonArray array;
|
||||||
|
|
||||||
|
// TODO use JsonParser for the item generation
|
||||||
|
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(ROLE_NAMES.value(MembershipNumberRole), row);
|
||||||
|
itemObject.insert(ROLE_NAMES.value(LastNameRole), QString("Nachname%1").arg(row));
|
||||||
|
itemObject.insert(ROLE_NAMES.value(FirstNameRole), QString("Vorname%1").arg(row));
|
||||||
|
itemObject.insert(ROLE_NAMES.value(Bidding1Role), 100 + row);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
rootObject.insert(ITEMS_KEY_STRING, array);
|
||||||
|
|
||||||
|
doc.setObject(rootObject);
|
||||||
|
return doc.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableModel::TableModel(QUndoStack* undoStack, QObject* parent)
|
||||||
|
: QAbstractTableModel{parent}
|
||||||
|
, m_undoStack(undoStack) {
|
||||||
|
connect(this, &TableModel::rowsInserted, this, &TableModel::onRowCountChanged);
|
||||||
|
connect(this, &TableModel::rowsRemoved, this, &TableModel::onRowCountChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags TableModel::flags(const QModelIndex& index) const {
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return QAbstractTableModel::flags(index);
|
||||||
|
}
|
||||||
|
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> TableModel::roleNames() const { return ROLE_NAMES; }
|
||||||
|
|
||||||
|
int TableModel::rowCount(const QModelIndex& parent) const {
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0; /// no children
|
||||||
|
}
|
||||||
|
return m_items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int TableModel::columnCount(const QModelIndex& parent) const {
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0; /// no children
|
||||||
|
}
|
||||||
|
return USER_FACING_ROLES.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TableModel::data(const QModelIndex& index, int role) const {
|
||||||
|
const int row = index.row();
|
||||||
|
const int column = index.column();
|
||||||
|
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
if (row >= rowCount(QModelIndex()) || column >= columnCount(QModelIndex())) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int roleForColumn = GET_ROLE_FOR_COLUMN(column);
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
case Qt::EditRole:
|
||||||
|
return m_items.at(row)->data(roleForColumn);
|
||||||
|
case Qt::ToolTipRole:
|
||||||
|
return m_items.at(index.row())->data(ToStringRole);
|
||||||
|
break;
|
||||||
|
case MembershipNumberRole:
|
||||||
|
case LastNameRole:
|
||||||
|
case FirstNameRole:
|
||||||
|
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 JsonObjectRole:
|
||||||
|
return m_items.at(row)->data(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||||
|
if (role == Qt::DisplayRole) {
|
||||||
|
if (orientation == Qt::Horizontal) {
|
||||||
|
const int columnRole = GET_ROLE_FOR_COLUMN(section);
|
||||||
|
const QString headerName = ROLE_NAMES.value(columnRole);
|
||||||
|
return QString("%1").arg(headerName);
|
||||||
|
} else {
|
||||||
|
return QString("%1").arg(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TableModel::setData(const QModelIndex& index, const QVariant& value, int role) {
|
||||||
|
if (role == Qt::EditRole && checkIndex(index)) {
|
||||||
|
const int column = index.column();
|
||||||
|
const int roleForColumn = GET_ROLE_FOR_COLUMN(column);
|
||||||
|
return setItemData(index, {{roleForColumn, value}});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TableModel::setItemData(const QModelIndex& index, const QMap<int, QVariant>& roles) {
|
||||||
|
if (!checkIndex(index)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// if (isRoleReadOnly(roleForColumn)) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
QMap<int, QVariant> changedValues = onlyChangedValues(index, roles);
|
||||||
|
if (changedValues.size() > 0) {
|
||||||
|
EditItemCommand* editCommand = new EditItemCommand(this, index, changedValues);
|
||||||
|
m_undoStack->push(editCommand);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelItemValues TableModel::getItemValues(const QModelIndex& index) const {
|
||||||
|
ModelItemValues values;
|
||||||
|
|
||||||
|
QListIterator<UserRoles> i(USER_FACING_ROLES);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
const UserRoles role = i.next();
|
||||||
|
values.insert(role, data(index, role));
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument TableModel::getAllItemsAsJsonDoc() const {
|
||||||
|
QJsonDocument doc = QJsonDocument();
|
||||||
|
QJsonObject rootObject;
|
||||||
|
QJsonArray array;
|
||||||
|
|
||||||
|
foreach (shared_ptr<ModelItem> item, m_items) {
|
||||||
|
QJsonObject itemObject = item->toJsonObject();
|
||||||
|
array.append(itemObject);
|
||||||
|
}
|
||||||
|
rootObject.insert(ITEMS_KEY_STRING, array);
|
||||||
|
|
||||||
|
doc.setObject(rootObject);
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QStringList> TableModel::getItemsAsStringLists() const {
|
||||||
|
QList<QStringList> result;
|
||||||
|
foreach (shared_ptr<ModelItem> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use item selection as parameter to wrap multiple items into JSON data structure
|
||||||
|
QByteArray TableModel::jsonDataForServer(const QModelIndex& currentIndex) const {
|
||||||
|
const QJsonObject itemObject = data(currentIndex, JsonObjectRole).toJsonObject();
|
||||||
|
QJsonObject rootObject;
|
||||||
|
rootObject.insert(ITEM_KEY_STRING, itemObject);
|
||||||
|
const QJsonDocument jsonDoc(rootObject);
|
||||||
|
return jsonDoc.toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TableModel::updateItemsFromJson(const QByteArray& jsonData) {
|
||||||
|
const QList<ModelItemValues> valueList = JsonParser::toItemValuesList(jsonData, ITEM_KEY_STRING);
|
||||||
|
int nChangedItems = 0;
|
||||||
|
for (const ModelItemValues& itemValues : valueList) {
|
||||||
|
bool updateHappened = updateItem(itemValues);
|
||||||
|
if (updateHappened) {
|
||||||
|
nChangedItems++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString("Found and updated %1 item(s).").arg(nChangedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TableModel::updateItem(const ModelItemValues& itemValues) {
|
||||||
|
QModelIndex foundIndex = searchItemIndex(itemValues);
|
||||||
|
|
||||||
|
qDebug() << "Search done!";
|
||||||
|
if (foundIndex == QModelIndex()) {
|
||||||
|
const QString errorMessage = "No matching item found!";
|
||||||
|
qWarning() << errorMessage;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
qInfo() << "Item found!";
|
||||||
|
/// update existing item
|
||||||
|
setItemData(foundIndex, itemValues);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableModel::setOnlineCredentials(const QString& mail,
|
||||||
|
const QString& uuid,
|
||||||
|
const QString& token) {
|
||||||
|
QModelIndex itemIndex = getIndexByRoleValue(mail, MailRole);
|
||||||
|
setItemData(itemIndex, {{OnlineIdRole, uuid}, {AccessCodeRole, token}});
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument TableModel::getMailInviteJsonDoc(const QString& mail,
|
||||||
|
const QString& serverUrl) const {
|
||||||
|
QJsonDocument doc = QJsonDocument();
|
||||||
|
|
||||||
|
QModelIndex index = getIndexByRoleValue(mail, MailRole);
|
||||||
|
|
||||||
|
if (index.isValid()) {
|
||||||
|
QJsonObject rootObject;
|
||||||
|
|
||||||
|
const QString user_id = data(index, OnlineIdRole).toString();
|
||||||
|
const QString email = data(index, MailRole).toString();
|
||||||
|
const QString name = data(index, FullNameRole).toString();
|
||||||
|
const QString token = data(index, AccessCodeRole).toString();
|
||||||
|
const QString accessUrl = serverUrl + "/" + token;
|
||||||
|
|
||||||
|
QJsonObject userObject;
|
||||||
|
userObject.insert("user_id", user_id);
|
||||||
|
userObject.insert("email", email);
|
||||||
|
userObject.insert("name", name);
|
||||||
|
userObject.insert("access_url", accessUrl);
|
||||||
|
|
||||||
|
rootObject.insert("user", userObject);
|
||||||
|
|
||||||
|
doc.setObject(rootObject);
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableModel::updateBiddings(const QList<bidding> biddings) {
|
||||||
|
QListIterator<bidding> i(biddings);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
const bidding localBidding = i.next();
|
||||||
|
qInfo() << "Processing bidding:";
|
||||||
|
|
||||||
|
const int biddingRound = localBidding.biddingRound;
|
||||||
|
|
||||||
|
if (biddingRound == 0 || biddingRound > 3) {
|
||||||
|
qWarning() << "Bidding round exceeds valid bounds. Ignoring...";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const QString uuid = localBidding.userId;
|
||||||
|
const QModelIndex itemIndex = getIndexByRoleValue(localBidding.userId, OnlineIdRole);
|
||||||
|
if (itemIndex.isValid()) {
|
||||||
|
qInfo() << "Found Uuid for user:" << itemIndex.data(MailRole);
|
||||||
|
const QMap<int, QVariant> values = getItemValues(localBidding);
|
||||||
|
setItemData(itemIndex, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIndex) {
|
||||||
|
if (parentIndex != QModelIndex()) {
|
||||||
|
qWarning() << "Removing of child rows is not supported yet!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int lastRow = firstRow + nRows - 1;
|
||||||
|
if (firstRow < 0 || lastRow >= m_items.size()) {
|
||||||
|
qWarning() << "Trying to remove rows is out of bounds!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveRowsCommand* removeCommand = new RemoveRowsCommand(this, firstRow, nRows);
|
||||||
|
m_undoStack->push(removeCommand);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableModel::appendItems(const QByteArray& jsonDoc) { insertItems(-1, jsonDoc, QModelIndex()); }
|
||||||
|
|
||||||
|
void TableModel::insertItems(int startPosition,
|
||||||
|
const QByteArray& jsonDoc,
|
||||||
|
const QModelIndex& parentIndex) {
|
||||||
|
const QList<ModelItemValues> valueList = JsonParser::toItemValuesList(jsonDoc, ITEMS_KEY_STRING);
|
||||||
|
|
||||||
|
if (valueList.empty()) {
|
||||||
|
/// don't try inserting if no values to insert
|
||||||
|
qDebug() << "No items found in JSON document. Not adding anything...";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertItems(startPosition, valueList, parentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableModel::insertItems(int startPosition,
|
||||||
|
const QList<ModelItemValues>& itemValuesList,
|
||||||
|
const QModelIndex& parentIndex) {
|
||||||
|
qInfo() << "Inserting item(s) into model...";
|
||||||
|
if (parentIndex != QModelIndex()) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertRowsCommand* insertCommand = new InsertRowsCommand(this, startPosition, itemValuesList);
|
||||||
|
m_undoStack->push(insertCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal TableModel::nTotalExpectedShares() const {
|
||||||
|
qreal result = 0;
|
||||||
|
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
|
||||||
|
/// if BiddingTypeRole is not empty
|
||||||
|
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
|
||||||
|
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
|
||||||
|
if (biddingType.isEmpty() || shareAmount == 0.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/// add amount
|
||||||
|
const QString shareType = (*i)->data(ShareTypeRole).toString();
|
||||||
|
if (shareType == "bezahlt") {
|
||||||
|
result += shareAmount;
|
||||||
|
} else if (shareType == "teils/teils") {
|
||||||
|
result += shareAmount / 2;
|
||||||
|
} else {
|
||||||
|
qInfo() << "Share type not 'bezahlt' nor 'teils/teils'. Will be ignored..";
|
||||||
|
qDebug() << "Share type:" << shareType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qInfo() << "Biddings expected for " << result << "shares!";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TableModel::nExpectedBiddings() const {
|
||||||
|
int result = 0;
|
||||||
|
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
|
||||||
|
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
|
||||||
|
if (biddingType.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double shareAmount = (*i)->data(ShareAmountRole).toDouble();
|
||||||
|
if (shareAmount == 0.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const QString shareType = (*i)->data(ShareTypeRole).toString();
|
||||||
|
if (shareType == "bezahlt" || shareType == "teils/teils") {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TableModel::nPlacedBiddings1() const { return nPlacedBiddings(Bidding1Role); }
|
||||||
|
int TableModel::nPlacedBiddings2() const { return nPlacedBiddings(Bidding2Role); }
|
||||||
|
int TableModel::nPlacedBiddings3() const { return nPlacedBiddings(Bidding3Role); }
|
||||||
|
|
||||||
|
int TableModel::biddingSum1() const { return biddingSum(Bidding1Role); }
|
||||||
|
int TableModel::biddingSum2() const { return biddingSum(Bidding2Role); }
|
||||||
|
int TableModel::biddingSum3() const { return biddingSum(Bidding3Role); }
|
||||||
|
|
||||||
|
qreal TableModel::biddingAverage1() const {
|
||||||
|
const UserRoles biddingRole = Bidding1Role;
|
||||||
|
const qreal averageBidding = averageBiddingAmount(biddingRole);
|
||||||
|
qInfo() << "average calculation (1):" << averageBidding;
|
||||||
|
return averageBidding;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal TableModel::biddingAverage2() const {
|
||||||
|
const UserRoles biddingRole = Bidding2Role;
|
||||||
|
const qreal averageBidding = averageBiddingAmount(biddingRole);
|
||||||
|
qInfo() << "average calculation (2):" << averageBidding;
|
||||||
|
return averageBidding;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal TableModel::biddingAverage3() const {
|
||||||
|
const UserRoles biddingRole = Bidding3Role;
|
||||||
|
const qreal averageBidding = averageBiddingAmount(biddingRole);
|
||||||
|
qInfo() << "average calculation (3):" << averageBidding;
|
||||||
|
return averageBidding;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableModel::onRowCountChanged(const QModelIndex& parent, int first, int last) {
|
||||||
|
Q_UNUSED(first);
|
||||||
|
Q_UNUSED(last);
|
||||||
|
|
||||||
|
if (parent != QModelIndex()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit rowCountChanged();
|
||||||
|
emit nExpectedBiddingsChanged();
|
||||||
|
emit nTotalExpectedSharesChanged();
|
||||||
|
|
||||||
|
emit nPlacedBiddingsChanged(1);
|
||||||
|
emit nPlacedBiddingsChanged(2);
|
||||||
|
emit nPlacedBiddingsChanged(3);
|
||||||
|
|
||||||
|
emit biddingSumChanged(1);
|
||||||
|
emit biddingSumChanged(2);
|
||||||
|
emit biddingSumChanged(3);
|
||||||
|
|
||||||
|
emit biddingAverageChanged(1);
|
||||||
|
emit biddingAverageChanged(2);
|
||||||
|
emit biddingAverageChanged(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableModel::execInsertItems(const int firstRow, const QList<ModelItemValues> valueList) {
|
||||||
|
const int nRows = valueList.size();
|
||||||
|
qDebug() << "Inserting" << nRows << "items...";
|
||||||
|
|
||||||
|
const int lastRow = firstRow + nRows - 1;
|
||||||
|
beginInsertRows(QModelIndex(), firstRow, lastRow);
|
||||||
|
for (int row = 0; row < nRows; ++row) {
|
||||||
|
const int rowPosition = firstRow + row;
|
||||||
|
shared_ptr<ModelItem> item = make_unique<ModelItem>(valueList.at(row));
|
||||||
|
m_items.insert(rowPosition, std::move(item));
|
||||||
|
}
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableModel::execRemoveItems(const int firstRow, const int nRows) {
|
||||||
|
const int lastRow = firstRow + nRows - 1;
|
||||||
|
beginRemoveRows(QModelIndex(), firstRow, lastRow);
|
||||||
|
m_items.remove(firstRow, nRows);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableModel::execEditItemData(const int row, const QMap<int, QVariant>& changedValues) {
|
||||||
|
shared_ptr<ModelItem> item = m_items.at(row);
|
||||||
|
bool isDataChanged = item->setItemData(changedValues);
|
||||||
|
|
||||||
|
if (isDataChanged) {
|
||||||
|
/// FIXME due to the mapping from roles to the DisplayRole of different columns the complete
|
||||||
|
/// row is getting notified about (potential) data changes; dataChanged should be called only
|
||||||
|
/// for the affected columns
|
||||||
|
const QModelIndex firstIndex = this->index(row, 0);
|
||||||
|
const QModelIndex lastIndex = this->index(row, USER_FACING_ROLES.size() - 1);
|
||||||
|
QList<int> roles = changedValues.keys();
|
||||||
|
roles.insert(0, Qt::DisplayRole);
|
||||||
|
emit dataChanged(firstIndex, lastIndex, roles.toVector());
|
||||||
|
|
||||||
|
if (roles.contains(BiddingTypeRole) || roles.contains(ShareAmountRole) ||
|
||||||
|
roles.contains(ShareTypeRole)) {
|
||||||
|
emit nExpectedBiddingsChanged();
|
||||||
|
emit nTotalExpectedSharesChanged();
|
||||||
|
}
|
||||||
|
if (roles.contains(ShareAmountRole) || roles.contains(ShareTypeRole)) {
|
||||||
|
emit nPlacedBiddingsChanged(1);
|
||||||
|
emit nPlacedBiddingsChanged(2);
|
||||||
|
emit nPlacedBiddingsChanged(3);
|
||||||
|
emit biddingSumChanged(1);
|
||||||
|
emit biddingSumChanged(2);
|
||||||
|
emit biddingSumChanged(3);
|
||||||
|
emit biddingAverageChanged(1);
|
||||||
|
emit biddingAverageChanged(2);
|
||||||
|
emit biddingAverageChanged(3);
|
||||||
|
} else {
|
||||||
|
/// no changes to share amount or type, but maybe to the biddings:
|
||||||
|
if (roles.contains(Bidding1Role)) {
|
||||||
|
emit nPlacedBiddingsChanged(1);
|
||||||
|
emit biddingSumChanged(1);
|
||||||
|
emit biddingAverageChanged(1);
|
||||||
|
}
|
||||||
|
if (roles.contains(Bidding2Role)) {
|
||||||
|
emit nPlacedBiddingsChanged(2);
|
||||||
|
emit biddingSumChanged(2);
|
||||||
|
emit biddingAverageChanged(2);
|
||||||
|
}
|
||||||
|
if (roles.contains(Bidding3Role)) {
|
||||||
|
emit nPlacedBiddingsChanged(3);
|
||||||
|
emit biddingSumChanged(3);
|
||||||
|
emit biddingAverageChanged(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<int, QVariant> TableModel::onlyChangedValues(const QModelIndex& index,
|
||||||
|
const QMap<int, QVariant>& roleValueMap) const {
|
||||||
|
QMap<int, QVariant> result;
|
||||||
|
QMap<int, QVariant>::const_iterator i;
|
||||||
|
for (i = roleValueMap.constBegin(); i != roleValueMap.constEnd(); ++i) {
|
||||||
|
const int role = i.key();
|
||||||
|
const QVariant newValue = i.value();
|
||||||
|
const QVariant oldValue = index.data(role);
|
||||||
|
// TODO check if role is a editable role?
|
||||||
|
if (oldValue != newValue) {
|
||||||
|
bool emptyValueIsEqualToZero = isEmptyValueEqualToZero(role);
|
||||||
|
if (emptyValueIsEqualToZero && oldValue == QVariant() && newValue == 0) {
|
||||||
|
qDebug() << "oldValue:" << oldValue << "& newValue:" << newValue
|
||||||
|
<< "mean the same. Ignoring...";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
qDebug() << "oldValue:" << oldValue << "!= newValue:" << newValue;
|
||||||
|
result.insert(role, newValue);
|
||||||
|
} else {
|
||||||
|
qInfo() << "oldValue is already the same as newValue:" << oldValue << "-> ignoring...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TableModel::isEmptyValueEqualToZero(const int role) const {
|
||||||
|
if (INT_ROLES.contains(role)) {
|
||||||
|
return true;
|
||||||
|
} else if (DOUBLE_ROLES.contains(role)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex TableModel::searchItemIndex(const ModelItemValues givenItemValues) const {
|
||||||
|
// iterate over indexes to search item : see searchItem(...);
|
||||||
|
// for (const shared_ptr<ModelItem>& item : m_items) {
|
||||||
|
for (int row = 0; row < rowCount(); ++row) {
|
||||||
|
qDebug() << "Processing item at row" << row << "...";
|
||||||
|
QModelIndex itemIndex = index(row, 0);
|
||||||
|
if (isItemEqualToItemValues(itemIndex, givenItemValues)) {
|
||||||
|
qInfo() << "Found item at row" << row << "! Returning its index...";
|
||||||
|
return itemIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qDebug() << "No matching item found. Returning empty pointer...";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex,
|
||||||
|
const ModelItemValues givenItemValues) const {
|
||||||
|
/// do both have a UUID?
|
||||||
|
const UserRoles idRole = OnlineIdRole;
|
||||||
|
QVariant idOfItem = data(itemIndex, idRole);
|
||||||
|
QVariant given = givenItemValues.value(idRole);
|
||||||
|
if (idOfItem.isValid() && given.isValid()) {
|
||||||
|
/// are the UUIDs the same?
|
||||||
|
if (idOfItem.toString() == given.toString()) {
|
||||||
|
qInfo() << "UUIDs are the same.";
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
qDebug() << "UUIDs are NOT the same.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/// are all other values the same? (for now only USER_FACING_ROLES are checked)
|
||||||
|
QListIterator<UserRoles> i(USER_FACING_ROLES);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
const UserRoles role = i.next();
|
||||||
|
const QString roleName = ROLE_NAMES.value(role);
|
||||||
|
const QVariant valueOfItem = data(itemIndex, role);
|
||||||
|
const QVariant givenValue = givenItemValues.value(role);
|
||||||
|
if (STRING_ROLES.contains(role)) {
|
||||||
|
if (valueOfItem.toString() != givenValue.toString()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (INT_ROLES.contains(role)) {
|
||||||
|
if (valueOfItem.toInt() != givenValue.toInt()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (DOUBLE_ROLES.contains(role)) {
|
||||||
|
if (valueOfItem.toDouble() != givenValue.toDouble()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCritical() << QString("Can't find data type for role %1!!!").arg(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TableModel::nPlacedBiddings(const UserRoles biddingRole) const {
|
||||||
|
int result = 0;
|
||||||
|
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
|
||||||
|
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
|
||||||
|
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
|
||||||
|
const QString shareType = (*i)->data(ShareTypeRole).toString();
|
||||||
|
if (biddingType.isEmpty() || shareAmount == 0.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (shareType == "erarbeitet" || shareType == "") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int localBidding = (*i)->data(biddingRole).toInt();
|
||||||
|
if (localBidding > 0) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TableModel::biddingSum(const UserRoles biddingRole) const {
|
||||||
|
int result = 0;
|
||||||
|
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
|
||||||
|
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
|
||||||
|
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
|
||||||
|
const QString shareType = (*i)->data(ShareTypeRole).toString();
|
||||||
|
if (biddingType.isEmpty() || shareAmount == 0.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (shareType == "erarbeitet" || shareType == "") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result += (*i)->data(biddingRole).toInt();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal TableModel::averageBiddingAmount(const UserRoles biddingRole) const {
|
||||||
|
qInfo() << "Calculating average bidding for role:" << ROLE_NAMES.value(biddingRole);
|
||||||
|
const qreal localTotalSharesWithBiddings = totalSharesWithBiddings(biddingRole);
|
||||||
|
|
||||||
|
if (localTotalSharesWithBiddings <= 0) {
|
||||||
|
qInfo() << "No biddings found. Aborting calculation...";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qreal localTotalBiddingAmount = biddingSum(biddingRole);
|
||||||
|
const qreal alternateBiddingAverage = localTotalBiddingAmount / localTotalSharesWithBiddings;
|
||||||
|
qDebug() << "Total bidding amount:" << localTotalBiddingAmount;
|
||||||
|
qDebug() << "Total bidding shares:" << localTotalSharesWithBiddings;
|
||||||
|
return alternateBiddingAverage;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal TableModel::totalSharesWithBiddings(const UserRoles biddingRole) const {
|
||||||
|
qreal result = 0;
|
||||||
|
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
|
||||||
|
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
|
||||||
|
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
|
||||||
|
if (biddingType.isEmpty() || shareAmount == 0.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const int bidding = (*i)->data(biddingRole).toInt();
|
||||||
|
const QString shareType = (*i)->data(ShareTypeRole).toString();
|
||||||
|
const bool isValid = bidding != 0 && shareAmount != 0.0;
|
||||||
|
if (isValid) {
|
||||||
|
// qInfo() << "Including entry in bidding average calculation. MailRole:"
|
||||||
|
// << (*i)->data(MailRole);
|
||||||
|
// qreal bidForWholeShare = 0;
|
||||||
|
if (shareType == "bezahlt") {
|
||||||
|
result += shareAmount;
|
||||||
|
} else if (shareType == "teils/teils") {
|
||||||
|
result += shareAmount / 2;
|
||||||
|
} else {
|
||||||
|
qInfo() << "Share type not 'bezahlt' nor 'teils/teils'. Will be ignored..";
|
||||||
|
qDebug() << "Share type:" << shareType;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qInfo() << "Biddings exist for " << result << "shares!";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex TableModel::getIndexByRoleValue(const QString& valueString, const int role) const {
|
||||||
|
const QString text =
|
||||||
|
QString("Searching list item with value '%1' for role '%2'").arg(valueString, role);
|
||||||
|
qInfo() << text;
|
||||||
|
int itemRow = -1;
|
||||||
|
|
||||||
|
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
|
||||||
|
const QString mailValue = (*i)->data(role).toString();
|
||||||
|
qDebug() << "found value:" << mailValue;
|
||||||
|
if (valueString == mailValue) {
|
||||||
|
itemRow = i - m_items.constBegin();
|
||||||
|
qInfo() << "Found item at row:" << itemRow;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Not the right item. Continuing search...";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (itemRow < 0 || itemRow >= m_items.length()) {
|
||||||
|
qWarning() << "Couldn't find item with:" << valueString << " matching role" << role
|
||||||
|
<< "- Returning...";
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
const QModelIndex itemIndex = index(itemRow, 0);
|
||||||
|
return itemIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<int, QVariant> TableModel::getItemValues(const bidding bid) {
|
||||||
|
const int biddingRound = bid.biddingRound;
|
||||||
|
const int amount = bid.amount;
|
||||||
|
const QString depotWishOne = bid.depotWishOne;
|
||||||
|
const QString depotWishTwo = bid.depotWishTwo;
|
||||||
|
|
||||||
|
UserRoles biddingRole;
|
||||||
|
switch (biddingRound) {
|
||||||
|
case 1:
|
||||||
|
biddingRole = Bidding1Role;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
biddingRole = Bidding2Role;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
biddingRole = Bidding3Role;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (biddingRound == 1) {
|
||||||
|
return {{biddingRole, amount}, {DepotWish1Role, depotWishOne}, {DepotWish2Role, depotWishTwo}};
|
||||||
|
} else {
|
||||||
|
return {{biddingRole, amount}};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,9 @@
|
|||||||
#define TABLEMODEL_H
|
#define TABLEMODEL_H
|
||||||
|
|
||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
|
#include "metadata.h"
|
||||||
|
|
||||||
|
class bidding;
|
||||||
class QUndoStack;
|
class QUndoStack;
|
||||||
class ModelItem;
|
class ModelItem;
|
||||||
|
|
||||||
@ -43,6 +45,11 @@ class TableModel : public QAbstractTableModel {
|
|||||||
QString updateItemsFromJson(const QByteArray& jsonData);
|
QString updateItemsFromJson(const QByteArray& jsonData);
|
||||||
bool updateItem(const ModelItemValues& itemValues);
|
bool updateItem(const ModelItemValues& itemValues);
|
||||||
|
|
||||||
|
void setOnlineCredentials(const QString& mail, const QString& uuid, const QString& token);
|
||||||
|
QJsonDocument getMailInviteJsonDoc(const QString& mail, const QString& serverUrl) const;
|
||||||
|
|
||||||
|
void updateBiddings(const QList<bidding> biddings);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// bool insertRows(int position, int rows, const QModelIndex& parentIndex = QModelIndex())
|
// bool insertRows(int position, int rows, const QModelIndex& parentIndex = QModelIndex())
|
||||||
// override;
|
// override;
|
||||||
@ -55,6 +62,33 @@ class TableModel : public QAbstractTableModel {
|
|||||||
const QList<ModelItemValues>& itemValuesList,
|
const QList<ModelItemValues>& itemValuesList,
|
||||||
const QModelIndex& parentIndex = QModelIndex());
|
const QModelIndex& parentIndex = QModelIndex());
|
||||||
|
|
||||||
|
/// property functions
|
||||||
|
qreal nTotalExpectedShares() const;
|
||||||
|
int nExpectedBiddings() const;
|
||||||
|
int nPlacedBiddings1() const;
|
||||||
|
int nPlacedBiddings2() const;
|
||||||
|
int nPlacedBiddings3() const;
|
||||||
|
|
||||||
|
int biddingSum1() const;
|
||||||
|
int biddingSum2() const;
|
||||||
|
int biddingSum3() const;
|
||||||
|
|
||||||
|
qreal biddingAverage1() const;
|
||||||
|
qreal biddingAverage2() const;
|
||||||
|
qreal biddingAverage3() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void rowCountChanged();
|
||||||
|
|
||||||
|
void nExpectedBiddingsChanged();
|
||||||
|
void nTotalExpectedSharesChanged();
|
||||||
|
void nPlacedBiddingsChanged(const int roundNumber);
|
||||||
|
void biddingSumChanged(const int roundNumber);
|
||||||
|
void biddingAverageChanged(const int roundNumber);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onRowCountChanged(const QModelIndex& parent, int first, int last);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// *** members ***
|
/// *** members ***
|
||||||
// TODO shared_ptr -> unique_ptr
|
// TODO shared_ptr -> unique_ptr
|
||||||
@ -74,6 +108,14 @@ class TableModel : public QAbstractTableModel {
|
|||||||
QModelIndex searchItemIndex(const ModelItemValues givenItemValues) const;
|
QModelIndex searchItemIndex(const ModelItemValues givenItemValues) const;
|
||||||
bool isItemEqualToItemValues(const QModelIndex& itemIndex,
|
bool isItemEqualToItemValues(const QModelIndex& itemIndex,
|
||||||
const ModelItemValues givenItemValues) const;
|
const ModelItemValues givenItemValues) const;
|
||||||
|
|
||||||
|
int nPlacedBiddings(const UserRoles biddingRole) const;
|
||||||
|
int biddingSum(const UserRoles biddingRole) const;
|
||||||
|
qreal averageBiddingAmount(const UserRoles biddingRole) const;
|
||||||
|
qreal totalSharesWithBiddings(const UserRoles biddingRole) const;
|
||||||
|
|
||||||
|
QModelIndex getIndexByRoleValue(const QString& valueString, const int role) const;
|
||||||
|
QMap<int, QVariant> getItemValues(const bidding bid);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TABLEMODEL_H
|
#endif // TABLEMODEL_H
|
||||||
27
libs/BeetRoundCore/network/apiroutes.h
Normal file
27
libs/BeetRoundCore/network/apiroutes.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef APIROUTES_H
|
||||||
|
#define APIROUTES_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
// TODO add namespace
|
||||||
|
|
||||||
|
static const QString ROUTE_USER_LOG_IN = "/log_in";
|
||||||
|
|
||||||
|
static const QString apiPrefix = "/api/";
|
||||||
|
|
||||||
|
static const QString ROUTE_ITEMS = apiPrefix + "items";
|
||||||
|
|
||||||
|
static const QString ROUTE_ADMIN_LOG_IN = apiPrefix + "log_in";
|
||||||
|
|
||||||
|
static const QString ROUTE_REGISTER_USER = apiPrefix + "users";
|
||||||
|
static const QString ROUTE_MAIL_INVITE = apiPrefix + "invite";
|
||||||
|
|
||||||
|
static const QString ROUTE_BIDDINGROUNDS = apiPrefix + "bidding_rounds";
|
||||||
|
static const QString ROUTE_CURRENT_BIDDINGROUND = ROUTE_BIDDINGROUNDS + "/get_current";
|
||||||
|
static const QString ROUTE_START_BIDDINGROUND = ROUTE_BIDDINGROUNDS + "/start_new";
|
||||||
|
static const QString ROUTE_RESTART_BIDDINGROUND = ROUTE_BIDDINGROUNDS + "/restart";
|
||||||
|
static const QString ROUTE_STOP_BIDDINGROUND = ROUTE_BIDDINGROUNDS + "/stop";
|
||||||
|
|
||||||
|
static const QString ROUTE_GET_BIDDINGS_OF_SPECIFIC_ROUND = apiPrefix + "biddings_of_round";
|
||||||
|
static const QString ROUTE_GET_BIDDINGS_OF_HIGHEST_ROUND = apiPrefix + "biddings_of_highest_round";
|
||||||
|
#endif // APIROUTES_H
|
||||||
321
libs/BeetRoundCore/network/servercommunicator.cpp
Normal file
321
libs/BeetRoundCore/network/servercommunicator.cpp
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
#include "servercommunicator.h"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QRestAccessManager>
|
||||||
|
#include <QRestReply>
|
||||||
|
|
||||||
|
#include "../formats/jsonparser.h"
|
||||||
|
#include "../genericcore.h"
|
||||||
|
#include "../structs.h"
|
||||||
|
#include "apiroutes.h"
|
||||||
|
|
||||||
|
#include "../data/settingshandler.h"
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
ServerCommunicator::ServerCommunicator(GenericCore* core)
|
||||||
|
: m_core(core)
|
||||||
|
, QObject{core} {
|
||||||
|
m_netManager.setAutoDeleteReplies(true);
|
||||||
|
m_restManager = std::make_shared<QRestAccessManager>(&m_netManager);
|
||||||
|
m_serviceApi = std::make_shared<QNetworkRequestFactory>();
|
||||||
|
|
||||||
|
connect(core, &GenericCore::loginAndStoreAuthToken, this, &ServerCommunicator::onLoginTriggered);
|
||||||
|
connect(this, &ServerCommunicator::loginSuccessful, core, &GenericCore::onLoginSuccessful);
|
||||||
|
|
||||||
|
connect(m_core, &GenericCore::sendGetRequest, this,
|
||||||
|
&ServerCommunicator::onSendGetRequestTriggered);
|
||||||
|
connect(m_core, &GenericCore::sendPostRequest, this,
|
||||||
|
&ServerCommunicator::onSendPostRequestTriggered);
|
||||||
|
connect(this, &ServerCommunicator::biddingsChanged, m_core, &GenericCore::onBiddingsChanged);
|
||||||
|
connect(this, &ServerCommunicator::onlineUserAccountReceived, m_core,
|
||||||
|
&GenericCore::onOnlineUserAccountReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerCommunicator::sslSupported() const {
|
||||||
|
#if QT_CONFIG(ssl)
|
||||||
|
return QSslSocket::supportsSsl();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl ServerCommunicator::url() const { return m_serviceApi->baseUrl(); }
|
||||||
|
|
||||||
|
void ServerCommunicator::setUrl(const QUrl& url) {
|
||||||
|
if (m_serviceApi->baseUrl() == url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_serviceApi->setBaseUrl(url);
|
||||||
|
QHttpHeaders authenticationHeaders;
|
||||||
|
authenticationHeaders.append(QHttpHeaders::WellKnownHeader::ContentType, "application/json");
|
||||||
|
m_serviceApi->setCommonHeaders(authenticationHeaders);
|
||||||
|
emit urlChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::setServerConfiguration(const QString url,
|
||||||
|
const QString email,
|
||||||
|
const QString password,
|
||||||
|
const QString authToken) {
|
||||||
|
setUrl(url);
|
||||||
|
|
||||||
|
m_email = email;
|
||||||
|
m_password = password;
|
||||||
|
m_authToken = authToken;
|
||||||
|
|
||||||
|
if (!m_authToken.isEmpty()) {
|
||||||
|
m_serviceApi->setBearerToken(authToken.toLatin1());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ServerCommunicator::getUserLoginUrl() const {
|
||||||
|
const QString logInUrl = m_serviceApi->baseUrl().toString() + ROUTE_USER_LOG_IN;
|
||||||
|
return logInUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::onSendGetRequestTriggered(const GetRequestTypes type,
|
||||||
|
QVariant data = QVariant()) {
|
||||||
|
QString path;
|
||||||
|
switch (type) {
|
||||||
|
case GetCurrentBiddingRound:
|
||||||
|
path = ROUTE_CURRENT_BIDDINGROUND;
|
||||||
|
break;
|
||||||
|
case StartNewBiddingRound:
|
||||||
|
path = ROUTE_START_BIDDINGROUND;
|
||||||
|
break;
|
||||||
|
case RestartLastBiddingRound:
|
||||||
|
path = ROUTE_RESTART_BIDDINGROUND;
|
||||||
|
break;
|
||||||
|
case StopCurrentBiddingRound:
|
||||||
|
path = ROUTE_STOP_BIDDINGROUND;
|
||||||
|
break;
|
||||||
|
case GetBiddingsOfSpecificRound:
|
||||||
|
path = ROUTE_GET_BIDDINGS_OF_SPECIFIC_ROUND + "/" + data.toString();
|
||||||
|
break;
|
||||||
|
case GetBiddingsOfHighestRound:
|
||||||
|
path = ROUTE_GET_BIDDINGS_OF_HIGHEST_ROUND;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qWarning() << "No route found for GetRequestType:" << type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO move into default case of switch statement?
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
qDebug() << "Empty path -> Not sending a request.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_serviceApi->setBearerToken(m_authToken.toLatin1());
|
||||||
|
const QNetworkRequest request = m_serviceApi->createRequest(path);
|
||||||
|
|
||||||
|
m_restManager->get(request, this, [this, type](QRestReply& reply) {
|
||||||
|
if (reply.isSuccess()) {
|
||||||
|
qInfo() << "Request successful.";
|
||||||
|
const QJsonDocument doc = reply.readJson().value();
|
||||||
|
onGetReplySuccessful(type, doc);
|
||||||
|
} else {
|
||||||
|
if (reply.hasError()) {
|
||||||
|
const QString errorString = reply.errorString();
|
||||||
|
qWarning() << "Network error:" << errorString;
|
||||||
|
onGetReplyFailure(type, errorString);
|
||||||
|
} else {
|
||||||
|
int statusCode = reply.httpStatus();
|
||||||
|
qWarning() << "Request not successful:" << statusCode;
|
||||||
|
onGetReplyFailure(type, QString("HTTP status code: %1").arg(statusCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::onGetReplySuccessful(const GetRequestTypes type, const QJsonDocument doc) {
|
||||||
|
switch (type) {
|
||||||
|
case GetCurrentBiddingRound:
|
||||||
|
case StartNewBiddingRound:
|
||||||
|
case RestartLastBiddingRound:
|
||||||
|
case StopCurrentBiddingRound:
|
||||||
|
currentBiddingRoundChangedReply(doc);
|
||||||
|
break;
|
||||||
|
case GetBiddingsOfSpecificRound:
|
||||||
|
case GetBiddingsOfHighestRound:
|
||||||
|
currentBiddingsReply(doc);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qWarning() << "Can't match request type:" << type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::onGetReplyFailure(const GetRequestTypes type, const QString errorString) {
|
||||||
|
const QString message =
|
||||||
|
QString("Request of type %1 returned: %2").arg(QString::number(type), errorString);
|
||||||
|
m_core->displayStatusMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::onSendPostRequestTriggered(const PostRequestTypes type,
|
||||||
|
const QByteArray data) {
|
||||||
|
QString path;
|
||||||
|
switch (type) {
|
||||||
|
case LogInAdmin:
|
||||||
|
path = ROUTE_ADMIN_LOG_IN;
|
||||||
|
break;
|
||||||
|
case RegisterUser:
|
||||||
|
path = ROUTE_REGISTER_USER;
|
||||||
|
break;
|
||||||
|
case MailInvite:
|
||||||
|
path = ROUTE_MAIL_INVITE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qWarning() << "No route found for PostRequestType:" << type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO move into default case of switch statement?
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
qDebug() << "Empty path -> Not sending a request.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_serviceApi->setBearerToken(m_authToken.toLatin1());
|
||||||
|
const QNetworkRequest request = m_serviceApi->createRequest(path);
|
||||||
|
|
||||||
|
m_restManager->post(request, data, this, [this, type](QRestReply& reply) {
|
||||||
|
if (reply.isSuccess()) {
|
||||||
|
int statusCode = reply.httpStatus();
|
||||||
|
qInfo() << "Request successful. Status code:" << statusCode;
|
||||||
|
const QJsonDocument doc = reply.readJson().value();
|
||||||
|
onPostReplySuccessful(type, doc);
|
||||||
|
} else {
|
||||||
|
if (reply.hasError()) {
|
||||||
|
const QString errorString = reply.errorString();
|
||||||
|
qWarning() << "Network error:" << errorString;
|
||||||
|
onPostReplyFailure(type, errorString);
|
||||||
|
} else {
|
||||||
|
int statusCode = reply.httpStatus();
|
||||||
|
qWarning() << "Request not successful:" << statusCode;
|
||||||
|
qInfo() << "Content:" << reply.readJson();
|
||||||
|
onPostReplyFailure(type, QString("HTTP status code: %1").arg(statusCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::onPostReplySuccessful(const PostRequestTypes type,
|
||||||
|
const QJsonDocument doc) {
|
||||||
|
switch (type) {
|
||||||
|
case LogInAdmin:
|
||||||
|
qInfo() << "Admin successfully logged in:" << type;
|
||||||
|
handleLogInReply(doc);
|
||||||
|
break;
|
||||||
|
case RegisterUser:
|
||||||
|
qInfo() << "Register user successful:" << type;
|
||||||
|
onlineUserAccountReply(doc);
|
||||||
|
break;
|
||||||
|
case MailInvite:
|
||||||
|
qInfo() << "Mail invite successful sent:" << type;
|
||||||
|
mailInviteSentReply(doc);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qWarning() << "Can't match request type:" << type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::onPostReplyFailure(const PostRequestTypes type,
|
||||||
|
const QString errorString) {
|
||||||
|
const QString message =
|
||||||
|
QString("Request of type %1 returned: %2").arg(QString::number(type), errorString);
|
||||||
|
// NEXT improve error message to the UI;
|
||||||
|
emit m_core->displayStatusMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ServerCommunicator::createLoginBody() {
|
||||||
|
QHash<QString, QVariant> values;
|
||||||
|
values.insert("email", m_email);
|
||||||
|
values.insert("password", m_password);
|
||||||
|
return JsonParser::toJsonDoc(values, "admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::handleLogInReply(const QJsonDocument jsonDoc) {
|
||||||
|
QJsonObject rootObject = jsonDoc.object();
|
||||||
|
bool hasErrors = rootObject.contains(QString("errors"));
|
||||||
|
if (hasErrors) {
|
||||||
|
qCritical() << "Reply has error(s)!";
|
||||||
|
QString errorString = rootObject["errors"].toString();
|
||||||
|
emit m_core->displayStatusMessage(errorString);
|
||||||
|
} else {
|
||||||
|
qInfo() << "Reply has no error(s)!";
|
||||||
|
const QJsonObject dataObject = rootObject["data"].toObject();
|
||||||
|
const QString authToken = dataObject["token"].toString();
|
||||||
|
m_authToken = authToken;
|
||||||
|
|
||||||
|
SettingsHandler::saveSettings({{"token", authToken}}, "Server");
|
||||||
|
|
||||||
|
emit loginSuccessful();
|
||||||
|
emit m_core->displayStatusMessage("Successfully logged in.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::currentBiddingRoundChangedReply(const QJsonDocument jsonDoc) {
|
||||||
|
qInfo() << "Current bidding round received.";
|
||||||
|
// REFACTOR implement & use "JsonParser::parseServerResponse(const QJsonDocument& jsonDoc,
|
||||||
|
// QHash<QString, int> entries)" (generalized version of
|
||||||
|
// "serverUserCredentialsToItemValues")
|
||||||
|
const QJsonObject rootObject = jsonDoc["data"].toObject();
|
||||||
|
const int roundNumber = rootObject["round_number"].toInt();
|
||||||
|
const bool stopped = rootObject["stopped"].toBool();
|
||||||
|
const bool isActive = !stopped;
|
||||||
|
|
||||||
|
// NOTE ?use ServerCommunicator::currentBiddingRoundChanged signal instead of emiting a signal of
|
||||||
|
// the core directly?
|
||||||
|
emit m_core->currentBiddingRoundChanged(roundNumber, isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::currentBiddingsReply(const QJsonDocument jsonDoc) {
|
||||||
|
qInfo() << "currentBiddingsReply:" << jsonDoc;
|
||||||
|
|
||||||
|
const QList<bidding> biddings = JsonParser::extractBiddings(jsonDoc);
|
||||||
|
|
||||||
|
emit biddingsChanged(biddings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::onlineUserAccountReply(const QJsonDocument jsonDoc) {
|
||||||
|
qInfo() << "Online user account received.";
|
||||||
|
ModelItemValues values = JsonParser::serverUserCredentialsToItemValues(jsonDoc);
|
||||||
|
|
||||||
|
emit onlineUserAccountReceived(values[MailRole].toString(), values[OnlineIdRole].toString(),
|
||||||
|
values[AccessCodeRole].toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::mailInviteSentReply(const QJsonDocument jsonDoc) {
|
||||||
|
qInfo() << "Invitation mail successfully sent.";
|
||||||
|
emit m_core->displayStatusMessage("Invitation mail successfully sent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommunicator::onLoginTriggered() {
|
||||||
|
qInfo() << "Login triggered...";
|
||||||
|
if (m_email.isEmpty() || m_password.isEmpty()) {
|
||||||
|
emit m_core->displayStatusMessage(
|
||||||
|
"Missing email or password in settings! Not trying to log in.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_authToken.isEmpty()) {
|
||||||
|
/// get new authToken
|
||||||
|
qInfo() << "Creating a new authToken!";
|
||||||
|
const QByteArray loginBody = createLoginBody();
|
||||||
|
onSendPostRequestTriggered(LogInAdmin, loginBody);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/// try authToken
|
||||||
|
qWarning() << "Validity check of token not implemented yet!!!";
|
||||||
|
qInfo() << "Assuming validity of token...";
|
||||||
|
|
||||||
|
emit loginSuccessful();
|
||||||
|
// TODO try validity of token and trigger log_in if not valid
|
||||||
|
// try default route to test access;
|
||||||
|
// if (access denied) {
|
||||||
|
// m_apiClient->sendAPIRequestPost(ROUTE_LOG_IN_ADMIN, );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
82
libs/BeetRoundCore/network/servercommunicator.h
Normal file
82
libs/BeetRoundCore/network/servercommunicator.h
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#ifndef SERVERCOMMUNICATOR_H
|
||||||
|
#define SERVERCOMMUNICATOR_H
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkRequestFactory>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QRestAccessManager>
|
||||||
|
|
||||||
|
#include "../model/metadata.h"
|
||||||
|
|
||||||
|
class bidding;
|
||||||
|
class GenericCore;
|
||||||
|
|
||||||
|
class ServerCommunicator : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ServerCommunicator(GenericCore* core);
|
||||||
|
|
||||||
|
bool sslSupported() const;
|
||||||
|
|
||||||
|
QUrl url() const;
|
||||||
|
void setUrl(const QUrl& url);
|
||||||
|
|
||||||
|
void setServerConfiguration(const QString url,
|
||||||
|
const QString email,
|
||||||
|
const QString password,
|
||||||
|
const QString authToken);
|
||||||
|
QString getUserLoginUrl() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onLoginTriggered();
|
||||||
|
|
||||||
|
void onSendGetRequestTriggered(const GetRequestTypes type, QVariant data);
|
||||||
|
void onGetReplySuccessful(const GetRequestTypes type, const QJsonDocument doc);
|
||||||
|
void onGetReplyFailure(const GetRequestTypes type, const QString errorString);
|
||||||
|
|
||||||
|
void onSendPostRequestTriggered(const PostRequestTypes type, const QByteArray data);
|
||||||
|
void onPostReplySuccessful(const PostRequestTypes type, const QJsonDocument doc);
|
||||||
|
void onPostReplyFailure(const PostRequestTypes type, const QString errorString);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void urlChanged();
|
||||||
|
|
||||||
|
void itemsFetched(const QByteArray jsonDoc);
|
||||||
|
void itemsFetchFailure(const QString errorString);
|
||||||
|
void postRequestSuccessful(const QByteArray responseData);
|
||||||
|
void postRequestFailure(const QString errorString);
|
||||||
|
void deleteRequestSuccessful(const QByteArray responseData);
|
||||||
|
void deleteRequestFailure(const QString errorString);
|
||||||
|
|
||||||
|
void loginSuccessful();
|
||||||
|
|
||||||
|
void currentBiddingRoundChanged(int round, bool isRunning);
|
||||||
|
void biddingsChanged(QList<bidding> biddings);
|
||||||
|
void onlineUserAccountReceived(const QString mailAddress,
|
||||||
|
const QString uuid,
|
||||||
|
const QString accessToken);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GenericCore* m_core = nullptr;
|
||||||
|
|
||||||
|
QNetworkAccessManager m_netManager;
|
||||||
|
std::shared_ptr<QRestAccessManager> m_restManager;
|
||||||
|
std::shared_ptr<QNetworkRequestFactory> m_serviceApi;
|
||||||
|
|
||||||
|
QString m_email;
|
||||||
|
QString m_password;
|
||||||
|
QString m_authToken;
|
||||||
|
|
||||||
|
QByteArray createLoginBody();
|
||||||
|
|
||||||
|
/// reply parser
|
||||||
|
void handleLogInReply(const QJsonDocument jsonDoc);
|
||||||
|
|
||||||
|
void currentBiddingRoundChangedReply(const QJsonDocument jsonDoc);
|
||||||
|
void currentBiddingsReply(const QJsonDocument jsonDoc);
|
||||||
|
void onlineUserAccountReply(const QJsonDocument jsonDoc);
|
||||||
|
void mailInviteSentReply(const QJsonDocument jsonDoc);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SERVERCOMMUNICATOR_H
|
||||||
14
libs/BeetRoundCore/structs.h
Normal file
14
libs/BeetRoundCore/structs.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef STRUCTS_H
|
||||||
|
#define STRUCTS_H
|
||||||
|
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
struct bidding {
|
||||||
|
QString userId;
|
||||||
|
int biddingRound;
|
||||||
|
int amount;
|
||||||
|
QString depotWishOne;
|
||||||
|
QString depotWishTwo;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STRUCTS_H
|
||||||
@ -1,73 +0,0 @@
|
|||||||
#ifndef GENERICCORE_H
|
|
||||||
#define GENERICCORE_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
class QUndoStack;
|
|
||||||
class QAbstractItemModel;
|
|
||||||
class QAbstractItemModelTester;
|
|
||||||
class QString;
|
|
||||||
|
|
||||||
class TableModel;
|
|
||||||
class GeneralSortFilterModel;
|
|
||||||
class ServerCommunicator;
|
|
||||||
|
|
||||||
class GenericCore : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
GenericCore();
|
|
||||||
~GenericCore();
|
|
||||||
|
|
||||||
QString toString() const;
|
|
||||||
void sayHello() const;
|
|
||||||
|
|
||||||
bool isApplicationUpdateAvailable();
|
|
||||||
void triggerApplicationUpdate(const bool saveChanges);
|
|
||||||
|
|
||||||
QUndoStack* getModelUndoStack() const;
|
|
||||||
std::shared_ptr<TableModel> getModel() const;
|
|
||||||
std::shared_ptr<GeneralSortFilterModel> getSortFilterModel() const;
|
|
||||||
|
|
||||||
void saveItems();
|
|
||||||
void importCSVFile(const QString& filePath);
|
|
||||||
bool exportCSVFile(const QString& filePath);
|
|
||||||
|
|
||||||
QVariantMap getSettings(QString group = "") const;
|
|
||||||
void applySettings(QVariantMap settingMap, QString group = "");
|
|
||||||
bool isSyncServerSetup() const;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void onSendItemTriggered(const QByteArray& jsonData);
|
|
||||||
void onItemsFetched(const QByteArray jsonData);
|
|
||||||
void onItemsFetchFailure(const QString errorString);
|
|
||||||
void onPostRequestSuccessful(const QByteArray responseData);
|
|
||||||
void onPostRequestFailure(const QString errorString);
|
|
||||||
void onDeleteRequestSuccessful(const QByteArray responseData);
|
|
||||||
void onDeleteRequestFailure(const QString errorString);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void displayStatusMessage(QString message);
|
|
||||||
void fetchItemsFromServer();
|
|
||||||
void postItemToServer(const QByteArray& jsonData);
|
|
||||||
void deleteItemFromServer(const QString& id);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QUndoStack* m_modelUndoStack;
|
|
||||||
std::shared_ptr<TableModel> m_mainModel;
|
|
||||||
std::shared_ptr<GeneralSortFilterModel> m_sortFilterModel;
|
|
||||||
std::unique_ptr<QAbstractItemModelTester> m_mainModelTester;
|
|
||||||
std::unique_ptr<QAbstractItemModelTester> m_proxyModelTester;
|
|
||||||
|
|
||||||
void setupModels();
|
|
||||||
void initModelData();
|
|
||||||
|
|
||||||
QString getMaintenanceToolFilePath() const;
|
|
||||||
|
|
||||||
/// Network communication
|
|
||||||
std::unique_ptr<ServerCommunicator> m_serverCommunicator;
|
|
||||||
void setupServerConfiguration();
|
|
||||||
void applyServerConfiguration();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // GENERICCORE_H
|
|
||||||
@ -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
|
|
||||||
@ -1,390 +0,0 @@
|
|||||||
#include "tablemodel.h"
|
|
||||||
|
|
||||||
#include "../formats/jsonparser.h"
|
|
||||||
#include "commands/edititemcommand.h"
|
|
||||||
#include "commands/insertrowscommand.h"
|
|
||||||
#include "commands/removerowscommand.h"
|
|
||||||
#include "metadata.h"
|
|
||||||
#include "modelitem.h"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
QByteArray TableModel::generateExampleItems() {
|
|
||||||
QJsonDocument doc = QJsonDocument();
|
|
||||||
QJsonObject rootObject;
|
|
||||||
QJsonArray array;
|
|
||||||
|
|
||||||
// TODO use JsonParser for the item generation
|
|
||||||
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(ROLE_NAMES.value(NameRole), QString("Item %1").arg(row));
|
|
||||||
itemObject.insert(ROLE_NAMES.value(DescriptionRole), QString("This is item %1").arg(row));
|
|
||||||
itemObject.insert(ROLE_NAMES.value(InfoRole), QString("Info of item %1").arg(row));
|
|
||||||
itemObject.insert(ROLE_NAMES.value(AmountRole), row);
|
|
||||||
itemObject.insert(ROLE_NAMES.value(FactorRole), row * 1.1);
|
|
||||||
|
|
||||||
array.append(itemObject);
|
|
||||||
}
|
|
||||||
rootObject.insert(ITEMS_KEY_STRING, array);
|
|
||||||
|
|
||||||
doc.setObject(rootObject);
|
|
||||||
return doc.toJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
TableModel::TableModel(QUndoStack* undoStack, QObject* parent)
|
|
||||||
: QAbstractTableModel{parent}
|
|
||||||
, m_undoStack(undoStack) {}
|
|
||||||
|
|
||||||
Qt::ItemFlags TableModel::flags(const QModelIndex& index) const {
|
|
||||||
if (!index.isValid()) {
|
|
||||||
return QAbstractTableModel::flags(index);
|
|
||||||
}
|
|
||||||
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> TableModel::roleNames() const { return ROLE_NAMES; }
|
|
||||||
|
|
||||||
int TableModel::rowCount(const QModelIndex& parent) const {
|
|
||||||
if (parent.isValid()) {
|
|
||||||
return 0; /// no children
|
|
||||||
}
|
|
||||||
return m_items.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
int TableModel::columnCount(const QModelIndex& parent) const {
|
|
||||||
if (parent.isValid()) {
|
|
||||||
return 0; /// no children
|
|
||||||
}
|
|
||||||
return USER_FACING_ROLES.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant TableModel::data(const QModelIndex& index, int role) const {
|
|
||||||
const int row = index.row();
|
|
||||||
const int column = index.column();
|
|
||||||
|
|
||||||
if (!index.isValid()) {
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
if (row >= rowCount(QModelIndex()) || column >= columnCount(QModelIndex())) {
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
int roleForColumn = GET_ROLE_FOR_COLUMN(column);
|
|
||||||
switch (role) {
|
|
||||||
case Qt::DisplayRole:
|
|
||||||
case Qt::EditRole:
|
|
||||||
return m_items.at(row)->data(roleForColumn);
|
|
||||||
case NameRole:
|
|
||||||
case DescriptionRole:
|
|
||||||
case InfoRole:
|
|
||||||
case AmountRole:
|
|
||||||
case FactorRole:
|
|
||||||
case IdRole:
|
|
||||||
return m_items.at(row)->data(role);
|
|
||||||
case ToStringRole:
|
|
||||||
return m_items.at(row)->toString();
|
|
||||||
case ToJsonRole:
|
|
||||||
return m_items.at(row)->toJsonObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
|
||||||
if (role == Qt::DisplayRole) {
|
|
||||||
if (orientation == Qt::Horizontal) {
|
|
||||||
const int columnRole = GET_ROLE_FOR_COLUMN(section);
|
|
||||||
const QString headerName = ROLE_NAMES.value(columnRole);
|
|
||||||
return QString("%1").arg(headerName);
|
|
||||||
} else {
|
|
||||||
return QString("%1").arg(section);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TableModel::setData(const QModelIndex& index, const QVariant& value, int role) {
|
|
||||||
if (role == Qt::EditRole && checkIndex(index)) {
|
|
||||||
const int column = index.column();
|
|
||||||
const int roleForColumn = GET_ROLE_FOR_COLUMN(column);
|
|
||||||
return setItemData(index, {{roleForColumn, value}});
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TableModel::setItemData(const QModelIndex& index, const QMap<int, QVariant>& roles) {
|
|
||||||
if (!checkIndex(index)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// if (isRoleReadOnly(roleForColumn)) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
QMap<int, QVariant> changedValues = onlyChangedValues(index, roles);
|
|
||||||
if (changedValues.size() > 0) {
|
|
||||||
EditItemCommand* editCommand = new EditItemCommand(this, index, changedValues);
|
|
||||||
m_undoStack->push(editCommand);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModelItemValues TableModel::getItemValues(const QModelIndex& index) const {
|
|
||||||
ModelItemValues values;
|
|
||||||
|
|
||||||
QListIterator<UserRoles> i(USER_FACING_ROLES);
|
|
||||||
while (i.hasNext()) {
|
|
||||||
const UserRoles role = i.next();
|
|
||||||
values.insert(role, data(index, role));
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonDocument TableModel::getAllItemsAsJsonDoc() const {
|
|
||||||
QJsonDocument doc = QJsonDocument();
|
|
||||||
QJsonObject rootObject;
|
|
||||||
QJsonArray array;
|
|
||||||
|
|
||||||
foreach (shared_ptr<ModelItem> item, m_items) {
|
|
||||||
QJsonObject itemObject = item->toJsonObject();
|
|
||||||
array.append(itemObject);
|
|
||||||
}
|
|
||||||
rootObject.insert(ITEMS_KEY_STRING, array);
|
|
||||||
|
|
||||||
doc.setObject(rootObject);
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QStringList> TableModel::getItemsAsStringLists() const {
|
|
||||||
QList<QStringList> result;
|
|
||||||
foreach (shared_ptr<ModelItem> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO use item selection as parameter to wrap multiple items into JSON data structure
|
|
||||||
QByteArray TableModel::jsonDataForServer(const QModelIndex& currentIndex) const {
|
|
||||||
const QJsonObject itemObject = data(currentIndex, ToJsonRole).toJsonObject();
|
|
||||||
QJsonObject rootObject;
|
|
||||||
rootObject.insert(ITEM_KEY_STRING, itemObject);
|
|
||||||
const QJsonDocument jsonDoc(rootObject);
|
|
||||||
return jsonDoc.toJson(QJsonDocument::Compact);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TableModel::updateItemsFromJson(const QByteArray& jsonData) {
|
|
||||||
const QList<ModelItemValues> valueList = JsonParser::toItemValuesList(jsonData, ITEM_KEY_STRING);
|
|
||||||
int nChangedItems = 0;
|
|
||||||
for (const ModelItemValues& itemValues : valueList) {
|
|
||||||
bool updateHappened = updateItem(itemValues);
|
|
||||||
if (updateHappened) {
|
|
||||||
nChangedItems++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString("Found and updated %1 item(s).").arg(nChangedItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TableModel::updateItem(const ModelItemValues& itemValues) {
|
|
||||||
QModelIndex foundIndex = searchItemIndex(itemValues);
|
|
||||||
|
|
||||||
qDebug() << "Search done!";
|
|
||||||
if (foundIndex == QModelIndex()) {
|
|
||||||
const QString errorMessage = "No matching item found!";
|
|
||||||
qWarning() << errorMessage;
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
qInfo() << "Item found!";
|
|
||||||
/// update existing item
|
|
||||||
setItemData(foundIndex, itemValues);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIndex) {
|
|
||||||
if (parentIndex != QModelIndex()) {
|
|
||||||
qWarning() << "Removing of child rows is not supported yet!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int lastRow = firstRow + nRows - 1;
|
|
||||||
if (firstRow < 0 || lastRow >= m_items.size()) {
|
|
||||||
qWarning() << "Trying to remove rows is out of bounds!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveRowsCommand* removeCommand = new RemoveRowsCommand(this, firstRow, nRows);
|
|
||||||
m_undoStack->push(removeCommand);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableModel::appendItems(const QByteArray& jsonDoc) { insertItems(-1, jsonDoc, QModelIndex()); }
|
|
||||||
|
|
||||||
void TableModel::insertItems(int startPosition,
|
|
||||||
const QByteArray& jsonDoc,
|
|
||||||
const QModelIndex& parentIndex) {
|
|
||||||
const QList<ModelItemValues> valueList = JsonParser::toItemValuesList(jsonDoc, ITEMS_KEY_STRING);
|
|
||||||
|
|
||||||
if (valueList.empty()) {
|
|
||||||
/// don't try inserting if no values to insert
|
|
||||||
qDebug() << "No items found in JSON document. Not adding anything...";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertItems(startPosition, valueList, parentIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableModel::insertItems(int startPosition,
|
|
||||||
const QList<ModelItemValues>& itemValuesList,
|
|
||||||
const QModelIndex& parentIndex) {
|
|
||||||
qInfo() << "Inserting item(s) into model...";
|
|
||||||
if (parentIndex != QModelIndex()) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
InsertRowsCommand* insertCommand = new InsertRowsCommand(this, startPosition, itemValuesList);
|
|
||||||
m_undoStack->push(insertCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableModel::execInsertItems(const int firstRow, const QList<ModelItemValues> valueList) {
|
|
||||||
const int nRows = valueList.size();
|
|
||||||
qDebug() << "Inserting" << nRows << "items...";
|
|
||||||
|
|
||||||
const int lastRow = firstRow + nRows - 1;
|
|
||||||
beginInsertRows(QModelIndex(), firstRow, lastRow);
|
|
||||||
for (int row = 0; row < nRows; ++row) {
|
|
||||||
const int rowPosition = firstRow + row;
|
|
||||||
shared_ptr<ModelItem> item = make_unique<ModelItem>(valueList.at(row));
|
|
||||||
m_items.insert(rowPosition, std::move(item));
|
|
||||||
}
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableModel::execRemoveItems(const int firstRow, const int nRows) {
|
|
||||||
const int lastRow = firstRow + nRows - 1;
|
|
||||||
beginRemoveRows(QModelIndex(), firstRow, lastRow);
|
|
||||||
m_items.remove(firstRow, nRows);
|
|
||||||
endRemoveRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableModel::execEditItemData(const int row, const QMap<int, QVariant>& changedValues) {
|
|
||||||
shared_ptr<ModelItem> item = m_items.at(row);
|
|
||||||
bool isDataChanged = item->setItemData(changedValues);
|
|
||||||
|
|
||||||
if (isDataChanged) {
|
|
||||||
/// FIXME due to the mapping from roles to the DisplayRole of different columns the complete row
|
|
||||||
/// is getting notified about (potential) data changes; dataChanged should be called only for
|
|
||||||
/// the affected columns
|
|
||||||
const QModelIndex firstIndex = this->index(row, 0);
|
|
||||||
const QModelIndex lastIndex = this->index(row, USER_FACING_ROLES.size() - 1);
|
|
||||||
QList<int> roles = changedValues.keys();
|
|
||||||
roles.insert(0, Qt::DisplayRole);
|
|
||||||
emit dataChanged(firstIndex, lastIndex, roles.toVector());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QMap<int, QVariant> TableModel::onlyChangedValues(const QModelIndex& index,
|
|
||||||
const QMap<int, QVariant>& roleValueMap) const {
|
|
||||||
QMap<int, QVariant> result;
|
|
||||||
QMap<int, QVariant>::const_iterator i;
|
|
||||||
for (i = roleValueMap.constBegin(); i != roleValueMap.constEnd(); ++i) {
|
|
||||||
const int role = i.key();
|
|
||||||
const QVariant newValue = i.value();
|
|
||||||
const QVariant oldValue = index.data(role);
|
|
||||||
// TODO check if role is a editable role?
|
|
||||||
if (oldValue != newValue) {
|
|
||||||
bool emptyValueIsEqualToZero = isEmptyValueEqualToZero(role);
|
|
||||||
if (emptyValueIsEqualToZero && oldValue == QVariant() && newValue == 0) {
|
|
||||||
qDebug() << "oldValue:" << oldValue << "& newValue:" << newValue
|
|
||||||
<< "mean the same. Ignoring...";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
qDebug() << "oldValue:" << oldValue << "!= newValue:" << newValue;
|
|
||||||
result.insert(role, newValue);
|
|
||||||
} else {
|
|
||||||
qInfo() << "oldValue is already the same as newValue:" << oldValue << "-> ignoring...";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TableModel::isEmptyValueEqualToZero(const int role) const {
|
|
||||||
if (INT_ROLES.contains(role)) {
|
|
||||||
return true;
|
|
||||||
} else if (DOUBLE_ROLES.contains(role)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex TableModel::searchItemIndex(const ModelItemValues givenItemValues) const {
|
|
||||||
// iterate over indexes to search item : see searchItem(...);
|
|
||||||
// for (const shared_ptr<ModelItem>& item : m_items) {
|
|
||||||
for (int row = 0; row < rowCount(); ++row) {
|
|
||||||
qDebug() << "Processing item at row" << row << "...";
|
|
||||||
QModelIndex itemIndex = index(row, 0);
|
|
||||||
if (isItemEqualToItemValues(itemIndex, givenItemValues)) {
|
|
||||||
qInfo() << "Found item at row" << row << "! Returning its index...";
|
|
||||||
return itemIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
qDebug() << "No matching item found. Returning empty pointer...";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex,
|
|
||||||
const ModelItemValues givenItemValues) const {
|
|
||||||
/// do both have a UUID?
|
|
||||||
QVariant idOfItem = data(itemIndex, IdRole);
|
|
||||||
QVariant given = givenItemValues.value(IdRole);
|
|
||||||
if (idOfItem.isValid() && given.isValid()) {
|
|
||||||
/// are the UUIDs the same?
|
|
||||||
if (idOfItem.toString() == given.toString()) {
|
|
||||||
qInfo() << "UUIDs are the same.";
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
qDebug() << "UUIDs are NOT the same.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/// are all other values the same? (for now only USER_FACING_ROLES are checked)
|
|
||||||
QListIterator<UserRoles> i(USER_FACING_ROLES);
|
|
||||||
while (i.hasNext()) {
|
|
||||||
const UserRoles role = i.next();
|
|
||||||
const QString roleName = ROLE_NAMES.value(role);
|
|
||||||
const QVariant valueOfItem = data(itemIndex, role);
|
|
||||||
const QVariant givenValue = givenItemValues.value(role);
|
|
||||||
if (STRING_ROLES.contains(role)) {
|
|
||||||
if (valueOfItem.toString() != givenValue.toString()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (INT_ROLES.contains(role)) {
|
|
||||||
if (valueOfItem.toInt() != givenValue.toInt()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (DOUBLE_ROLES.contains(role)) {
|
|
||||||
if (valueOfItem.toDouble() != givenValue.toDouble()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCritical() << QString("Can't find data type for role %1!!!").arg(role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#ifndef APIROUTES_H
|
|
||||||
#define APIROUTES_H
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
// TODO add namespace
|
|
||||||
|
|
||||||
static const QString apiPrefix = "/api/";
|
|
||||||
|
|
||||||
static const QString ROUTE_ITEMS = apiPrefix + "items";
|
|
||||||
|
|
||||||
#endif // APIROUTES_H
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
#include "servercommunicator.h"
|
|
||||||
#include "apiroutes.h"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QRestReply>
|
|
||||||
|
|
||||||
using namespace Qt::StringLiterals;
|
|
||||||
|
|
||||||
ServerCommunicator::ServerCommunicator(QObject* parent)
|
|
||||||
: QObject{parent} {
|
|
||||||
m_netManager.setAutoDeleteReplies(true);
|
|
||||||
m_restManager = std::make_shared<QRestAccessManager>(&m_netManager);
|
|
||||||
m_serviceApi = std::make_shared<QNetworkRequestFactory>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServerCommunicator::sslSupported() {
|
|
||||||
#if QT_CONFIG(ssl)
|
|
||||||
return QSslSocket::supportsSsl();
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl ServerCommunicator::url() const { return m_serviceApi->baseUrl(); }
|
|
||||||
|
|
||||||
void ServerCommunicator::setUrl(const QUrl& url) {
|
|
||||||
if (m_serviceApi->baseUrl() == url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_serviceApi->setBaseUrl(url);
|
|
||||||
QHttpHeaders authenticationHeaders;
|
|
||||||
authenticationHeaders.append(QHttpHeaders::WellKnownHeader::ContentType, "application/json");
|
|
||||||
m_serviceApi->setCommonHeaders(authenticationHeaders);
|
|
||||||
emit urlChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCommunicator::fetchItems() {
|
|
||||||
/// Set up a GET request
|
|
||||||
m_restManager->get(m_serviceApi->createRequest(ROUTE_ITEMS), this, [this](QRestReply& reply) {
|
|
||||||
if (reply.isSuccess()) {
|
|
||||||
qInfo() << "Fetching items successful.";
|
|
||||||
const QJsonDocument doc = reply.readJson().value();
|
|
||||||
emit itemsFetched(doc.toJson());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (reply.hasError()) {
|
|
||||||
const QString errorString = reply.errorString();
|
|
||||||
qCritical() << "ERROR:" << errorString;
|
|
||||||
emit itemsFetchFailure(errorString);
|
|
||||||
} else {
|
|
||||||
int statusCode = reply.httpStatus();
|
|
||||||
qCritical() << "ERROR:" << statusCode;
|
|
||||||
emit itemsFetchFailure(QString::number(statusCode));
|
|
||||||
emit itemsFetchFailure(QString("HTTP status code: %1").arg(statusCode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCommunicator::postItems(const QByteArray& jsonData) {
|
|
||||||
QNetworkReply* reply = m_restManager->post(m_serviceApi->createRequest(ROUTE_ITEMS), jsonData);
|
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [=]() {
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
const QString message = QString("POST successful! Response: %1").arg(responseData);
|
|
||||||
qInfo() << message;
|
|
||||||
emit postRequestSuccessful(responseData);
|
|
||||||
} else {
|
|
||||||
const QString message = QString("Error: %1").arg(reply->errorString());
|
|
||||||
qDebug() << message;
|
|
||||||
emit postRequestFailure(message);
|
|
||||||
}
|
|
||||||
reply->deleteLater();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCommunicator::deleteItem(const QString& id) {
|
|
||||||
const QString deleteRoute = QString("%1/%2").arg(ROUTE_ITEMS, id);
|
|
||||||
QNetworkReply* reply = m_restManager->deleteResource(m_serviceApi->createRequest(deleteRoute));
|
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [=]() {
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
const QString message = QString("DELETE successful! Response: %1").arg(responseData);
|
|
||||||
qInfo() << message;
|
|
||||||
emit deleteRequestSuccessful(responseData);
|
|
||||||
} else {
|
|
||||||
const QString message = QString("Error: %1").arg(reply->errorString());
|
|
||||||
qDebug() << message;
|
|
||||||
emit deleteRequestFailure(message);
|
|
||||||
}
|
|
||||||
reply->deleteLater();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCommunicator::setServerConfiguration(const QString url,
|
|
||||||
const QString email,
|
|
||||||
const QString password) {
|
|
||||||
setUrl(url);
|
|
||||||
|
|
||||||
m_email = email;
|
|
||||||
m_password = password;
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
#ifndef SERVERCOMMUNICATOR_H
|
|
||||||
#define SERVERCOMMUNICATOR_H
|
|
||||||
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkRequestFactory>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QRestAccessManager>
|
|
||||||
|
|
||||||
class ServerCommunicator : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ServerCommunicator(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
bool sslSupported();
|
|
||||||
|
|
||||||
QUrl url() const;
|
|
||||||
void setUrl(const QUrl& url);
|
|
||||||
|
|
||||||
void setServerConfiguration(const QString url, const QString email, const QString password);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void fetchItems();
|
|
||||||
void postItems(const QByteArray& jsonData);
|
|
||||||
void deleteItem(const QString& id);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void urlChanged();
|
|
||||||
|
|
||||||
void itemsFetched(const QByteArray jsonDoc);
|
|
||||||
void itemsFetchFailure(const QString errorString);
|
|
||||||
void postRequestSuccessful(const QByteArray responseData);
|
|
||||||
void postRequestFailure(const QString errorString);
|
|
||||||
void deleteRequestSuccessful(const QByteArray responseData);
|
|
||||||
void deleteRequestFailure(const QString errorString);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QNetworkAccessManager m_netManager;
|
|
||||||
std::shared_ptr<QRestAccessManager> m_restManager;
|
|
||||||
std::shared_ptr<QNetworkRequestFactory> m_serviceApi;
|
|
||||||
|
|
||||||
QString m_email;
|
|
||||||
QString m_password;
|
|
||||||
// QString m_authToken;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SERVERCOMMUNICATOR_H
|
|
||||||
@ -17,14 +17,15 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core LinguistTools)
|
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core LinguistTools)
|
||||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core LinguistTools)
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core LinguistTools)
|
||||||
|
|
||||||
add_executable(${TARGET_APP} core_test.cpp)
|
add_executable(${TARGET_APP} core_test.cpp
|
||||||
|
model_test.cpp)
|
||||||
|
|
||||||
target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/include)
|
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})
|
||||||
|
|||||||
@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
#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); }
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
TEST(CoreTests, TestEqualString) {
|
TEST(CoreTests, TestEqualString) {
|
||||||
const QString coreName("GenericCore");
|
const QString coreName("BeetRoundCore");
|
||||||
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();
|
||||||
|
|||||||
131
tests/GenericCoreTests/model_test.cpp
Normal file
131
tests/GenericCoreTests/model_test.cpp
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <QtGui/QUndoCommand>
|
||||||
|
|
||||||
|
#include "../../libs/BeetRoundCore/model/tablemodel.h"
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
inline void PrintTo(const QString& qString, ::std::ostream* os) { *os << qUtf8Printable(qString); }
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
struct ModelTest : testing::Test {
|
||||||
|
std::shared_ptr<TableModel> model;
|
||||||
|
|
||||||
|
ModelTest() { model = make_shared<TableModel>(new QUndoStack()); }
|
||||||
|
|
||||||
|
virtual ~ModelTest() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModelTestWithData : ModelTest {
|
||||||
|
ModelTestWithData() {
|
||||||
|
QList<ModelItemValues> itemList;
|
||||||
|
|
||||||
|
ModelItemValues itemValues1 = {{Bidding1Role, 100},
|
||||||
|
{ShareTypeRole, "bezahlt"},
|
||||||
|
{ShareAmountRole, 1},
|
||||||
|
{BiddingTypeRole, "digital"}};
|
||||||
|
itemList.append(itemValues1);
|
||||||
|
|
||||||
|
ModelItemValues itemValues2 = {{Bidding1Role, 100},
|
||||||
|
{ShareTypeRole, "bezahlt"},
|
||||||
|
{ShareAmountRole, 1},
|
||||||
|
{BiddingTypeRole, ""}};
|
||||||
|
itemList.append(itemValues2);
|
||||||
|
|
||||||
|
ModelItemValues itemValues3 = {{Bidding1Role, 50},
|
||||||
|
{ShareTypeRole, "teils/teils"},
|
||||||
|
{ShareAmountRole, 1},
|
||||||
|
{BiddingTypeRole, "paper"}};
|
||||||
|
itemList.append(itemValues3);
|
||||||
|
|
||||||
|
ModelItemValues itemValues4 = {{Bidding1Role, 50},
|
||||||
|
{ShareTypeRole, "bezahlt"},
|
||||||
|
{ShareAmountRole, 0.5},
|
||||||
|
{BiddingTypeRole, "paper"}};
|
||||||
|
itemList.append(itemValues4);
|
||||||
|
|
||||||
|
ModelItemValues itemValues5 = {{Bidding1Role, 100},
|
||||||
|
{ShareTypeRole, "erarbeitet"},
|
||||||
|
{ShareAmountRole, 1},
|
||||||
|
{BiddingTypeRole, "paper"}};
|
||||||
|
itemList.append(itemValues5);
|
||||||
|
|
||||||
|
ModelItemValues itemValues6 = {{Bidding1Role, 0},
|
||||||
|
{ShareTypeRole, "bezahlt"},
|
||||||
|
{ShareAmountRole, 1},
|
||||||
|
{BiddingTypeRole, "online"}};
|
||||||
|
itemList.append(itemValues6);
|
||||||
|
|
||||||
|
model->insertItems(0, itemList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// empty model
|
||||||
|
|
||||||
|
TEST_F(ModelTest, TestRowCountEmpty) {
|
||||||
|
const int expectedRowCount = 0;
|
||||||
|
|
||||||
|
const auto actualRowCount = model->rowCount();
|
||||||
|
|
||||||
|
ASSERT_EQ(expectedRowCount, actualRowCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModelTest, TestBidding1Average) {
|
||||||
|
const int expected = 0;
|
||||||
|
const auto actualBidding1Average = model->biddingAverage1();
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actualBidding1Average);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModelTest, ExpectedPlacedBiddings) {
|
||||||
|
const int expected = 0;
|
||||||
|
const auto expectedPlacedBiddings = model->nExpectedBiddings();
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, expectedPlacedBiddings);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModelTest, ExpectedTotalShareAmount) {
|
||||||
|
const int expected = 0;
|
||||||
|
const auto nTotalExpectedShares = model->nTotalExpectedShares();
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, nTotalExpectedShares);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// model with data
|
||||||
|
|
||||||
|
TEST_F(ModelTestWithData, TestRowCount) {
|
||||||
|
const int expected = 6;
|
||||||
|
|
||||||
|
const auto actual = model->rowCount();
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModelTestWithData, TestBidding1Average) {
|
||||||
|
const qreal expected = 100;
|
||||||
|
const auto actual = model->biddingAverage1();
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModelTestWithData, ExpectedPlacedBiddings) {
|
||||||
|
const int expected = 4;
|
||||||
|
const auto actual = model->nExpectedBiddings();
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModelTestWithData, PlacedBiddings1) {
|
||||||
|
const int expected = 3;
|
||||||
|
const auto actual = model->nPlacedBiddings1();
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModelTestWithData, NTotalExpectedShares) {
|
||||||
|
const qreal expected = 3;
|
||||||
|
const auto actual = model->nTotalExpectedShares();
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user