2020-10-28 13:36:09 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include "statswidget.h"
|
|
|
|
#include "mainwindow.h"
|
|
|
|
#include "stats/statsview.h"
|
|
|
|
#include <QCheckBox>
|
|
|
|
#include <QPainter>
|
|
|
|
#include <QStyledItemDelegate>
|
2022-02-16 23:42:21 +00:00
|
|
|
#include <QQmlEngine>
|
2020-10-28 13:36:09 +00:00
|
|
|
|
|
|
|
class ChartItemDelegate : public QStyledItemDelegate {
|
|
|
|
private:
|
|
|
|
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|
|
|
const QModelIndex &index) const override;
|
|
|
|
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Number of pixels that non-toplevel items are indented
|
|
|
|
static int indent(const QFontMetrics &fm)
|
|
|
|
{
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
|
|
|
return 4 * fm.width('-');
|
|
|
|
#else
|
|
|
|
return 4 * fm.horizontalAdvance('-');
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static const int iconSpace = 2; // Number of pixels between icon and text
|
|
|
|
static const int topSpace = 2; // Number of pixels above icon
|
|
|
|
|
|
|
|
void ChartItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|
|
|
const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
QFont font = index.data(Qt::FontRole).value<QFont>();
|
|
|
|
QFontMetrics fm(font);
|
|
|
|
QString name = index.data(ChartListModel::ChartNameRole).value<QString>();
|
|
|
|
painter->setFont(font);
|
|
|
|
QRect rect = option.rect;
|
|
|
|
if (option.state & QStyle::State_Selected) {
|
|
|
|
painter->save();
|
|
|
|
painter->setBrush(option.palette.highlight());
|
|
|
|
painter->setPen(Qt::NoPen);
|
|
|
|
painter->drawRect(rect);
|
|
|
|
painter->restore();
|
|
|
|
}
|
|
|
|
bool isHeader = index.data(ChartListModel::IsHeaderRole).value<bool>();
|
|
|
|
if (!isHeader)
|
|
|
|
rect.translate(indent(fm), 0);
|
|
|
|
QPixmap icon = index.data(ChartListModel::IconRole).value<QPixmap>();
|
|
|
|
if (!icon.isNull()) {
|
|
|
|
rect.translate(0, topSpace);
|
|
|
|
painter->drawPixmap(rect.topLeft(), icon);
|
|
|
|
rect.translate(icon.size().width() + iconSpace, (icon.size().height() - fm.height()) / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
painter->drawText(rect, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize ChartItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
QFont font = index.data(Qt::FontRole).value<QFont>();
|
|
|
|
QFontMetrics fm(font);
|
|
|
|
QString name = index.data(ChartListModel::ChartNameRole).value<QString>();
|
|
|
|
QSize iconSize = index.data(ChartListModel::IconSizeRole).value<QSize>();
|
|
|
|
QSize size = fm.size(Qt::TextSingleLine, name);
|
|
|
|
bool isHeader = index.data(ChartListModel::IsHeaderRole).value<bool>();
|
|
|
|
if (!isHeader)
|
|
|
|
size += QSize(indent(fm), 0);
|
|
|
|
if (iconSize.isValid())
|
|
|
|
size = QSize(size.width() + iconSize.width() + iconSpace,
|
|
|
|
std::max(size.height(), iconSize.height()) + 2 * topSpace);
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
statistics: convert chart to QQuickItem
It turns out that the wrong base class was used for the chart.
QQuickWidget can only be used on desktop, not in a mobile UI.
Therefore, turn this into a QQuickItem and move the container
QQuickWidget into desktop-only code.
Currently, this code is insane: The chart is rendered onto a
QGraphicsScene (as it was before), which is then rendered into
a QImage, which is transformed into a QSGTexture, which is then
projected onto the device. This is performed on every mouse
move event, since these events in general change the position
of the info-box.
The plan is to slowly convert elements such as the info-box into
QQuickItems. Browsing the QtQuick documentation, this will
not be much fun.
Also note that the rendering currently tears, flickers and has
antialiasing artifacts, most likely owing to integer (QImage)
to floating point (QGraphicsScene, QQuickItem) conversion
problems. The data flow is
QGraphicsScene (float) -> QImage (int) -> QQuickItem (float).
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2021-01-07 13:38:37 +00:00
|
|
|
static const QUrl urlStatsView = QUrl(QStringLiteral("qrc:/qml/statsview2.qml"));
|
2020-10-28 13:36:09 +00:00
|
|
|
StatsWidget::StatsWidget(QWidget *parent) : QWidget(parent)
|
|
|
|
{
|
|
|
|
ui.setupUi(this);
|
|
|
|
|
|
|
|
connect(ui.close, &QToolButton::clicked, this, &StatsWidget::closeStats);
|
|
|
|
|
|
|
|
ui.chartType->setModel(&charts);
|
|
|
|
connect(ui.chartType, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::chartTypeChanged);
|
|
|
|
connect(ui.var1, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var1Changed);
|
|
|
|
connect(ui.var2, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var2Changed);
|
|
|
|
connect(ui.var1Binner, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var1BinnerChanged);
|
|
|
|
connect(ui.var2Binner, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var2BinnerChanged);
|
|
|
|
connect(ui.var2Operation, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var2OperationChanged);
|
2021-12-31 17:29:06 +00:00
|
|
|
connect(ui.var1Sort, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var1SortChanged);
|
2021-02-12 09:56:48 +00:00
|
|
|
connect(ui.restrictButton, &QToolButton::clicked, this, &StatsWidget::restrict);
|
|
|
|
connect(ui.unrestrictButton, &QToolButton::clicked, this, &StatsWidget::unrestrict);
|
statistics: convert chart to QQuickItem
It turns out that the wrong base class was used for the chart.
QQuickWidget can only be used on desktop, not in a mobile UI.
Therefore, turn this into a QQuickItem and move the container
QQuickWidget into desktop-only code.
Currently, this code is insane: The chart is rendered onto a
QGraphicsScene (as it was before), which is then rendered into
a QImage, which is transformed into a QSGTexture, which is then
projected onto the device. This is performed on every mouse
move event, since these events in general change the position
of the info-box.
The plan is to slowly convert elements such as the info-box into
QQuickItems. Browsing the QtQuick documentation, this will
not be much fun.
Also note that the rendering currently tears, flickers and has
antialiasing artifacts, most likely owing to integer (QImage)
to floating point (QGraphicsScene, QQuickItem) conversion
problems. The data flow is
QGraphicsScene (float) -> QImage (int) -> QQuickItem (float).
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2021-01-07 13:38:37 +00:00
|
|
|
|
|
|
|
ui.stats->setSource(urlStatsView);
|
|
|
|
ui.stats->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
2022-02-16 23:42:21 +00:00
|
|
|
(void)getView();
|
|
|
|
}
|
|
|
|
|
|
|
|
// hack around the Qt6 bug where the QML object gets destroyed and recreated
|
|
|
|
StatsView *StatsWidget::getView()
|
|
|
|
{
|
|
|
|
StatsView *view = qobject_cast<StatsView *>(ui.stats->rootObject());
|
statistics: convert chart to QQuickItem
It turns out that the wrong base class was used for the chart.
QQuickWidget can only be used on desktop, not in a mobile UI.
Therefore, turn this into a QQuickItem and move the container
QQuickWidget into desktop-only code.
Currently, this code is insane: The chart is rendered onto a
QGraphicsScene (as it was before), which is then rendered into
a QImage, which is transformed into a QSGTexture, which is then
projected onto the device. This is performed on every mouse
move event, since these events in general change the position
of the info-box.
The plan is to slowly convert elements such as the info-box into
QQuickItems. Browsing the QtQuick documentation, this will
not be much fun.
Also note that the rendering currently tears, flickers and has
antialiasing artifacts, most likely owing to integer (QImage)
to floating point (QGraphicsScene, QQuickItem) conversion
problems. The data flow is
QGraphicsScene (float) -> QImage (int) -> QQuickItem (float).
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2021-01-07 13:38:37 +00:00
|
|
|
if (!view)
|
|
|
|
qWarning("Oops. The root of the StatsView is not a StatsView.");
|
2022-02-16 23:42:21 +00:00
|
|
|
if (view) {
|
|
|
|
// try to prevent the JS garbage collection from freeing the object
|
|
|
|
// this appears to fail with Qt6 which is why we still look up the
|
|
|
|
// object from the ui.stats rootObject
|
|
|
|
ui.stats->engine()->setObjectOwnership(view, QQmlEngine::CppOwnership);
|
|
|
|
view->setParent(this);
|
2021-03-30 21:18:15 +00:00
|
|
|
view->setVisible(isVisible()); // Synchronize visibility of widget and QtQuick-view.
|
2022-02-16 23:42:21 +00:00
|
|
|
}
|
|
|
|
return view;
|
2020-10-28 13:36:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize QComboBox with list of variables
|
|
|
|
static void setVariableList(QComboBox *combo, const StatsState::VariableList &list)
|
|
|
|
{
|
|
|
|
combo->clear();
|
2021-01-02 23:31:55 +00:00
|
|
|
combo->setEnabled(!list.variables.empty());
|
2020-10-28 13:36:09 +00:00
|
|
|
for (const StatsState::Variable &v: list.variables)
|
|
|
|
combo->addItem(v.name, QVariant(v.id));
|
|
|
|
combo->setCurrentIndex(list.selected);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize QComboBox and QLabel of binners. Hide if there are no binners.
|
statistics: convert chart to QQuickItem
It turns out that the wrong base class was used for the chart.
QQuickWidget can only be used on desktop, not in a mobile UI.
Therefore, turn this into a QQuickItem and move the container
QQuickWidget into desktop-only code.
Currently, this code is insane: The chart is rendered onto a
QGraphicsScene (as it was before), which is then rendered into
a QImage, which is transformed into a QSGTexture, which is then
projected onto the device. This is performed on every mouse
move event, since these events in general change the position
of the info-box.
The plan is to slowly convert elements such as the info-box into
QQuickItems. Browsing the QtQuick documentation, this will
not be much fun.
Also note that the rendering currently tears, flickers and has
antialiasing artifacts, most likely owing to integer (QImage)
to floating point (QGraphicsScene, QQuickItem) conversion
problems. The data flow is
QGraphicsScene (float) -> QImage (int) -> QQuickItem (float).
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2021-01-07 13:38:37 +00:00
|
|
|
static void setBinList(QComboBox *combo, const StatsState::BinnerList &list)
|
2020-10-28 13:36:09 +00:00
|
|
|
{
|
|
|
|
combo->clear();
|
2021-01-02 23:31:55 +00:00
|
|
|
combo->setEnabled(!list.binners.empty());
|
2020-10-28 13:36:09 +00:00
|
|
|
|
|
|
|
for (const QString &s: list.binners)
|
|
|
|
combo->addItem(s);
|
|
|
|
combo->setCurrentIndex(list.selected);
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::updateUi()
|
|
|
|
{
|
|
|
|
StatsState::UIState uiState = state.getUIState();
|
|
|
|
setVariableList(ui.var1, uiState.var1);
|
|
|
|
setVariableList(ui.var2, uiState.var2);
|
|
|
|
int pos = charts.update(uiState.charts);
|
|
|
|
ui.chartType->setCurrentIndex(pos);
|
|
|
|
ui.chartType->setItemDelegate(new ChartItemDelegate);
|
statistics: convert chart to QQuickItem
It turns out that the wrong base class was used for the chart.
QQuickWidget can only be used on desktop, not in a mobile UI.
Therefore, turn this into a QQuickItem and move the container
QQuickWidget into desktop-only code.
Currently, this code is insane: The chart is rendered onto a
QGraphicsScene (as it was before), which is then rendered into
a QImage, which is transformed into a QSGTexture, which is then
projected onto the device. This is performed on every mouse
move event, since these events in general change the position
of the info-box.
The plan is to slowly convert elements such as the info-box into
QQuickItems. Browsing the QtQuick documentation, this will
not be much fun.
Also note that the rendering currently tears, flickers and has
antialiasing artifacts, most likely owing to integer (QImage)
to floating point (QGraphicsScene, QQuickItem) conversion
problems. The data flow is
QGraphicsScene (float) -> QImage (int) -> QQuickItem (float).
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2021-01-07 13:38:37 +00:00
|
|
|
setBinList(ui.var1Binner, uiState.binners1);
|
|
|
|
setBinList(ui.var2Binner, uiState.binners2);
|
2021-01-02 23:31:55 +00:00
|
|
|
setVariableList(ui.var2Operation, uiState.operations2);
|
2021-12-31 17:29:06 +00:00
|
|
|
setVariableList(ui.var1Sort, uiState.sortMode1);
|
|
|
|
ui.sortGroup->setVisible(!uiState.sortMode1.variables.empty());
|
2020-10-28 13:36:09 +00:00
|
|
|
|
|
|
|
// Add checkboxes for additional features
|
|
|
|
features.clear();
|
|
|
|
for (const StatsState::Feature &f: uiState.features) {
|
|
|
|
features.emplace_back(new QCheckBox(f.name));
|
|
|
|
QCheckBox *check = features.back().get();
|
|
|
|
check->setChecked(f.selected);
|
|
|
|
int id = f.id;
|
|
|
|
connect(check, &QCheckBox::stateChanged, [this,id] (int state) { featureChanged(id, state); });
|
|
|
|
ui.features->addWidget(check);
|
|
|
|
}
|
2022-02-16 23:42:21 +00:00
|
|
|
StatsView *view = getView();
|
statistics: convert chart to QQuickItem
It turns out that the wrong base class was used for the chart.
QQuickWidget can only be used on desktop, not in a mobile UI.
Therefore, turn this into a QQuickItem and move the container
QQuickWidget into desktop-only code.
Currently, this code is insane: The chart is rendered onto a
QGraphicsScene (as it was before), which is then rendered into
a QImage, which is transformed into a QSGTexture, which is then
projected onto the device. This is performed on every mouse
move event, since these events in general change the position
of the info-box.
The plan is to slowly convert elements such as the info-box into
QQuickItems. Browsing the QtQuick documentation, this will
not be much fun.
Also note that the rendering currently tears, flickers and has
antialiasing artifacts, most likely owing to integer (QImage)
to floating point (QGraphicsScene, QQuickItem) conversion
problems. The data flow is
QGraphicsScene (float) -> QImage (int) -> QQuickItem (float).
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2021-01-07 13:38:37 +00:00
|
|
|
if (view)
|
|
|
|
view->plot(state);
|
2020-10-28 13:36:09 +00:00
|
|
|
}
|
|
|
|
|
2021-02-12 09:56:48 +00:00
|
|
|
void StatsWidget::updateRestrictionLabel()
|
|
|
|
{
|
2022-02-16 23:42:21 +00:00
|
|
|
StatsView *view = getView();
|
2021-02-12 09:56:48 +00:00
|
|
|
if (!view)
|
|
|
|
return;
|
|
|
|
int num = view->restrictionCount();
|
|
|
|
if (num < 0)
|
|
|
|
ui.restrictionLabel->setText(tr("Analyzing all dives"));
|
|
|
|
else
|
|
|
|
ui.restrictionLabel->setText(tr("Analyzing subset (%L1) dives").arg(num));
|
|
|
|
ui.unrestrictButton->setEnabled(num > 0);
|
|
|
|
}
|
|
|
|
|
2020-10-28 13:36:09 +00:00
|
|
|
void StatsWidget::closeStats()
|
|
|
|
{
|
2021-02-14 19:59:02 +00:00
|
|
|
MainWindow::instance()->setApplicationState(MainWindow::ApplicationState::Default);
|
2020-10-28 13:36:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::chartTypeChanged(int idx)
|
|
|
|
{
|
|
|
|
state.chartChanged(ui.chartType->itemData(idx).toInt());
|
|
|
|
updateUi();
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::var1Changed(int idx)
|
|
|
|
{
|
|
|
|
state.var1Changed(ui.var1->itemData(idx).toInt());
|
|
|
|
updateUi();
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::var2Changed(int idx)
|
|
|
|
{
|
|
|
|
state.var2Changed(ui.var2->itemData(idx).toInt());
|
|
|
|
updateUi();
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::var1BinnerChanged(int idx)
|
|
|
|
{
|
|
|
|
state.binner1Changed(idx);
|
|
|
|
updateUi();
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::var2BinnerChanged(int idx)
|
|
|
|
{
|
|
|
|
state.binner2Changed(idx);
|
|
|
|
updateUi();
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::var2OperationChanged(int idx)
|
|
|
|
{
|
|
|
|
state.var2OperationChanged(ui.var2Operation->itemData(idx).toInt());
|
|
|
|
updateUi();
|
|
|
|
}
|
|
|
|
|
2021-12-31 17:29:06 +00:00
|
|
|
void StatsWidget::var1SortChanged(int idx)
|
|
|
|
{
|
|
|
|
state.sortMode1Changed(ui.var1Sort->itemData(idx).toInt());
|
|
|
|
updateUi();
|
|
|
|
}
|
|
|
|
|
2020-10-28 13:36:09 +00:00
|
|
|
void StatsWidget::featureChanged(int idx, bool status)
|
|
|
|
{
|
|
|
|
state.featureChanged(idx, status);
|
2021-01-19 08:54:39 +00:00
|
|
|
// No need for a full chart replot - just show/hide the features
|
2022-02-16 23:42:21 +00:00
|
|
|
StatsView *view = getView();
|
2021-01-19 08:54:39 +00:00
|
|
|
if (view)
|
|
|
|
view->updateFeatures(state);
|
2020-10-28 13:36:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::showEvent(QShowEvent *e)
|
|
|
|
{
|
2021-02-12 09:56:48 +00:00
|
|
|
unrestrict();
|
2020-10-28 13:36:09 +00:00
|
|
|
updateUi();
|
|
|
|
QWidget::showEvent(e);
|
2021-03-30 21:18:15 +00:00
|
|
|
// Apparently, we have to manage the visibility of the view ourselves. That's mad.
|
2022-02-16 23:42:21 +00:00
|
|
|
// this is implicitly done in getView() - so we can ignore the return value
|
|
|
|
(void)getView();
|
2021-03-30 21:18:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::hideEvent(QHideEvent *e)
|
|
|
|
{
|
|
|
|
QWidget::hideEvent(e);
|
|
|
|
// Apparently, we have to manage the visibility of the view ourselves. That's mad.
|
2022-02-16 23:42:21 +00:00
|
|
|
// this is implicitly done in getView() - so we can ignore the return value
|
|
|
|
(void)getView();
|
2020-10-28 13:36:09 +00:00
|
|
|
}
|
2021-02-12 09:56:48 +00:00
|
|
|
|
|
|
|
void StatsWidget::restrict()
|
|
|
|
{
|
2022-02-16 23:42:21 +00:00
|
|
|
StatsView *view = getView();
|
2021-02-12 09:56:48 +00:00
|
|
|
if (view)
|
|
|
|
view->restrictToSelection();
|
|
|
|
updateRestrictionLabel();
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatsWidget::unrestrict()
|
|
|
|
{
|
2022-02-16 23:42:21 +00:00
|
|
|
StatsView *view = getView();
|
2021-02-12 09:56:48 +00:00
|
|
|
if (view)
|
|
|
|
view->unrestrict();
|
|
|
|
updateRestrictionLabel();
|
|
|
|
}
|