| 
									
										
										
										
											2017-04-27 20:26:05 +02:00
										 |  |  | // SPDX-License-Identifier: GPL-2.0
 | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | #include "TabDiveStatistics.h"
 | 
					
						
							|  |  |  | #include "ui_TabDiveStatistics.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-24 13:46:14 +01:00
										 |  |  | #include "core/qthelper.h"
 | 
					
						
							|  |  |  | #include "core/selection.h"
 | 
					
						
							|  |  |  | #include "core/statistics.h"
 | 
					
						
							| 
									
										
										
										
											2020-10-26 17:05:37 +01:00
										 |  |  | #include <QLabel>
 | 
					
						
							|  |  |  | #include <QIcon>
 | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | TabDiveStatistics::TabDiveStatistics(QWidget *parent) : TabBase(parent), ui(new Ui::TabDiveStatistics()) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	ui->setupUi(this); | 
					
						
							|  |  |  | 	ui->sacLimits->overrideMaxToolTipText(tr("Highest total SAC of a dive")); | 
					
						
							|  |  |  | 	ui->sacLimits->overrideMinToolTipText(tr("Lowest total SAC of a dive")); | 
					
						
							|  |  |  | 	ui->sacLimits->overrideAvgToolTipText(tr("Average total SAC of all selected dives")); | 
					
						
							|  |  |  | 	ui->tempLimits->overrideMaxToolTipText(tr("Highest temperature")); | 
					
						
							|  |  |  | 	ui->tempLimits->overrideMinToolTipText(tr("Lowest temperature")); | 
					
						
							|  |  |  | 	ui->tempLimits->overrideAvgToolTipText(tr("Average temperature of all selected dives")); | 
					
						
							|  |  |  | 	ui->depthLimits->overrideMaxToolTipText(tr("Deepest dive")); | 
					
						
							|  |  |  | 	ui->depthLimits->overrideMinToolTipText(tr("Shallowest dive")); | 
					
						
							|  |  |  | 	ui->timeLimits->overrideMaxToolTipText(tr("Longest dive")); | 
					
						
							|  |  |  | 	ui->timeLimits->overrideMinToolTipText(tr("Shortest dive")); | 
					
						
							|  |  |  | 	ui->timeLimits->overrideAvgToolTipText(tr("Average length of all selected dives")); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-06 21:31:39 +02:00
										 |  |  | 	connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &TabDiveStatistics::divesChanged); | 
					
						
							| 
									
										
										
										
											2020-05-05 11:55:23 +02:00
										 |  |  | 	connect(&diveListNotifier, &DiveListNotifier::cylinderAdded, this, &TabDiveStatistics::cylinderChanged); | 
					
						
							|  |  |  | 	connect(&diveListNotifier, &DiveListNotifier::cylinderRemoved, this, &TabDiveStatistics::cylinderChanged); | 
					
						
							|  |  |  | 	connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &TabDiveStatistics::cylinderChanged); | 
					
						
							| 
									
										
										
										
											2019-10-06 21:31:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-01 22:15:19 +02:00
										 |  |  | 	const auto l = findChildren<QLabel *>(QString(), Qt::FindDirectChildrenOnly); | 
					
						
							|  |  |  | 	for (QLabel *label: l) { | 
					
						
							|  |  |  | 		label->setAlignment(Qt::AlignHCenter); | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | TabDiveStatistics::~TabDiveStatistics() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	delete ui; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void TabDiveStatistics::clear() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	ui->depthLimits->clear(); | 
					
						
							|  |  |  | 	ui->sacLimits->clear(); | 
					
						
							|  |  |  | 	ui->divesAllText->clear(); | 
					
						
							|  |  |  | 	ui->tempLimits->clear(); | 
					
						
							|  |  |  | 	ui->totalTimeAllText->clear(); | 
					
						
							|  |  |  | 	ui->timeLimits->clear(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-06 21:31:39 +02:00
										 |  |  | // This function gets called if a field gets updated by an undo command.
 | 
					
						
							|  |  |  | // Refresh the corresponding UI field.
 | 
					
						
							|  |  |  | void TabDiveStatistics::divesChanged(const QVector<dive *> &dives, DiveField field) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	// If none of the changed dives is selected, do nothing
 | 
					
						
							|  |  |  | 	if (std::none_of(dives.begin(), dives.end(), [] (const dive *d) { return d->selected; })) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TODO: make this more fine grained. Currently, the core can only calculate *all* statistics.
 | 
					
						
							| 
									
										
										
										
											2019-10-13 12:44:39 +02:00
										 |  |  | 	if (field.duration || field.depth || field.mode || field.air_temp || field.water_temp) | 
					
						
							| 
									
										
										
										
											2019-10-06 21:31:39 +02:00
										 |  |  | 		updateData(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-05 11:55:23 +02:00
										 |  |  | void TabDiveStatistics::cylinderChanged(dive *d) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	// If the changed dive is not selected, do nothing
 | 
					
						
							|  |  |  | 	if (!d->selected) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	updateData(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | void TabDiveStatistics::updateData() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-10-06 16:30:57 +02:00
										 |  |  | 	stats_t stats_selection; | 
					
						
							|  |  |  | 	calculate_stats_selected(&stats_selection); | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	clear(); | 
					
						
							| 
									
										
										
										
											2021-10-11 18:53:52 +02:00
										 |  |  | 	if (amount_selected > 1 && stats_selection.selection_size >= 1) { | 
					
						
							| 
									
										
										
										
											2019-09-15 10:16:07 +02:00
										 |  |  | 		ui->depthLimits->setMaximum(get_depth_string(stats_selection.max_depth, true)); | 
					
						
							| 
									
										
										
										
											2017-04-27 21:46:29 +02:00
										 |  |  | 		ui->depthLimits->setMinimum(get_depth_string(stats_selection.min_depth, true)); | 
					
						
							| 
									
										
										
										
											2018-12-16 21:01:25 +01:00
										 |  |  | 		ui->depthLimits->setAverage(get_depth_string(stats_selection.combined_max_depth.mm / stats_selection.selection_size, true)); | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2019-09-15 10:16:07 +02:00
										 |  |  | 		ui->depthLimits->setMaximum(""); | 
					
						
							| 
									
										
										
										
											2017-04-27 21:46:29 +02:00
										 |  |  | 		ui->depthLimits->setMinimum(""); | 
					
						
							| 
									
										
										
										
											2019-09-15 10:16:07 +02:00
										 |  |  | 		ui->depthLimits->setAverage(get_depth_string(stats_selection.max_depth, true)); | 
					
						
							| 
									
										
										
										
											2018-12-16 21:01:25 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-27 21:46:29 +02:00
										 |  |  | 	if (stats_selection.max_sac.mliter && (stats_selection.max_sac.mliter != stats_selection.avg_sac.mliter)) | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 		ui->sacLimits->setMaximum(get_volume_string(stats_selection.max_sac, true).append(tr("/min"))); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		ui->sacLimits->setMaximum(""); | 
					
						
							| 
									
										
										
										
											2017-04-27 21:46:29 +02:00
										 |  |  | 	if (stats_selection.min_sac.mliter && (stats_selection.min_sac.mliter != stats_selection.avg_sac.mliter)) | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 		ui->sacLimits->setMinimum(get_volume_string(stats_selection.min_sac, true).append(tr("/min"))); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		ui->sacLimits->setMinimum(""); | 
					
						
							|  |  |  | 	if (stats_selection.avg_sac.mliter) | 
					
						
							|  |  |  | 		ui->sacLimits->setAverage(get_volume_string(stats_selection.avg_sac, true).append(tr("/min"))); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		ui->sacLimits->setAverage(""); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-15 10:11:27 +02:00
										 |  |  | 	if (stats_selection.combined_count > 1) { | 
					
						
							|  |  |  | 		ui->tempLimits->setMaximum(get_temperature_string(stats_selection.max_temp, true)); | 
					
						
							|  |  |  | 		ui->tempLimits->setMinimum(get_temperature_string(stats_selection.min_temp, true)); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-18 21:55:57 +01:00
										 |  |  | 	if (stats_selection.combined_temp.mkelvin && stats_selection.combined_count) { | 
					
						
							|  |  |  | 		temperature_t avg_temp; | 
					
						
							|  |  |  | 		avg_temp.mkelvin = stats_selection.combined_temp.mkelvin / stats_selection.combined_count; | 
					
						
							|  |  |  | 		ui->tempLimits->setAverage(get_temperature_string(avg_temp, true)); | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 18:58:16 +02:00
										 |  |  | 	bool is_freedive = current_dive && current_dive->dc.divemode == FREEDIVE; | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	ui->divesAllText->setText(QString::number(stats_selection.selection_size)); | 
					
						
							| 
									
										
										
										
											2020-04-13 18:58:16 +02:00
										 |  |  | 	ui->totalTimeAllText->setText(get_dive_duration_string(stats_selection.total_time.seconds, tr("h"), tr("min"), tr("sec"), " ", is_freedive)); | 
					
						
							| 
									
										
										
										
											2020-11-02 12:10:33 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	int seconds = stats_selection.total_time.seconds; | 
					
						
							|  |  |  | 	if (stats_selection.selection_size) | 
					
						
							|  |  |  | 		seconds /= stats_selection.selection_size; | 
					
						
							| 
									
										
										
										
											2017-05-11 22:43:36 +02:00
										 |  |  | 	ui->timeLimits->setAverage(get_dive_duration_string(seconds, tr("h"), tr("min"), tr("sec"), | 
					
						
							| 
									
										
										
										
											2020-04-13 18:58:16 +02:00
										 |  |  | 			" ", is_freedive)); | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	if (amount_selected > 1) { | 
					
						
							| 
									
										
										
										
											2020-04-13 18:58:16 +02:00
										 |  |  | 		ui->timeLimits->setMaximum(get_dive_duration_string(stats_selection.longest_time.seconds, tr("h"), tr("min"), tr("sec"), " ", is_freedive)); | 
					
						
							|  |  |  | 		ui->timeLimits->setMinimum(get_dive_duration_string(stats_selection.shortest_time.seconds, tr("h"), tr("min"), tr("sec"), " ", is_freedive)); | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		ui->timeLimits->setMaximum(""); | 
					
						
							|  |  |  | 		ui->timeLimits->setMinimum(""); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-03 23:05:44 +02:00
										 |  |  | 	QVector<QPair<QString, int> > gasUsed = selectedDivesGasUsed(); | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	QString gasUsedString; | 
					
						
							|  |  |  | 	volume_t vol; | 
					
						
							| 
									
										
										
										
											2019-08-04 18:44:57 +02:00
										 |  |  | 	while (!gasUsed.isEmpty()) { | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 		QPair<QString, int> gasPair = gasUsed.last(); | 
					
						
							|  |  |  | 		gasUsed.pop_back(); | 
					
						
							|  |  |  | 		vol.mliter = gasPair.second; | 
					
						
							|  |  |  | 		gasUsedString.append(gasPair.first).append(": ").append(get_volume_string(vol, true)).append("\n"); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	volume_t o2_tot = {}, he_tot = {}; | 
					
						
							|  |  |  | 	selected_dives_gas_parts(&o2_tot, &he_tot); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* No need to show the gas mixing information if diving
 | 
					
						
							|  |  |  | 		* with pure air, and only display the he / O2 part when | 
					
						
							|  |  |  | 		* it is used. | 
					
						
							|  |  |  | 		*/ | 
					
						
							|  |  |  | 	if (he_tot.mliter || o2_tot.mliter) { | 
					
						
							|  |  |  | 		gasUsedString.append(tr("These gases could be\nmixed from Air and using:\n")); | 
					
						
							| 
									
										
										
										
											2017-04-27 21:47:51 +02:00
										 |  |  | 		if (he_tot.mliter) { | 
					
						
							|  |  |  | 			gasUsedString.append(tr("He")); | 
					
						
							|  |  |  | 			gasUsedString.append(QString(": %1").arg(get_volume_string(he_tot, true))); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 		if (he_tot.mliter && o2_tot.mliter) | 
					
						
							| 
									
										
										
										
											2017-04-27 21:47:51 +02:00
										 |  |  | 			gasUsedString.append(" ").append(tr("and")).append(" "); | 
					
						
							|  |  |  | 		if (o2_tot.mliter) { | 
					
						
							|  |  |  | 			gasUsedString.append(tr("O₂")); | 
					
						
							|  |  |  | 			gasUsedString.append(QString(": %2\n").arg(get_volume_string(o2_tot, true))); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-04-04 19:21:30 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	ui->gasConsumption->setText(gasUsedString); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-26 17:05:37 +01:00
										 |  |  | double MinMaxAvgWidget::average() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return avgValue->text().toDouble(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | double MinMaxAvgWidget::maximum() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return maxValue->text().toDouble(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | double MinMaxAvgWidget::minimum() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return minValue->text().toDouble(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MinMaxAvgWidget::MinMaxAvgWidget(QWidget *parent) : QWidget(parent) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	avgIco = new QLabel(this); | 
					
						
							|  |  |  | 	avgIco->setPixmap(QIcon(":value-average-icon").pixmap(16, 16)); | 
					
						
							|  |  |  | 	avgIco->setToolTip(gettextFromC::tr("Average")); | 
					
						
							|  |  |  | 	minIco = new QLabel(this); | 
					
						
							|  |  |  | 	minIco->setPixmap(QIcon(":value-minimum-icon").pixmap(16, 16)); | 
					
						
							|  |  |  | 	minIco->setToolTip(gettextFromC::tr("Minimum")); | 
					
						
							|  |  |  | 	maxIco = new QLabel(this); | 
					
						
							|  |  |  | 	maxIco->setPixmap(QIcon(":value-maximum-icon").pixmap(16, 16)); | 
					
						
							|  |  |  | 	maxIco->setToolTip(gettextFromC::tr("Maximum")); | 
					
						
							|  |  |  | 	avgValue = new QLabel(this); | 
					
						
							|  |  |  | 	minValue = new QLabel(this); | 
					
						
							|  |  |  | 	maxValue = new QLabel(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	QGridLayout *formLayout = new QGridLayout; | 
					
						
							|  |  |  | 	formLayout->addWidget(maxIco, 0, 0); | 
					
						
							|  |  |  | 	formLayout->addWidget(maxValue, 0, 1); | 
					
						
							|  |  |  | 	formLayout->addWidget(avgIco, 1, 0); | 
					
						
							|  |  |  | 	formLayout->addWidget(avgValue, 1, 1); | 
					
						
							|  |  |  | 	formLayout->addWidget(minIco, 2, 0); | 
					
						
							|  |  |  | 	formLayout->addWidget(minValue, 2, 1); | 
					
						
							|  |  |  | 	setLayout(formLayout); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::clear() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	avgValue->setText(QString()); | 
					
						
							|  |  |  | 	maxValue->setText(QString()); | 
					
						
							|  |  |  | 	minValue->setText(QString()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::setAverage(double average) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	avgValue->setText(QString::number(average)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::setMaximum(double maximum) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	maxValue->setText(QString::number(maximum)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | void MinMaxAvgWidget::setMinimum(double minimum) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	minValue->setText(QString::number(minimum)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::setAverage(const QString &average) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	avgValue->setText(average); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::setMaximum(const QString &maximum) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	maxValue->setText(maximum); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::setMinimum(const QString &minimum) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	minValue->setText(minimum); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::overrideMinToolTipText(const QString &newTip) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	minIco->setToolTip(newTip); | 
					
						
							|  |  |  | 	minValue->setToolTip(newTip); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::overrideAvgToolTipText(const QString &newTip) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	avgIco->setToolTip(newTip); | 
					
						
							|  |  |  | 	avgValue->setToolTip(newTip); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::overrideMaxToolTipText(const QString &newTip) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	maxIco->setToolTip(newTip); | 
					
						
							|  |  |  | 	maxValue->setToolTip(newTip); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void MinMaxAvgWidget::setAvgVisibility(bool visible) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	avgIco->setVisible(visible); | 
					
						
							|  |  |  | 	avgValue->setVisible(visible); | 
					
						
							|  |  |  | } |