mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	Match subsurface-desktop's invalid dive behavior Signed-off-by: Tim Segers <tsegers@pm.me>
		
			
				
	
	
		
			287 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			287 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // 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();
 | |
| }
 |