diff options
author | Berthold Stoeger <bstoeger@mail.tuwien.ac.at> | 2021-01-02 22:16:11 +0100 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2021-01-03 13:41:15 -0800 |
commit | 319a7af31afe3b3b1ba03114b01e88c7067709f0 (patch) | |
tree | cf0a37d4e923c95413e6f72955a9192758f427a7 | |
parent | fbb17871c9aab2eee71da98163b802836c691476 (diff) | |
download | subsurface-319a7af31afe3b3b1ba03114b01e88c7067709f0.tar.gz |
statistics: add a model that describes a list of charts
Qt's comboboxes are controlled by models, there's no way around
that. To customize the chart-selection widget this must therefore
be abstracted into a model. On the upside, this hopefully can
be used for desktop and mobile.
The model provides icons and paints a warning-symbol on it
if the statistics core code deems the chart to be not recommended.
Notably, when plotting a categorical bar chart against a
numerical value (in such a case histograms are preferred).
Includes a fix for a silly oversight in CMakelist.txt: add the
statstranslations.h header.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
-rw-r--r-- | stats/CMakeLists.txt | 3 | ||||
-rw-r--r-- | stats/chartlistmodel.cpp | 119 | ||||
-rw-r--r-- | stats/chartlistmodel.h | 54 |
3 files changed, 176 insertions, 0 deletions
diff --git a/stats/CMakeLists.txt b/stats/CMakeLists.txt index 31e809270..e0e2dc303 100644 --- a/stats/CMakeLists.txt +++ b/stats/CMakeLists.txt @@ -9,6 +9,8 @@ set(SUBSURFACE_STATS_SRCS barseries.cpp boxseries.h boxseries.cpp + chartlistmodel.h + chartlistmodel.cpp informationbox.h informationbox.cpp legend.h @@ -25,6 +27,7 @@ set(SUBSURFACE_STATS_SRCS statsseries.cpp statsstate.h statsstate.cpp + statstranslations.h statsvariables.h statsvariables.cpp statsview.h diff --git a/stats/chartlistmodel.cpp b/stats/chartlistmodel.cpp new file mode 100644 index 000000000..9ae3dae6e --- /dev/null +++ b/stats/chartlistmodel.cpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "chartlistmodel.h" +#include "core/metrics.h" +#include "core/qthelper.h" +#include <QIcon> +#include <QFontMetrics> +#include <QPainter> + +ChartListModel::ChartListModel() : + itemFont(defaultModelFont()), + headerFont(itemFont.family(), itemFont.pointSize(), itemFont.weight(), true) +{ + QFontMetrics fm(itemFont); + int fontHeight = fm.height(); + + int iconSize = fontHeight * 3; + warningPixmap = QPixmap::fromImage(renderSVGIcon(":chart-warning-icon", fontHeight, true)); + initIcon(ChartSubType::Vertical, ":chart-bar-vertical-icon", iconSize); + initIcon(ChartSubType::VerticalGrouped, ":chart-bar-grouped-vertical-icon", iconSize); + initIcon(ChartSubType::VerticalStacked, ":chart-bar-stacked-vertical-icon", iconSize); + initIcon(ChartSubType::Horizontal, ":chart-bar-horizontal-icon", iconSize); + initIcon(ChartSubType::HorizontalGrouped, ":chart-bar-grouped-horizontal-icon", iconSize); + initIcon(ChartSubType::HorizontalStacked, ":chart-bar-stacked-horizontal-icon", iconSize); + initIcon(ChartSubType::Dots, ":chart-points-icon", iconSize); + initIcon(ChartSubType::Box, ":chart-box-icon", iconSize); + initIcon(ChartSubType::Pie, ":chart-pie-icon", iconSize); +} + +ChartListModel::~ChartListModel() +{ +} + +void ChartListModel::initIcon(ChartSubType type, const char *name, int iconSize) +{ + QPixmap icon = QPixmap::fromImage(renderSVGIcon(name, iconSize, true)); + QPixmap iconWarning = icon.copy(); + QPainter painter(&iconWarning); + painter.drawPixmap(0, 0, warningPixmap); + subTypeIcons[(size_t)type].normal = icon; + subTypeIcons[(size_t)type].warning = iconWarning; +} + +const QPixmap &ChartListModel::getIcon(ChartSubType type, bool warning) const +{ + int idx = std::clamp((int)type, 0, (int)ChartSubType::Count); + return warning ? subTypeIcons[idx].warning : subTypeIcons[idx].normal; +} + +int ChartListModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : (int)items.size(); +} + +Qt::ItemFlags ChartListModel::flags(const QModelIndex &index) const +{ + int row = index.row(); + if (index.parent().isValid() || row < 0 || row >= (int)items.size()) + return Qt::NoItemFlags; + return items[row].isHeader ? Qt::ItemIsEnabled + : Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +QVariant ChartListModel::data(const QModelIndex &index, int role) const +{ + int row = index.row(); + if (index.parent().isValid() || row < 0 || row >= (int)items.size()) + return QVariant(); + + switch (role) { + case Qt::FontRole: + return items[row].isHeader ? headerFont : itemFont; + case Qt::DisplayRole: + return items[row].fullName; + case Qt::DecorationRole: + return items[row].warning ? QVariant::fromValue(QIcon(warningPixmap)) + : QVariant(); + case IconRole: + return items[row].isHeader ? QVariant() + : QVariant::fromValue(getIcon(items[row].subtype, items[row].warning)); + case IconSizeRole: + return items[row].isHeader ? QVariant() + : QVariant::fromValue(getIcon(items[row].subtype, items[row].warning).size()); + case ChartNameRole: + return items[row].name; + case IsHeaderRole: + return items[row].isHeader; + case Qt::UserRole: + return items[row].id; + } + return QVariant(); +} + +int ChartListModel::update(const StatsState::ChartList &charts) +{ + // Sort non-recommended entries to the back + std::vector<StatsState::Chart> sorted; + sorted.reserve(charts.charts.size()); + std::copy_if(charts.charts.begin(), charts.charts.end(), std::back_inserter(sorted), + [] (const StatsState::Chart &chart) { return !chart.warning; }); + std::copy_if(charts.charts.begin(), charts.charts.end(), std::back_inserter(sorted), + [] (const StatsState::Chart &chart) { return chart.warning; }); + + beginResetModel(); + items.clear(); + QString act; + int res = -1; + for (const StatsState::Chart &chart: sorted) { + if (act != chart.name) { + items.push_back({ true, chart.name, QString(), (ChartSubType)-1, -1, false }); + act = chart.name; + } + if (charts.selected == chart.id) + res = (int)items.size(); + QString fullName = QString("%1 / %2").arg(chart.name, chart.subtypeName); + items.push_back({ false, chart.subtypeName, fullName, chart.subtype, chart.id, chart.warning }); + } + endResetModel(); + return res; +} diff --git a/stats/chartlistmodel.h b/stats/chartlistmodel.h new file mode 100644 index 000000000..a33f6875a --- /dev/null +++ b/stats/chartlistmodel.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +// A model to feed to the chart-selection combobox +#ifndef CHART_LIST_MODEL_H +#define CHART_LIST_MODEL_H + +#include "statsstate.h" +#include <vector> +#include <QAbstractListModel> +#include <QString> +#include <QFont> +#include <QIcon> +#include <QPixmap> + +class ChartListModel : public QAbstractListModel { + Q_OBJECT +public: + ChartListModel(); + ~ChartListModel(); + + // Returns index of selected item + int update(const StatsState::ChartList &charts); + + static const constexpr int ChartNameRole = Qt::UserRole + 1; + static const constexpr int IsHeaderRole = Qt::UserRole + 2; + static const constexpr int IconRole = Qt::UserRole + 3; + static const constexpr int IconSizeRole = Qt::UserRole + 4; +private: + struct Item { + bool isHeader; + QString name; + QString fullName; + ChartSubType subtype; + int id; + bool warning; + }; + + struct SubTypeIcons { + QPixmap normal; + QPixmap warning; + }; + QPixmap warningPixmap; + SubTypeIcons subTypeIcons[(size_t)ChartSubType::Count]; + + QFont itemFont; + QFont headerFont; + std::vector<Item> items; + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + void initIcon(ChartSubType type, const char *name, int iconSize); + const QPixmap &getIcon(ChartSubType type, bool warning) const; +}; + +#endif |