diff options
Diffstat (limited to 'qt-models')
-rw-r--r-- | qt-models/CMakeLists.txt | 2 | ||||
-rw-r--r-- | qt-models/divesummarymodel.cpp | 258 | ||||
-rw-r--r-- | qt-models/divesummarymodel.h | 58 |
3 files changed, 318 insertions, 0 deletions
diff --git a/qt-models/CMakeLists.txt b/qt-models/CMakeLists.txt index 8a469a5c2..8cdeecd25 100644 --- a/qt-models/CMakeLists.txt +++ b/qt-models/CMakeLists.txt @@ -53,6 +53,8 @@ set(SUBSURFACE_DESKTOP_MODELS_LIB_SRCS set(SUBSURFACE_MOBILE_MODELS_LIB_SRCS divelistmodel.cpp divelistmodel.h + divesummarymodel.cpp + divesummarymodel.h gpslistmodel.cpp gpslistmodel.h messagehandlermodel.cpp diff --git a/qt-models/divesummarymodel.cpp b/qt-models/divesummarymodel.cpp new file mode 100644 index 000000000..f7110fbf4 --- /dev/null +++ b/qt-models/divesummarymodel.cpp @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "qt-models/divesummarymodel.h" +#include "core/dive.h" +#include "core/qthelper.h" + +#include <QLocale> +#include <QDateTime> + +int DiveSummaryModel::rowCount(const QModelIndex &) const +{ + return NUM_ROW; +} + +int DiveSummaryModel::columnCount(const QModelIndex &) const +{ + return (int)results.size(); +} + +QHash<int, QByteArray> DiveSummaryModel::roleNames() const +{ + return { { HEADER_ROLE, "header" }, + { COLUMN0_ROLE, "col0" }, + { COLUMN1_ROLE, "col1" } }; +} + +QVariant DiveSummaryModel::dataDisplay(int row, int col) const +{ + if (col >= (int)results.size()) + return QVariant(); + const Result &res = results[col]; + + switch (row) { + case DIVES: return res.dives; + case DIVES_EAN: return res.divesEAN; + case DIVES_DEEP: return res.divesDeep; + case PLANS: return res.plans; + case TIME: return res.time; + case TIME_MAX: return res.time_max; + case TIME_AVG: return res.time_avg; + case DEPTH_MAX: return res.depth_max; + case DEPTH_AVG: return res.depth_avg; + case SAC_MIN: return res.sac_min; + case SAC_MAX: return res.sac_max; + case SAC_AVG: return res.sac_avg; + } + + return QVariant(); +} + +QVariant DiveSummaryModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) // The "normal" case + return dataDisplay(index.row(), index.column()); + + // The QML case + int row = index.row(); + switch (role) { + case HEADER_ROLE: + return headerData(row, Qt::Vertical, Qt::DisplayRole); + case COLUMN0_ROLE: + return dataDisplay(row, 0); + case COLUMN1_ROLE: + return dataDisplay(row, 1); + } + + // The unsupported case + return QVariant(); +} + +QVariant DiveSummaryModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Vertical || role != Qt::DisplayRole) + return QVariant(); + + switch (section) { + case DIVES: return tr("Total"); + case DIVES_EAN: return tr("EAN dives"); + case DIVES_DEEP: return tr("Deep dives (> 39 m)"); + case PLANS: return tr("Dive plan(s)"); + case TIME: return tr("Total time"); + case TIME_MAX: return tr("Max Time"); + case TIME_AVG: return tr("Avg time"); + case DEPTH_MAX: return tr("Max depth"); + case DEPTH_AVG: return tr("Avg max depth"); + case SAC_MIN: return tr("Min SAC"); + case SAC_MAX: return tr("Max SAC"); + case SAC_AVG: return tr("Avg SAC"); + } + return QVariant(); +} + +struct Stats { + Stats(); + int dives, divesEAN, divesDeep, diveplans; + long divetime, depth; + long divetimeMax, depthMax, sacMin, sacMax; + long divetimeAvg, depthAvg, sacAvg; + long totalSACTime, totalSacVolume; +}; + +Stats::Stats() : + dives(0), divesEAN(0), divesDeep(0), diveplans(0), + divetime(0), depth(0), divetimeMax(0), depthMax(0), + sacMin(99999), sacMax(0), totalSACTime(0), totalSacVolume(0) +{ +} + +static void calculateDive(struct dive *dive, Stats &stats) +{ + if (is_dc_planner(&dive->dc)) { + stats.diveplans++; + return; + } + + // one more real dive + stats.dives++; + + // sum dive in minutes and check for new max. + stats.divetime += dive->duration.seconds; + if (dive->duration.seconds > stats.divetimeMax) + stats.divetimeMax = dive->duration.seconds; + + // sum depth in meters, check for new max. and if dive is a deep dive + stats.depth += dive->maxdepth.mm; + if (dive->maxdepth.mm > stats.depthMax) + stats.depthMax = dive->maxdepth.mm; + if (dive->maxdepth.mm > 39000) + stats.divesDeep++; + + // sum SAC, check for new min/max. + if (dive->sac) { + stats.totalSACTime += dive->duration.seconds; + stats.totalSacVolume += dive->sac * dive->duration.seconds; + if (dive->sac < stats.sacMin) + stats.sacMin = dive->sac; + if (dive->sac > stats.sacMax) + stats.sacMax = dive->sac; + } + + // EAN dive ? + for (int j = 0; j < dive->cylinders.nr; ++j) { + if (dive->cylinders.cylinders[j].gasmix.o2.permille > 210) { + stats.divesEAN++; + break; + } + } +} + +// Returns a (first_dive, last_dive) pair +static Stats loopDives(timestamp_t start) +{ + Stats stats; + struct dive *dive; + int i; + + for_each_dive (i, dive) { + // check if dive is newer than primaryStart (add to first column) + if (dive->when > start) + calculateDive(dive, stats); + } + return stats; +} + +static QString timeString(long duration) +{ + long hours = duration / 3600; + long minutes = (duration - hours * 3600) / 60; + if (hours >= 100) + return QStringLiteral("%1 h").arg(hours); + else + return QStringLiteral("%1:%2").arg(hours).arg(minutes, 2, 10, QChar('0')); +} + +static QString depthString(long depth) +{ + return QStringLiteral("%L1").arg(prefs.units.length == units::METERS ? depth / 1000 : lrint(mm_to_feet(depth))); +} + +static QString volumeString(long volume) +{ + return QStringLiteral("%L1").arg(prefs.units.volume == units::LITER ? volume / 1000 : round(100.0 * ml_to_cuft(volume)) / 100.0); +} + +static DiveSummaryModel::Result formatResults(const Stats &stats) +{ + DiveSummaryModel::Result res; + if (!stats.dives) { + res.dives = QObject::tr("no dives in period"); + res.divesEAN = res.divesDeep = res.plans = QStringLiteral("0"); + res.time = res.time_max = res.time_avg = QStringLiteral("0:00"); + res.depth_max = res.depth_avg = QStringLiteral("-"); + res.sac_min = res.sac_max = res.sac_avg = QStringLiteral("-"); + return res; + } + + // dives + QLocale loc; + res.dives = loc.toString(stats.dives); + res.divesEAN = loc.toString(stats.divesEAN); + res.divesDeep = loc.toString(stats.divesDeep); + + // time + res.time = timeString(stats.divetime); + res.time_max = timeString(stats.divetimeMax); + res.time_avg = timeString(stats.divetime / stats.dives); + + // depth + QString unitText = (prefs.units.length == units::METERS) ? " m" : " ft"; + res.depth_max = depthString(stats.depthMax) + unitText; + res.depth_avg = depthString(stats.depth / stats.dives) + unitText; + + // SAC + if (stats.totalSACTime) { + unitText = (prefs.units.volume == units::LITER) ? " l/min" : " cuft/min"; + long avgSac = stats.totalSacVolume / stats.totalSACTime; + res.sac_avg = volumeString(avgSac) + unitText; + res.sac_min = volumeString(stats.sacMin) + unitText; + res.sac_max = volumeString(stats.sacMax) + unitText; + } else { + res.sac_avg = QStringLiteral("-"); + res.sac_min = QStringLiteral("-"); + res.sac_max = QStringLiteral("-"); + } + + // Diveplan(s) + res.plans = loc.toString(stats.diveplans); + + return res; +} + +void DiveSummaryModel::calc(int column, int period) +{ + if (column >= (int)results.size()) + return; + + QDateTime localTime; + + // Calculate Start of the 2 periods. + timestamp_t now, start; + now = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset(); + start = (period == 0) ? 0 : now - period * 30 * 24 * 60 * 60; + + // Loop over all dives and sum up data + Stats stats = loopDives(start); + results[column] = formatResults(stats); + + // For QML always reload column 0, because that works via roles not columns + if (column != 0) + emit dataChanged(index(0, 0), index(NUM_ROW - 1, 0)); + emit dataChanged(index(0, column), index(NUM_ROW - 1, column)); +} + +void DiveSummaryModel::setNumData(int num) +{ + beginResetModel(); + results.resize(num); + endResetModel(); +} diff --git a/qt-models/divesummarymodel.h b/qt-models/divesummarymodel.h new file mode 100644 index 000000000..13d37ba17 --- /dev/null +++ b/qt-models/divesummarymodel.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef DIVESUMMARYMODEL_H +#define DIVESUMMARYMODEL_H + +#include <QAbstractTableModel> +#include <vector> + +class DiveSummaryModel : public QAbstractTableModel { + Q_OBJECT +public: + enum Row { + DIVES, + DIVES_EAN, + DIVES_DEEP, + PLANS, + TIME, + TIME_MAX, + TIME_AVG, + DEPTH_MAX, + DEPTH_AVG, + SAC_MIN, + SAC_MAX, + SAC_AVG, + NUM_ROW + }; + + // Roles for QML. Amazingly it appears that QML before Qt 5.12 *cannot* + // display run-of-the-mill tabular data. + // Therefore we transform a fixed number of columns, including the header + // into roles that can be displayed by QML. The mind boggles. + enum QMLRoles { + HEADER_ROLE = Qt::UserRole + 1, + COLUMN0_ROLE, + COLUMN1_ROLE, + }; + + struct Result { + QString dives, divesEAN, divesDeep, plans; + QString time, time_max, time_avg; + QString depth_max, depth_avg; + QString sac_min, sac_max, sac_avg; + }; + + Q_INVOKABLE void setNumData(int num); + Q_INVOKABLE void calc(int column, int period); +private: + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash<int, QByteArray> roleNames() const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + QVariant dataDisplay(int row, int col) const; + + std::vector<Result> results; +}; + +#endif |