aboutsummaryrefslogtreecommitdiffstats
path: root/qt-models
diff options
context:
space:
mode:
Diffstat (limited to 'qt-models')
-rw-r--r--qt-models/CMakeLists.txt2
-rw-r--r--qt-models/divesummarymodel.cpp258
-rw-r--r--qt-models/divesummarymodel.h58
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