From dac9ac46f2a4b77a8307eebcca5a9d36353480b5 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Sun, 15 Feb 2026 16:36:13 +0100 Subject: [PATCH 1/6] Added a ModelSummary class to make the overview over the model content accessible via QProperty system & a SummaryWidget to show this information. Only property rowCount as a proof of concept. Other properties will follow. --- UIs/BeetRoundWidgets/CMakeLists.txt | 1 + UIs/BeetRoundWidgets/mainwindow.cpp | 7 ++++ UIs/BeetRoundWidgets/mainwindow.h | 4 +++ .../widgets/summarywidget.cpp | 30 +++++++++++++++++ UIs/BeetRoundWidgets/widgets/summarywidget.h | 21 ++++++++++++ libs/BeetRoundCore/CMakeLists.txt | 1 + libs/BeetRoundCore/genericcore.cpp | 7 ++-- libs/BeetRoundCore/genericcore.h | 20 ++++++++---- libs/BeetRoundCore/model/modelsummary.cpp | 22 +++++++++++++ libs/BeetRoundCore/model/modelsummary.h | 32 +++++++++++++++++++ libs/BeetRoundCore/model/tablemodel.cpp | 15 ++++++++- libs/BeetRoundCore/model/tablemodel.h | 7 ++++ 12 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 UIs/BeetRoundWidgets/widgets/summarywidget.cpp create mode 100644 UIs/BeetRoundWidgets/widgets/summarywidget.h create mode 100644 libs/BeetRoundCore/model/modelsummary.cpp create mode 100644 libs/BeetRoundCore/model/modelsummary.h diff --git a/UIs/BeetRoundWidgets/CMakeLists.txt b/UIs/BeetRoundWidgets/CMakeLists.txt index 1e558a0..49eaae5 100644 --- a/UIs/BeetRoundWidgets/CMakeLists.txt +++ b/UIs/BeetRoundWidgets/CMakeLists.txt @@ -37,6 +37,7 @@ 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 ) # Define target properties for Android with Qt 6 as: # set_property(TARGET ${TARGET_APP} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR 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/summarywidget.cpp b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp new file mode 100644 index 0000000..bfe9904 --- /dev/null +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp @@ -0,0 +1,30 @@ +#include "summarywidget.h" + +#include +#include +#include + +#include + +SummaryWidget::SummaryWidget(std::shared_ptr modelSummary, QWidget* parent) + : QWidget{parent} + , m_modelSummary(modelSummary) { + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + QProperty nRows(-1); + + QHBoxLayout* footerLayout = new QHBoxLayout(); + QLabel* rowCountLabel = new QLabel("Row count:"); + m_rowCoundValueLabel = new QLabel(QString::number(nRows)); + + QObject::connect(m_modelSummary.get(), &ModelSummary::rowCountChanged, [&]() { + m_rowCoundValueLabel->setText(QString::number(m_modelSummary->rowCount())); + }); + m_modelSummary->bindableRowCount().setBinding([&]() { return nRows.value(); }); + + footerLayout->addWidget(rowCountLabel); + footerLayout->addWidget(m_rowCoundValueLabel); + mainLayout->addLayout(footerLayout); + + setLayout(mainLayout); +} diff --git a/UIs/BeetRoundWidgets/widgets/summarywidget.h b/UIs/BeetRoundWidgets/widgets/summarywidget.h new file mode 100644 index 0000000..7d851d6 --- /dev/null +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.h @@ -0,0 +1,21 @@ +#ifndef SUMMARYWIDGET_H +#define SUMMARYWIDGET_H + +#include + +class ModelSummary; +class QLabel; + +class SummaryWidget : public QWidget { + Q_OBJECT + + public: + explicit SummaryWidget(std::shared_ptr modelSummary, QWidget* parent = nullptr); + + private: + std::shared_ptr m_modelSummary; + + QLabel* m_rowCoundValueLabel; +}; + +#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..d4ba3d6 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. @@ -163,6 +164,8 @@ void GenericCore::setupModels() { m_mainModel = make_shared(m_modelUndoStack, this); m_sortFilterModel = make_shared(m_mainModel); + m_modelSummary = make_shared(m_mainModel, this); + /// 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..7be6e09 --- /dev/null +++ b/libs/BeetRoundCore/model/modelsummary.cpp @@ -0,0 +1,22 @@ +#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); +} + +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; +} diff --git a/libs/BeetRoundCore/model/modelsummary.h b/libs/BeetRoundCore/model/modelsummary.h new file mode 100644 index 0000000..4379c7f --- /dev/null +++ b/libs/BeetRoundCore/model/modelsummary.h @@ -0,0 +1,32 @@ +#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(); + + signals: + void rowCountChanged(); + + 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..d4f599a 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,16 @@ 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(); +} + void TableModel::execInsertItems(const int firstRow, const QList valueList) { const int nRows = valueList.size(); qDebug() << "Inserting" << nRows << "items..."; diff --git a/libs/BeetRoundCore/model/tablemodel.h b/libs/BeetRoundCore/model/tablemodel.h index a7d2089..5a11602 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,12 @@ class TableModel : public QAbstractTableModel { const QList& itemValuesList, const QModelIndex& parentIndex = QModelIndex()); + signals: + void rowCountChanged(); + + private slots: + void onRowCountChanged(const QModelIndex& parent, int first, int last); + private: /// *** members *** // TODO shared_ptr -> unique_ptr From f200fff99e5b3b73337cb64b67112446f9bf05fc Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Mon, 16 Feb 2026 09:24:58 +0100 Subject: [PATCH 2/6] Added a second bound property (nExpectedBiddings) & cleaned up the code a little. --- .../widgets/summarywidget.cpp | 44 ++++++++++++++----- UIs/BeetRoundWidgets/widgets/summarywidget.h | 5 ++- libs/BeetRoundCore/model/modelsummary.cpp | 10 +++++ libs/BeetRoundCore/model/modelsummary.h | 9 ++++ libs/BeetRoundCore/model/tablemodel.cpp | 14 ++++++ 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/UIs/BeetRoundWidgets/widgets/summarywidget.cpp b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp index bfe9904..66b7ac5 100644 --- a/UIs/BeetRoundWidgets/widgets/summarywidget.cpp +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp @@ -9,22 +9,44 @@ SummaryWidget::SummaryWidget(std::shared_ptr modelSummary, QWidget* parent) : QWidget{parent} , m_modelSummary(modelSummary) { + /// Layouting QVBoxLayout* mainLayout = new QVBoxLayout(this); + QHBoxLayout* rowCountLayout = new QHBoxLayout(); + QLabel* rowCountLabel = new QLabel("Row count:"); + m_rowCountValueLabel = new QLabel(""); + rowCountLayout->addWidget(rowCountLabel); + rowCountLayout->addWidget(m_rowCountValueLabel); + + QHBoxLayout* nExpectedBiddingsLayout = new QHBoxLayout(); + QLabel* nExpectedBiddingsLabel = new QLabel("Expected biddings:"); + m_nExpectedBiddingsValueLabel = new QLabel(""); + nExpectedBiddingsLayout->addWidget(nExpectedBiddingsLabel); + nExpectedBiddingsLayout->addWidget(m_nExpectedBiddingsValueLabel); + + mainLayout->addLayout(rowCountLayout); + mainLayout->addLayout(nExpectedBiddingsLayout); + + setLayout(mainLayout); + + setupBindableProperties(); +} + +void SummaryWidget::setupBindableProperties() { + // TODO figure out how to encapsulate each property binding into a dedicated function: + // "bindProperty(&bindable, &signal, &getter, widget)" + /// nRows QProperty nRows(-1); - - QHBoxLayout* footerLayout = new QHBoxLayout(); - QLabel* rowCountLabel = new QLabel("Row count:"); - m_rowCoundValueLabel = new QLabel(QString::number(nRows)); - QObject::connect(m_modelSummary.get(), &ModelSummary::rowCountChanged, [&]() { - m_rowCoundValueLabel->setText(QString::number(m_modelSummary->rowCount())); + m_rowCountValueLabel->setText(QString::number(m_modelSummary->rowCount())); }); m_modelSummary->bindableRowCount().setBinding([&]() { return nRows.value(); }); - footerLayout->addWidget(rowCountLabel); - footerLayout->addWidget(m_rowCoundValueLabel); - mainLayout->addLayout(footerLayout); - - setLayout(mainLayout); + /// nExpectedBiddings + QProperty nExpectedBiddings(-1); + QObject::connect(m_modelSummary.get(), &ModelSummary::nExpectedBiddingsChanged, [&]() { + m_nExpectedBiddingsValueLabel->setText(QString::number(m_modelSummary->nExpectedBiddings())); + }); + m_modelSummary->bindableNExpectedBiddings().setBinding( + [&]() { return nExpectedBiddings.value(); }); } diff --git a/UIs/BeetRoundWidgets/widgets/summarywidget.h b/UIs/BeetRoundWidgets/widgets/summarywidget.h index 7d851d6..9f17d70 100644 --- a/UIs/BeetRoundWidgets/widgets/summarywidget.h +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.h @@ -15,7 +15,10 @@ class SummaryWidget : public QWidget { private: std::shared_ptr m_modelSummary; - QLabel* m_rowCoundValueLabel; + QLabel* m_rowCountValueLabel; + QLabel* m_nExpectedBiddingsValueLabel; + + void setupBindableProperties(); }; #endif // SUMMARYWIDGET_H diff --git a/libs/BeetRoundCore/model/modelsummary.cpp b/libs/BeetRoundCore/model/modelsummary.cpp index 7be6e09..33566ea 100644 --- a/libs/BeetRoundCore/model/modelsummary.cpp +++ b/libs/BeetRoundCore/model/modelsummary.cpp @@ -7,6 +7,9 @@ ModelSummary::ModelSummary(std::shared_ptr model, 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); } ModelSummary::~ModelSummary() {} @@ -20,3 +23,10 @@ QBindable ModelSummary::bindableRowCount() { m_rowCount = m_model->rowCount(); return &m_rowCount; } + +int ModelSummary::nExpectedBiddings() const { return m_model->nExpectedBiddings(); } + +QBindable ModelSummary::bindableNExpectedBiddings() { + m_nExpectedBiddings = m_model->nExpectedBiddings(); + return &m_nExpectedBiddings; +} diff --git a/libs/BeetRoundCore/model/modelsummary.h b/libs/BeetRoundCore/model/modelsummary.h index 4379c7f..075b572 100644 --- a/libs/BeetRoundCore/model/modelsummary.h +++ b/libs/BeetRoundCore/model/modelsummary.h @@ -12,6 +12,8 @@ class ModelSummary : public QObject { Q_OBJECT Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged BINDABLE bindableRowCount) + Q_PROPERTY(int nExpectedBiddings READ nExpectedBiddings NOTIFY nExpectedBiddingsChanged BINDABLE + bindableNExpectedBiddings) public: ModelSummary(shared_ptr model, QObject* parent = nullptr); @@ -19,14 +21,21 @@ class ModelSummary : public QObject { int rowCount() const; QBindable bindableRowCount(); + int nExpectedBiddings() const; + QBindable bindableNExpectedBiddings(); signals: void rowCountChanged(); + void nExpectedBiddingsChanged(); private: shared_ptr m_model; Q_OBJECT_BINDABLE_PROPERTY(ModelSummary, int, m_rowCount, &ModelSummary::rowCountChanged); + Q_OBJECT_BINDABLE_PROPERTY(ModelSummary, + int, + m_nExpectedBiddings, + &ModelSummary::nExpectedBiddingsChanged); }; #endif // MODELSUMMARY_H diff --git a/libs/BeetRoundCore/model/tablemodel.cpp b/libs/BeetRoundCore/model/tablemodel.cpp index d4f599a..18855a1 100644 --- a/libs/BeetRoundCore/model/tablemodel.cpp +++ b/libs/BeetRoundCore/model/tablemodel.cpp @@ -290,8 +290,19 @@ void TableModel::onRowCountChanged(const QModelIndex& parent, int first, int las return; } emit rowCountChanged(); + emit nExpectedBiddingsChanged(); } +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()) { + result++; + } + } + return result; +} void TableModel::execInsertItems(const int firstRow, const QList valueList) { const int nRows = valueList.size(); qDebug() << "Inserting" << nRows << "items..."; @@ -326,6 +337,9 @@ 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 + emit nExpectedBiddingsChanged(); } } From f8201ead71bdcae35545a0e4a71d25425a75ad92 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Mon, 16 Feb 2026 11:46:01 +0100 Subject: [PATCH 3/6] Added a placeholder for the configuration of the monthly financial need. --- .../widgets/summarywidget.cpp | 35 +++++++++++++++---- UIs/BeetRoundWidgets/widgets/summarywidget.h | 8 +++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/UIs/BeetRoundWidgets/widgets/summarywidget.cpp b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp index 66b7ac5..0214c9e 100644 --- a/UIs/BeetRoundWidgets/widgets/summarywidget.cpp +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,13 +11,14 @@ SummaryWidget::SummaryWidget(std::shared_ptr modelSummary, QWidget : QWidget{parent} , m_modelSummary(modelSummary) { /// Layouting - QVBoxLayout* mainLayout = new QVBoxLayout(this); + QVBoxLayout* mainLayout = new QVBoxLayout(this); + QHBoxLayout* headerLayout = new QHBoxLayout(); - QHBoxLayout* rowCountLayout = new QHBoxLayout(); - QLabel* rowCountLabel = new QLabel("Row count:"); - m_rowCountValueLabel = new QLabel(""); - rowCountLayout->addWidget(rowCountLabel); - rowCountLayout->addWidget(m_rowCountValueLabel); + /// bindable (proof of concept) properties + QLabel* rowCountLabel = new QLabel("Row count:"); + m_rowCountValueLabel = new QLabel(""); + headerLayout->addWidget(rowCountLabel); + headerLayout->addWidget(m_rowCountValueLabel); QHBoxLayout* nExpectedBiddingsLayout = new QHBoxLayout(); QLabel* nExpectedBiddingsLabel = new QLabel("Expected biddings:"); @@ -24,14 +26,33 @@ SummaryWidget::SummaryWidget(std::shared_ptr modelSummary, QWidget nExpectedBiddingsLayout->addWidget(nExpectedBiddingsLabel); nExpectedBiddingsLayout->addWidget(m_nExpectedBiddingsValueLabel); - mainLayout->addLayout(rowCountLayout); + /// 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); + + mainLayout->addLayout(headerLayout); mainLayout->addLayout(nExpectedBiddingsLayout); + mainLayout->addLayout(footerLayout); setLayout(mainLayout); setupBindableProperties(); } +void SummaryWidget::onFinancialNeedChanged(int newFinancialNeed) { + // NEXT implement reaction on financial need changes + qCritical() << "Apply financial need changes!!!"; +} + void SummaryWidget::setupBindableProperties() { // TODO figure out how to encapsulate each property binding into a dedicated function: // "bindProperty(&bindable, &signal, &getter, widget)" diff --git a/UIs/BeetRoundWidgets/widgets/summarywidget.h b/UIs/BeetRoundWidgets/widgets/summarywidget.h index 9f17d70..553efdd 100644 --- a/UIs/BeetRoundWidgets/widgets/summarywidget.h +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.h @@ -5,6 +5,7 @@ class ModelSummary; class QLabel; +class QSpinBox; class SummaryWidget : public QWidget { Q_OBJECT @@ -12,9 +13,16 @@ class SummaryWidget : public QWidget { public: explicit SummaryWidget(std::shared_ptr modelSummary, QWidget* parent = nullptr); + private slots: + void onFinancialNeedChanged(int newFinancialNeed); + private: std::shared_ptr m_modelSummary; + // 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; From 1e64dda701260df0b9d94923920e2237171e221f Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Mon, 16 Feb 2026 18:48:05 +0100 Subject: [PATCH 4/6] Migrated old visualization of the bidding round statuses with donut charts from the legacy BeetRound project. --- UIs/BeetRoundWidgets/CMakeLists.txt | 6 +- .../widgets/biddingroundprogresslayout.cpp | 44 ++++++ .../widgets/biddingroundprogresslayout.h | 28 ++++ .../widgets/biddingroundstatuswidget.cpp | 110 +++++++++++++ .../widgets/biddingroundstatuswidget.h | 53 +++++++ .../widgets/summarywidget.cpp | 144 +++++++++++++----- UIs/BeetRoundWidgets/widgets/summarywidget.h | 15 +- libs/BeetRoundCore/model/modelsummary.cpp | 20 ++- libs/BeetRoundCore/model/modelsummary.h | 22 ++- libs/BeetRoundCore/model/tablemodel.cpp | 134 +++++++++++++++- libs/BeetRoundCore/model/tablemodel.h | 24 +++ 11 files changed, 551 insertions(+), 49 deletions(-) create mode 100644 UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.cpp create mode 100644 UIs/BeetRoundWidgets/widgets/biddingroundprogresslayout.h create mode 100644 UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.cpp create mode 100644 UIs/BeetRoundWidgets/widgets/biddingroundstatuswidget.h diff --git a/UIs/BeetRoundWidgets/CMakeLists.txt b/UIs/BeetRoundWidgets/CMakeLists.txt index 49eaae5..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) @@ -38,6 +38,8 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) 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 @@ -64,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/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 index 0214c9e..129b5be 100644 --- a/UIs/BeetRoundWidgets/widgets/summarywidget.cpp +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.cpp @@ -1,5 +1,7 @@ #include "summarywidget.h" +#include "biddingroundstatuswidget.h" + #include #include #include @@ -11,20 +13,7 @@ SummaryWidget::SummaryWidget(std::shared_ptr modelSummary, QWidget : QWidget{parent} , m_modelSummary(modelSummary) { /// Layouting - QVBoxLayout* mainLayout = new QVBoxLayout(this); - QHBoxLayout* headerLayout = new QHBoxLayout(); - - /// bindable (proof of concept) properties - QLabel* rowCountLabel = new QLabel("Row count:"); - m_rowCountValueLabel = new QLabel(""); - headerLayout->addWidget(rowCountLabel); - headerLayout->addWidget(m_rowCountValueLabel); - - QHBoxLayout* nExpectedBiddingsLayout = new QHBoxLayout(); - QLabel* nExpectedBiddingsLabel = new QLabel("Expected biddings:"); - m_nExpectedBiddingsValueLabel = new QLabel(""); - nExpectedBiddingsLayout->addWidget(nExpectedBiddingsLabel); - nExpectedBiddingsLayout->addWidget(m_nExpectedBiddingsValueLabel); + QVBoxLayout* mainLayout = new QVBoxLayout(this); /// monthly need QHBoxLayout* footerLayout = new QHBoxLayout(); @@ -39,35 +28,122 @@ SummaryWidget::SummaryWidget(std::shared_ptr modelSummary, QWidget footerLayout->addWidget(m_financialNeedBox); footerLayout->addStretch(1); - mainLayout->addLayout(headerLayout); - mainLayout->addLayout(nExpectedBiddingsLayout); + QBoxLayout* roundsLayout = createBiddingOverviewLayout(); + mainLayout->addLayout(roundsLayout); mainLayout->addLayout(footerLayout); + mainLayout->setSpacing(50); setLayout(mainLayout); - setupBindableProperties(); + setupConnections(); } void SummaryWidget::onFinancialNeedChanged(int newFinancialNeed) { - // NEXT implement reaction on financial need changes - qCritical() << "Apply financial need changes!!!"; + m_biddingStatus1->onFinancialNeedChanged(newFinancialNeed); + m_biddingStatus2->onFinancialNeedChanged(newFinancialNeed); + m_biddingStatus3->onFinancialNeedChanged(newFinancialNeed); } -void SummaryWidget::setupBindableProperties() { +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)" - /// nRows - QProperty nRows(-1); - QObject::connect(m_modelSummary.get(), &ModelSummary::rowCountChanged, [&]() { - m_rowCountValueLabel->setText(QString::number(m_modelSummary->rowCount())); - }); - m_modelSummary->bindableRowCount().setBinding([&]() { return nRows.value(); }); - - /// nExpectedBiddings - QProperty nExpectedBiddings(-1); - QObject::connect(m_modelSummary.get(), &ModelSummary::nExpectedBiddingsChanged, [&]() { - m_nExpectedBiddingsValueLabel->setText(QString::number(m_modelSummary->nExpectedBiddings())); - }); - m_modelSummary->bindableNExpectedBiddings().setBinding( - [&]() { return nExpectedBiddings.value(); }); + 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 index 553efdd..10f7252 100644 --- a/UIs/BeetRoundWidgets/widgets/summarywidget.h +++ b/UIs/BeetRoundWidgets/widgets/summarywidget.h @@ -3,9 +3,11 @@ #include +class BiddingRoundStatusWidget; class ModelSummary; class QLabel; class QSpinBox; +class QBoxLayout; class SummaryWidget : public QWidget { Q_OBJECT @@ -15,10 +17,19 @@ class SummaryWidget : public QWidget { 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; @@ -26,7 +37,9 @@ class SummaryWidget : public QWidget { QLabel* m_rowCountValueLabel; QLabel* m_nExpectedBiddingsValueLabel; - void setupBindableProperties(); + /// functions + QBoxLayout* createBiddingOverviewLayout(); + void setupConnections(); }; #endif // SUMMARYWIDGET_H diff --git a/libs/BeetRoundCore/model/modelsummary.cpp b/libs/BeetRoundCore/model/modelsummary.cpp index 33566ea..17b471e 100644 --- a/libs/BeetRoundCore/model/modelsummary.cpp +++ b/libs/BeetRoundCore/model/modelsummary.cpp @@ -10,6 +10,11 @@ ModelSummary::ModelSummary(std::shared_ptr model, QObject* parent) // 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() {} @@ -26,7 +31,14 @@ QBindable ModelSummary::bindableRowCount() { int ModelSummary::nExpectedBiddings() const { return m_model->nExpectedBiddings(); } -QBindable ModelSummary::bindableNExpectedBiddings() { - m_nExpectedBiddings = m_model->nExpectedBiddings(); - return &m_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 index 075b572..cc98771 100644 --- a/libs/BeetRoundCore/model/modelsummary.h +++ b/libs/BeetRoundCore/model/modelsummary.h @@ -12,8 +12,6 @@ class ModelSummary : public QObject { Q_OBJECT Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged BINDABLE bindableRowCount) - Q_PROPERTY(int nExpectedBiddings READ nExpectedBiddings NOTIFY nExpectedBiddingsChanged BINDABLE - bindableNExpectedBiddings) public: ModelSummary(shared_ptr model, QObject* parent = nullptr); @@ -22,20 +20,30 @@ class ModelSummary : public QObject { int rowCount() const; QBindable bindableRowCount(); int nExpectedBiddings() const; - QBindable bindableNExpectedBiddings(); + 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); - Q_OBJECT_BINDABLE_PROPERTY(ModelSummary, - int, - m_nExpectedBiddings, - &ModelSummary::nExpectedBiddingsChanged); }; #endif // MODELSUMMARY_H diff --git a/libs/BeetRoundCore/model/tablemodel.cpp b/libs/BeetRoundCore/model/tablemodel.cpp index 18855a1..8fac24f 100644 --- a/libs/BeetRoundCore/model/tablemodel.cpp +++ b/libs/BeetRoundCore/model/tablemodel.cpp @@ -291,6 +291,18 @@ void TableModel::onRowCountChanged(const QModelIndex& parent, int first, int las } 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 { @@ -303,6 +315,35 @@ int TableModel::nExpectedBiddings() const { } 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..."; @@ -339,7 +380,37 @@ void TableModel::execEditItemData(const int row, const QMap& chan emit dataChanged(firstIndex, lastIndex, roles.toVector()); // NEXT check which roles are changed & trigger only changed properties - emit nExpectedBiddingsChanged(); + 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); + } + } } } @@ -435,3 +506,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 5a11602..9d7d03d 100644 --- a/libs/BeetRoundCore/model/tablemodel.h +++ b/libs/BeetRoundCore/model/tablemodel.h @@ -56,9 +56,28 @@ 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); @@ -81,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 From 06e96916edeed125e6624d6bbd2671c6b4b9e4ab Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Mon, 16 Feb 2026 18:50:44 +0100 Subject: [PATCH 5/6] Fixed a crash bug when the application is quit. Because of intertwining shared pointer and the Qt ownership of child objects the model and the model summary were tried to be destroyed two times. --- libs/BeetRoundCore/genericcore.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/BeetRoundCore/genericcore.cpp b/libs/BeetRoundCore/genericcore.cpp index d4ba3d6..015705a 100644 --- a/libs/BeetRoundCore/genericcore.cpp +++ b/libs/BeetRoundCore/genericcore.cpp @@ -161,10 +161,10 @@ 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, this); + m_modelSummary = make_shared(m_mainModel); /// QAbstractItemModelTester #ifdef QT_DEBUG From 546d6ea3ef26da2f65202f45e01a69c1743bde2f Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Mon, 16 Feb 2026 19:00:06 +0100 Subject: [PATCH 6/6] Added two skips in calculation of amount of expected biddings (depending on share type & share amount). --- libs/BeetRoundCore/model/tablemodel.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libs/BeetRoundCore/model/tablemodel.cpp b/libs/BeetRoundCore/model/tablemodel.cpp index 8fac24f..8e638e3 100644 --- a/libs/BeetRoundCore/model/tablemodel.cpp +++ b/libs/BeetRoundCore/model/tablemodel.cpp @@ -309,9 +309,18 @@ 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()) { - result++; + 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; }