diff --git a/desktop-widgets/statswidget.cpp b/desktop-widgets/statswidget.cpp new file mode 100644 index 000000000..874a6db15 --- /dev/null +++ b/desktop-widgets/statswidget.cpp @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "statswidget.h" +#include "mainwindow.h" +#include "stats/statsview.h" +#include +#include +#include + +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(); + QFontMetrics fm(font); + QString name = index.data(ChartListModel::ChartNameRole).value(); + 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(); + if (!isHeader) + rect.translate(indent(fm), 0); + QPixmap icon = index.data(ChartListModel::IconRole).value(); + 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(); + QFontMetrics fm(font); + QString name = index.data(ChartListModel::ChartNameRole).value(); + QSize iconSize = index.data(ChartListModel::IconSizeRole).value(); + QSize size = fm.size(Qt::TextSingleLine, name); + bool isHeader = index.data(ChartListModel::IsHeaderRole).value(); + 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; +} + +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::of(&QComboBox::activated), this, &StatsWidget::chartTypeChanged); + connect(ui.var1, QOverload::of(&QComboBox::activated), this, &StatsWidget::var1Changed); + connect(ui.var2, QOverload::of(&QComboBox::activated), this, &StatsWidget::var2Changed); + connect(ui.var1Binner, QOverload::of(&QComboBox::activated), this, &StatsWidget::var1BinnerChanged); + connect(ui.var2Binner, QOverload::of(&QComboBox::activated), this, &StatsWidget::var2BinnerChanged); + connect(ui.var2Operation, QOverload::of(&QComboBox::activated), this, &StatsWidget::var2OperationChanged); +} + +// Initialize QComboBox with list of variables +static void setVariableList(QComboBox *combo, const StatsState::VariableList &list) +{ + combo->clear(); + 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. +static void setBinList(QLabel *label, QComboBox *combo, const QString &varName, const StatsState::BinnerList &list) +{ + combo->clear(); + if (list.binners.empty()) { + label->hide(); + combo->hide(); + return; + } + + label->show(); + combo->show(); + + for (const QString &s: list.binners) + combo->addItem(s); + combo->setCurrentIndex(list.selected); +} + +// Initialize QComboBox and QLabel of operations. Hide if there are no operations. +static void setOperationList(QLabel *label, QComboBox *combo, const QString &varName, const StatsState::VariableList &list) +{ + combo->clear(); + if (list.variables.empty()) { + label->hide(); + combo->hide(); + return; + } + + label->show(); + combo->show(); + + setVariableList(combo, list); +} + +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); + setBinList(ui.var1BinnerLabel, ui.var1Binner, uiState.var1Name, uiState.binners1); + setBinList(ui.var2BinnerLabel, ui.var2Binner, uiState.var2Name, uiState.binners2); + setOperationList(ui.var2OperationLabel, ui.var2Operation, uiState.var2Name, uiState.operations2); + + // 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); + } + + ui.stats->plot(state); +} + +void StatsWidget::closeStats() +{ + MainWindow::instance()->setApplicationState(ApplicationState::Default); +} + +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(); +} + +void StatsWidget::featureChanged(int idx, bool status) +{ + state.featureChanged(idx, status); + updateUi(); +} + +void StatsWidget::showEvent(QShowEvent *e) +{ + updateUi(); + QWidget::showEvent(e); +} diff --git a/desktop-widgets/statswidget.h b/desktop-widgets/statswidget.h new file mode 100644 index 000000000..40e6d106c --- /dev/null +++ b/desktop-widgets/statswidget.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef STATSWIDGET_H +#define STATSWIDGET_H + +#include "stats/statsstate.h" +#include "stats/chartlistmodel.h" +#include "ui_statswidget.h" +#include +#include + +class QCheckBox; + +class StatsWidget : public QWidget { + Q_OBJECT +public: + StatsWidget(QWidget *parent = 0); +private +slots: + void closeStats(); + void chartTypeChanged(int); + void var1Changed(int); + void var2Changed(int); + void var1BinnerChanged(int); + void var2BinnerChanged(int); + void var2OperationChanged(int); + void featureChanged(int, bool); +private: + Ui::StatsWidget ui; + StatsState state; + void updateUi(); + std::vector> features; + + ChartListModel charts; + //QStringListModel charts; + void showEvent(QShowEvent *) override; +}; + +#endif // STATSWIDGET_H diff --git a/desktop-widgets/statswidget.ui b/desktop-widgets/statswidget.ui new file mode 100644 index 000000000..684b0fa58 --- /dev/null +++ b/desktop-widgets/statswidget.ui @@ -0,0 +1,131 @@ + + + StatsWidget + + + + 0 + 0 + 555 + 848 + + + + + + + + + Close + + + :filter-close:filter-close + + + Qt::ToolButtonTextBesideIcon + + + + + + + Base variable + + + + + + + + + Binning + + + + + + + + + + + + + Data + + + + + + + + + Binning + + + + + + + + + + Operation + + + + + + + + + + + + + Chart + + + + + + + + + + + + + + + Qt::Vertical + + + + + + + + + + 0 + 0 + + + + + + + + + StatsView + QQuickWidget +
stats/statsview.h
+ 1 +
+
+ + + + +