diff --git a/UIs/BeetRoundWidgets/CMakeLists.txt b/UIs/BeetRoundWidgets/CMakeLists.txt index 1e558a0..7cbf417 100644 --- a/UIs/BeetRoundWidgets/CMakeLists.txt +++ b/UIs/BeetRoundWidgets/CMakeLists.txt @@ -11,7 +11,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools Charts) set(TS_FILES ${TARGET_APP}_en_US.ts) @@ -37,6 +37,9 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) 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: # set_property(TARGET ${TARGET_APP} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR @@ -63,7 +66,7 @@ endif() 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_link_libraries(${TARGET_APP} PRIVATE BeetRoundCore) diff --git a/UIs/BeetRoundWidgets/mainwindow.cpp b/UIs/BeetRoundWidgets/mainwindow.cpp index 1fb23d4..a042a03 100644 --- a/UIs/BeetRoundWidgets/mainwindow.cpp +++ b/UIs/BeetRoundWidgets/mainwindow.cpp @@ -16,10 +16,12 @@ #include "genericcore.h" #include "model/generalsortfiltermodel.h" #include "model/metadata.h" +#include "model/modelsummary.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; @@ -349,6 +351,8 @@ void MainWindow::setupModelViews() { ui->tableView->setModel((QAbstractItemModel*)m_proxyModel.get()); ui->tableView->setSortingEnabled(true); ui->tableView->setColumnWidth(0, intColumnWidth); + + m_modelSummary = m_core->getModelSummary(); } void MainWindow::createActions() { @@ -554,5 +558,8 @@ void MainWindow::setupEventTab() { 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)"); } diff --git a/UIs/BeetRoundWidgets/mainwindow.h b/UIs/BeetRoundWidgets/mainwindow.h index 75f610d..2d081a7 100644 --- a/UIs/BeetRoundWidgets/mainwindow.h +++ b/UIs/BeetRoundWidgets/mainwindow.h @@ -17,6 +17,7 @@ QT_END_NAMESPACE class GenericCore; class TableModel; class GeneralSortFilterModel; +class ModelSummary; class NewItemDialog; class EditItemDialog; class BiddingRoundControl; @@ -73,9 +74,12 @@ class MainWindow : public QMainWindow { unique_ptr m_core; shared_ptr m_proxyModel; + shared_ptr m_modelSummary; + QUndoStack* m_modelUndoStack; unique_ptr m_modelUndoView; unique_ptr m_biddingRoundControl; + /// File actions unique_ptr m_newFileAct; unique_ptr m_openAct; diff --git a/UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.cpp b/UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.cpp new file mode 100644 index 0000000..7dd7c84 --- /dev/null +++ b/UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.cpp @@ -0,0 +1,44 @@ +#include "biddingroundprogresslayout.h" + +#include +#include + +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_nBiddings1Label = new QLabel(QString::number(m_nCurrentBiddings)); + addWidget(m_nBiddings1Label); + + QLabel* nExpectedText = new QLabel("Erwartet:"); + addWidget(nExpectedText); + + m_nExpectedBidding1Label = new QLabel(QString::number(m_nExpectedBiddings)); + addWidget(m_nExpectedBidding1Label); + + m_biddingRound1ProgressBar = new QProgressBar(parent); + m_biddingRound1ProgressBar->setMinimum(0); + m_biddingRound1ProgressBar->setMaximum(m_nExpectedBiddings); + m_biddingRound1ProgressBar->setValue(m_nCurrentBiddings); + + addWidget(m_biddingRound1ProgressBar); +} + +void BiddingRoundProgressLayout::setCurrentBiddings(const int value) { + m_nCurrentBiddings = value; + const QString bidCountString = QString::number(value); + m_nBiddings1Label->setText(bidCountString); + m_biddingRound1ProgressBar->setValue(value); +} + +void BiddingRoundProgressLayout::setExpectedBiddings(const int value) { + m_nExpectedBiddings = value; + const QString expectedBiddingsString = QString::number(value); + m_nExpectedBidding1Label->setText(expectedBiddingsString); + m_biddingRound1ProgressBar->setMaximum(value); +} diff --git a/UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.h b/UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.h new file mode 100644 index 0000000..a25e18d --- /dev/null +++ b/UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.h @@ -0,0 +1,28 @@ +#ifndef BIDDINGROUNDPROGRESSLAYOUT_H +#define BIDDINGROUNDPROGRESSLAYOUT_H + +#include + +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_nBiddings1Label = nullptr; + QLabel* m_nExpectedBidding1Label = nullptr; + QProgressBar* m_biddingRound1ProgressBar = nullptr; +}; + +#endif // BIDDINGROUNDPROGRESSLAYOUT_H diff --git a/UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.cpp b/UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.cpp new file mode 100644 index 0000000..31a1538 --- /dev/null +++ b/UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.cpp @@ -0,0 +1,110 @@ +#include "biddingroundstatuswidget.h" + +#include +#include +#include +#include + +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) + " €"; +} diff --git a/UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.h b/UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.h new file mode 100644 index 0000000..f1fb835 --- /dev/null +++ b/UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.h @@ -0,0 +1,53 @@ +#ifndef BIDDINGROUNDSTATUSWIDGET_H +#define BIDDINGROUNDSTATUSWIDGET_H + +#include "biddingroundprogresslayout.h" + +#include + +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 diff --git a/UIs/BeetRoundWidgets/widgets/summarywidget.cpp b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp new file mode 100644 index 0000000..129b5be --- /dev/null +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp @@ -0,0 +1,149 @@ +#include "summarywidget.h" + +#include "biddingroundstatuswidget.h" + +#include +#include +#include +#include + +#include + +SummaryWidget::SummaryWidget(std::shared_ptr modelSummary, QWidget* parent) + : QWidget{parent} + , m_modelSummary(modelSummary) { + /// Layouting + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + /// monthly need + QHBoxLayout* footerLayout = new QHBoxLayout(); + 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(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::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("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("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("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::nPlacedBiddingsChanged, this, + &SummaryWidget::onNPlacedBiddingsChanged); + QObject::connect(m_modelSummary.get(), &ModelSummary::biddingSumChanged, this, + &SummaryWidget::onBiddingSumChanged); + QObject::connect(m_modelSummary.get(), &ModelSummary::biddingAverageChanged, this, + &SummaryWidget::onBiddingAverageChanged); +} diff --git a/UIs/BeetRoundWidgets/widgets/summarywidget.h b/UIs/BeetRoundWidgets/widgets/summarywidget.h new file mode 100644 index 0000000..10f7252 --- /dev/null +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.h @@ -0,0 +1,45 @@ +#ifndef SUMMARYWIDGET_H +#define SUMMARYWIDGET_H + +#include + +class BiddingRoundStatusWidget; +class ModelSummary; +class QLabel; +class QSpinBox; +class QBoxLayout; + +class SummaryWidget : public QWidget { + Q_OBJECT + + public: + explicit SummaryWidget(std::shared_ptr modelSummary, QWidget* parent = nullptr); + + private slots: + void onFinancialNeedChanged(int newFinancialNeed); + void onNExpectedBiddingChanged(int newNExpected); + + void onNPlacedBiddingsChanged(const int roundNumber); + void onBiddingSumChanged(const int roundNumber); + void onBiddingAverageChanged(const int roundNumber); + + private: + std::shared_ptr m_modelSummary; + + std::unique_ptr m_biddingStatus1; + std::unique_ptr m_biddingStatus2; + std::unique_ptr 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_rowCountValueLabel; + QLabel* m_nExpectedBiddingsValueLabel; + + /// functions + QBoxLayout* createBiddingOverviewLayout(); + void setupConnections(); +}; + +#endif // SUMMARYWIDGET_H diff --git a/libs/BeetRoundCore/CMakeLists.txt b/libs/BeetRoundCore/CMakeLists.txt index fcda6a6..f852dd9 100644 --- a/libs/BeetRoundCore/CMakeLists.txt +++ b/libs/BeetRoundCore/CMakeLists.txt @@ -32,6 +32,7 @@ add_library(${TARGET_APP} STATIC model/tablemodel.h model/tablemodel.cpp model/modelitem.h model/modelitem.cpp model/generalsortfiltermodel.h model/generalsortfiltermodel.cpp + model/modelsummary.h model/modelsummary.cpp model/commands/insertrowscommand.h model/commands/insertrowscommand.cpp model/commands/removerowscommand.h model/commands/removerowscommand.cpp model/commands/edititemcommand.h model/commands/edititemcommand.cpp diff --git a/libs/BeetRoundCore/genericcore.cpp b/libs/BeetRoundCore/genericcore.cpp index 3942fc0..015705a 100644 --- a/libs/BeetRoundCore/genericcore.cpp +++ b/libs/BeetRoundCore/genericcore.cpp @@ -17,11 +17,10 @@ #include "data/settingshandler.h" #include "model/generalsortfiltermodel.h" #include "model/metadata.h" +#include "model/modelsummary.h" #include "model/tablemodel.h" #include "network/servercommunicator.h" -using namespace std; - GenericCore::GenericCore() { qDebug() << "Creating core..."; @@ -97,6 +96,8 @@ std::shared_ptr GenericCore::getSortFilterModel() const return m_sortFilterModel; } +shared_ptr GenericCore::getModelSummary() const { return m_modelSummary; } + /** * Save items to default file (in standard location). * @brief GenericCore::saveItems Saves item fo file. @@ -160,9 +161,11 @@ void GenericCore::onBiddingsChanged(int round, QList biddings) { } void GenericCore::setupModels() { - m_mainModel = make_shared(m_modelUndoStack, this); + m_mainModel = make_shared(m_modelUndoStack); m_sortFilterModel = make_shared(m_mainModel); + m_modelSummary = make_shared(m_mainModel); + /// QAbstractItemModelTester #ifdef QT_DEBUG m_mainModelTester = make_unique( diff --git a/libs/BeetRoundCore/genericcore.h b/libs/BeetRoundCore/genericcore.h index 9df744b..8751648 100644 --- a/libs/BeetRoundCore/genericcore.h +++ b/libs/BeetRoundCore/genericcore.h @@ -14,8 +14,11 @@ class QString; class TableModel; class GeneralSortFilterModel; +class ModelSummary; class ServerCommunicator; +using namespace std; + class GenericCore : public QObject { Q_OBJECT @@ -30,8 +33,10 @@ class GenericCore : public QObject { void triggerApplicationUpdate(const bool saveChanges); QUndoStack* getModelUndoStack() const; - std::shared_ptr getModel() const; - std::shared_ptr getSortFilterModel() const; + + shared_ptr getModel() const; + shared_ptr getSortFilterModel() const; + shared_ptr getModelSummary() const; void saveItems(); void importCSVFile(const QString& filePath); @@ -60,10 +65,11 @@ class GenericCore : public QObject { private: QUndoStack* m_modelUndoStack; - std::shared_ptr m_mainModel; - std::shared_ptr m_sortFilterModel; - std::unique_ptr m_mainModelTester; - std::unique_ptr m_proxyModelTester; + shared_ptr m_mainModel; + shared_ptr m_sortFilterModel; + shared_ptr m_modelSummary; + unique_ptr m_mainModelTester; + unique_ptr m_proxyModelTester; void setupModels(); void initModelData(); @@ -71,7 +77,7 @@ class GenericCore : public QObject { QString getMaintenanceToolFilePath() const; /// Network communication - std::unique_ptr m_serverCommunicator; + unique_ptr m_serverCommunicator; void setupServerConfiguration(); void applyServerConfiguration(); }; diff --git a/libs/BeetRoundCore/model/modelsummary.cpp b/libs/BeetRoundCore/model/modelsummary.cpp new file mode 100644 index 0000000..17b471e --- /dev/null +++ b/libs/BeetRoundCore/model/modelsummary.cpp @@ -0,0 +1,44 @@ +#include "modelsummary.h" + +#include "tablemodel.h" + +ModelSummary::ModelSummary(std::shared_ptr 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::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 ModelSummary::bindableRowCount() { + m_rowCount = m_model->rowCount(); + return &m_rowCount; +} + +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(); } diff --git a/libs/BeetRoundCore/model/modelsummary.h b/libs/BeetRoundCore/model/modelsummary.h new file mode 100644 index 0000000..cc98771 --- /dev/null +++ b/libs/BeetRoundCore/model/modelsummary.h @@ -0,0 +1,49 @@ +#ifndef MODELSUMMARY_H +#define MODELSUMMARY_H + +#include +#include + +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 model, QObject* parent = nullptr); + ~ModelSummary(); + + int rowCount() const; + QBindable bindableRowCount(); + 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 nPlacedBiddingsChanged(const int roundNumber); + void biddingSumChanged(const int roundNumber); + void biddingAverageChanged(const int roundNumber); + + private: + shared_ptr m_model; + + Q_OBJECT_BINDABLE_PROPERTY(ModelSummary, int, m_rowCount, &ModelSummary::rowCountChanged); +}; + +#endif // MODELSUMMARY_H diff --git a/libs/BeetRoundCore/model/tablemodel.cpp b/libs/BeetRoundCore/model/tablemodel.cpp index 94f5334..8e638e3 100644 --- a/libs/BeetRoundCore/model/tablemodel.cpp +++ b/libs/BeetRoundCore/model/tablemodel.cpp @@ -46,7 +46,10 @@ QByteArray TableModel::generateExampleItems() { TableModel::TableModel(QUndoStack* undoStack, QObject* parent) : QAbstractTableModel{parent} - , m_undoStack(undoStack) {} + , 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()) { @@ -279,6 +282,77 @@ void TableModel::insertItems(int startPosition, m_undoStack->push(insertCommand); } +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 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); +} + +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 QString shareType = (*i)->data(ShareTypeRole).toString(); + if (shareType.isEmpty() || shareType == "erarbeitet") { + continue; + } + const double shareAmount = (*i)->data(ShareAmountRole).toDouble(); + if (shareAmount == 0.0) { + continue; + } + 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::execInsertItems(const int firstRow, const QList valueList) { const int nRows = valueList.size(); qDebug() << "Inserting" << nRows << "items..."; @@ -313,6 +387,39 @@ void TableModel::execEditItemData(const int row, const QMap& chan QList roles = changedValues.keys(); roles.insert(0, Qt::DisplayRole); emit dataChanged(firstIndex, lastIndex, roles.toVector()); + + // NEXT check which roles are changed & trigger only changed properties + if (roles.contains(BiddingTypeRole) || roles.contains(ShareAmountRole)) { + emit nExpectedBiddingsChanged(); + } + 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); + } + } } } @@ -408,3 +515,64 @@ bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex, 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) { + 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) { + 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 qreal bidding = (*i)->data(biddingRole).toReal(); + const qreal shareAmount = (*i)->data(ShareAmountRole).toReal(); + const QString shareType = (*i)->data(ShareTypeRole).toString(); + const bool isValid = bidding != 0 && shareAmount != 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; +} diff --git a/libs/BeetRoundCore/model/tablemodel.h b/libs/BeetRoundCore/model/tablemodel.h index a7d2089..9d7d03d 100644 --- a/libs/BeetRoundCore/model/tablemodel.h +++ b/libs/BeetRoundCore/model/tablemodel.h @@ -2,6 +2,7 @@ #define TABLEMODEL_H #include +#include "metadata.h" class QUndoStack; class ModelItem; @@ -55,6 +56,31 @@ class TableModel : public QAbstractTableModel { const QList& itemValuesList, const QModelIndex& parentIndex = QModelIndex()); + /// property functions + 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 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: /// *** members *** // TODO shared_ptr -> unique_ptr @@ -74,6 +100,11 @@ class TableModel : public QAbstractTableModel { QModelIndex searchItemIndex(const ModelItemValues givenItemValues) const; bool isItemEqualToItemValues(const QModelIndex& itemIndex, 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; }; #endif // TABLEMODEL_H