2020-02-08 11:06:57 +00:00
|
|
|
// 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" },
|
2020-02-08 11:22:52 +00:00
|
|
|
{ COLUMN1_ROLE, "col1" },
|
2020-03-30 21:31:35 +00:00
|
|
|
{ SECTION_ROLE, "section" } };
|
2020-02-08 11:06:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-02-08 11:22:52 +00:00
|
|
|
case SECTION_ROLE:
|
|
|
|
switch (row) {
|
|
|
|
case DIVES ... PLANS: return tr("Number of dives");
|
|
|
|
case TIME ... TIME_AVG: return tr("Time");
|
|
|
|
case DEPTH_MAX ... DEPTH_AVG: return tr("Depth");
|
|
|
|
case SAC_MIN ... SAC_AVG: return tr("SAC");
|
|
|
|
default: return QVariant();
|
|
|
|
}
|
2020-02-08 11:06:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2020-03-30 21:31:35 +00:00
|
|
|
int64_t divetime, depth;
|
|
|
|
int64_t divetimeMax, depthMax, sacMin, sacMax;
|
|
|
|
int64_t divetimeAvg, depthAvg, sacAvg;
|
|
|
|
int64_t totalSACTime, totalSacVolume;
|
2020-02-08 11:06:57 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-03-30 21:31:35 +00:00
|
|
|
static QString timeString(int64_t duration)
|
2020-02-08 11:06:57 +00:00
|
|
|
{
|
2020-03-30 21:31:35 +00:00
|
|
|
int64_t hours = duration / 3600;
|
|
|
|
int64_t minutes = (duration - hours * 3600) / 60;
|
2020-02-08 11:06:57 +00:00
|
|
|
if (hours >= 100)
|
|
|
|
return QStringLiteral("%1 h").arg(hours);
|
|
|
|
else
|
|
|
|
return QStringLiteral("%1:%2").arg(hours).arg(minutes, 2, 10, QChar('0'));
|
|
|
|
}
|
|
|
|
|
2020-03-30 21:31:35 +00:00
|
|
|
static QString depthString(int64_t depth)
|
2020-02-08 11:06:57 +00:00
|
|
|
{
|
|
|
|
return QStringLiteral("%L1").arg(prefs.units.length == units::METERS ? depth / 1000 : lrint(mm_to_feet(depth)));
|
|
|
|
}
|
|
|
|
|
2020-03-30 21:31:35 +00:00
|
|
|
static QString volumeString(int64_t volume)
|
2020-02-08 11:06:57 +00:00
|
|
|
{
|
|
|
|
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";
|
2020-03-30 21:31:35 +00:00
|
|
|
int64_t avgSac = stats.totalSacVolume / stats.totalSACTime;
|
2020-02-08 11:06:57 +00:00
|
|
|
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;
|
|
|
|
|
2020-02-07 23:49:31 +00:00
|
|
|
QDateTime currentTime = QDateTime::currentDateTime();
|
|
|
|
QDateTime startTime = currentTime;
|
|
|
|
|
|
|
|
// Calculate Start of the periods.
|
|
|
|
switch (period) {
|
|
|
|
case 0: // having startTime == currentTime is used as special case below
|
|
|
|
break;
|
|
|
|
case 1: startTime = currentTime.addMonths(-1);
|
|
|
|
break;
|
|
|
|
case 2: startTime = currentTime.addMonths(-3);
|
|
|
|
break;
|
|
|
|
case 3: startTime = currentTime.addMonths(-6);
|
|
|
|
break;
|
|
|
|
case 4: startTime = currentTime.addYears(-1);
|
|
|
|
break;
|
|
|
|
default: qWarning("DiveSummaryModel::calc called with invalid period");
|
|
|
|
}
|
|
|
|
timestamp_t start;
|
|
|
|
if (startTime == currentTime)
|
|
|
|
start = 0;
|
|
|
|
else
|
|
|
|
start = startTime.toMSecsSinceEpoch() / 1000L + gettimezoneoffset();
|
2020-02-08 11:06:57 +00:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|