aboutsummaryrefslogtreecommitdiffstats
path: root/qt-models/divesummarymodel.cpp
blob: b679da1f0b5687003320a34c551d146c8203d34d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// 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"  },
		 { SECTION_ROLE, "section" } };
}

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);
	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();
		}
	}

	// 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;
	int64_t divetime, depth;
	int64_t divetimeMax, depthMax, sacMin, sacMax;
	int64_t divetimeAvg, depthAvg, sacAvg;
	int64_t 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;
	}

	if (dive->invalid) {
		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 (get_cylinder(dive, 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(int64_t duration)
{
	int64_t hours = duration / 3600;
	int64_t 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(int64_t depth)
{
	return QStringLiteral("%L1").arg(prefs.units.length == units::METERS ? depth / 1000 : lrint(mm_to_feet(depth)));
}

static QString volumeString(int64_t 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";
		int64_t 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 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 = dateTimeToTimestamp(startTime) + gettimezoneoffset();

	// 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();
}