From a1ac99d5ed78d9c2e04dc472cf56dd1a05dedd03 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 1 May 2024 23:16:00 +0200 Subject: [PATCH] core: C++-ify statistics.c The old code was wild: For the yearly statistics it would allocate one entry per dive in the log. Of course, it would also leak C-style strings. Convert the whole thing to somewhat idiomatic C++. Somewhat wasted work, because I'd like to convert the whole thing to the new statistics code. But let's finish the conversion to C++ first. Signed-off-by: Berthold Stoeger --- Subsurface-mobile.pro | 2 +- core/CMakeLists.txt | 2 +- core/dive.cpp | 2 +- core/divelogexportlogic.cpp | 43 +- core/qthelper.cpp | 3 +- core/statistics.c | 415 ------------------ core/statistics.cpp | 369 ++++++++++++++++ core/statistics.h | 106 ++--- .../tab-widgets/TabDiveInformation.cpp | 3 +- .../tab-widgets/TabDiveStatistics.cpp | 12 +- desktop-widgets/templatelayout.cpp | 7 +- qt-models/yearlystatisticsmodel.cpp | 114 +++-- 12 files changed, 496 insertions(+), 582 deletions(-) delete mode 100644 core/statistics.c create mode 100644 core/statistics.cpp diff --git a/Subsurface-mobile.pro b/Subsurface-mobile.pro index f60b28f78..f773abcd1 100644 --- a/Subsurface-mobile.pro +++ b/Subsurface-mobile.pro @@ -75,7 +75,7 @@ SOURCES += subsurface-mobile-main.cpp \ core/import-divinglog.cpp \ core/import-csv.cpp \ core/save-html.cpp \ - core/statistics.c \ + core/statistics.cpp \ core/worldmap-save.cpp \ core/libdivecomputer.cpp \ core/version.cpp \ diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 9f653126c..f9f0e4da6 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -165,7 +165,7 @@ set(SUBSURFACE_CORE_LIB_SRCS sha1.cpp sha1.h ssrf.h - statistics.c + statistics.cpp statistics.h string-format.h string-format.cpp diff --git a/core/dive.cpp b/core/dive.cpp index e5fd35ac4..497774e0f 100644 --- a/core/dive.cpp +++ b/core/dive.cpp @@ -431,7 +431,7 @@ static bool cylinder_used(const cylinder_t *cyl) start_mbar = cyl->start.mbar ?: cyl->sample_start.mbar; end_mbar = cyl->end.mbar ?: cyl->sample_end.mbar; - // More than 5 bar used? This matches statistics.c + // More than 5 bar used? This matches statistics.cpp // heuristics return start_mbar > end_mbar + SOME_GAS; } diff --git a/core/divelogexportlogic.cpp b/core/divelogexportlogic.cpp index 06690e4d2..63a7c1add 100644 --- a/core/divelogexportlogic.cpp +++ b/core/divelogexportlogic.cpp @@ -75,45 +75,42 @@ static void exportHTMLstatistics(const QString filename, struct htmlExportSettin QFile file(filename); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); - stats_summary_auto_free stats; stats_t total_stats; - calculate_stats_summary(&stats, hes.selectedOnly); + stats_summary stats = calculate_stats_summary(hes.selectedOnly); total_stats.selection_size = 0; total_stats.total_time.seconds = 0; - int i = 0; out << "divestat=["; if (hes.yearlyStatistics) { - while (stats.stats_yearly != NULL && stats.stats_yearly[i].period) { + for (const auto &s: stats.stats_yearly) { out << "{"; - out << "\"YEAR\":\"" << stats.stats_yearly[i].period << "\","; - out << "\"DIVES\":\"" << stats.stats_yearly[i].selection_size << "\","; - out << "\"TOTAL_TIME\":\"" << get_dive_duration_string(stats.stats_yearly[i].total_time.seconds, + out << "\"YEAR\":\"" << s.period << "\","; + out << "\"DIVES\":\"" << s.selection_size << "\","; + out << "\"TOTAL_TIME\":\"" << get_dive_duration_string(s.total_time.seconds, gettextFromC::tr("h"), gettextFromC::tr("min"), gettextFromC::tr("sec"), " ") << "\","; - out << "\"AVERAGE_TIME\":\"" << formatMinutes(stats.stats_yearly[i].total_time.seconds / stats.stats_yearly[i].selection_size) << "\","; - out << "\"SHORTEST_TIME\":\"" << formatMinutes(stats.stats_yearly[i].shortest_time.seconds) << "\","; - out << "\"LONGEST_TIME\":\"" << formatMinutes(stats.stats_yearly[i].longest_time.seconds) << "\","; - out << "\"AVG_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].avg_depth) << "\","; - out << "\"MIN_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].min_depth) << "\","; - out << "\"MAX_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].max_depth) << "\","; - out << "\"AVG_SAC\":\"" << get_volume_string(stats.stats_yearly[i].avg_sac) << "\","; - out << "\"MIN_SAC\":\"" << get_volume_string(stats.stats_yearly[i].min_sac) << "\","; - out << "\"MAX_SAC\":\"" << get_volume_string(stats.stats_yearly[i].max_sac) << "\","; - if (stats.stats_yearly[i].combined_count) { + out << "\"AVERAGE_TIME\":\"" << formatMinutes(s.total_time.seconds / s.selection_size) << "\","; + out << "\"SHORTEST_TIME\":\"" << formatMinutes(s.shortest_time.seconds) << "\","; + out << "\"LONGEST_TIME\":\"" << formatMinutes(s.longest_time.seconds) << "\","; + out << "\"AVG_DEPTH\":\"" << get_depth_string(s.avg_depth) << "\","; + out << "\"MIN_DEPTH\":\"" << get_depth_string(s.min_depth) << "\","; + out << "\"MAX_DEPTH\":\"" << get_depth_string(s.max_depth) << "\","; + out << "\"AVG_SAC\":\"" << get_volume_string(s.avg_sac) << "\","; + out << "\"MIN_SAC\":\"" << get_volume_string(s.min_sac) << "\","; + out << "\"MAX_SAC\":\"" << get_volume_string(s.max_sac) << "\","; + if (s.combined_count) { temperature_t avg_temp; - avg_temp.mkelvin = stats.stats_yearly[i].combined_temp.mkelvin / stats.stats_yearly[i].combined_count; + avg_temp.mkelvin = s.combined_temp.mkelvin / s.combined_count; out << "\"AVG_TEMP\":\"" << get_temperature_string(avg_temp) << "\","; } else { out << "\"AVG_TEMP\":\"0.0\","; } - out << "\"MIN_TEMP\":\"" << (stats.stats_yearly[i].min_temp.mkelvin == 0 ? 0 : get_temperature_string(stats.stats_yearly[i].min_temp)) << "\","; - out << "\"MAX_TEMP\":\"" << (stats.stats_yearly[i].max_temp.mkelvin == 0 ? 0 : get_temperature_string(stats.stats_yearly[i].max_temp)) << "\","; + out << "\"MIN_TEMP\":\"" << (s.min_temp.mkelvin == 0 ? 0 : get_temperature_string(s.min_temp)) << "\","; + out << "\"MAX_TEMP\":\"" << (s.max_temp.mkelvin == 0 ? 0 : get_temperature_string(s.max_temp)) << "\","; out << "},"; - total_stats.selection_size += stats.stats_yearly[i].selection_size; - total_stats.total_time.seconds += stats.stats_yearly[i].total_time.seconds; - i++; + total_stats.selection_size += s.selection_size; + total_stats.total_time.seconds += s.total_time.seconds; } exportHTMLstatisticsTotal(out, &total_stats); } diff --git a/core/qthelper.cpp b/core/qthelper.cpp index a094efca4..e09973a17 100644 --- a/core/qthelper.cpp +++ b/core/qthelper.cpp @@ -385,14 +385,13 @@ QVector> selectedDivesGasUsed() int j; QMap gasUsed; for (dive *d: getDiveSelection()) { - volume_t *diveGases = get_gas_used(d); + std::vector diveGases = get_gas_used(d); for (j = 0; j < d->cylinders.nr; j++) { if (diveGases[j].mliter) { QString gasName = gasname(get_cylinder(d, j)->gasmix); gasUsed[gasName] += diveGases[j].mliter; } } - free(diveGases); } QVector> gasUsedOrdered; gasUsedOrdered.reserve(gasUsed.size()); diff --git a/core/statistics.c b/core/statistics.c deleted file mode 100644 index aa9d27c96..000000000 --- a/core/statistics.c +++ /dev/null @@ -1,415 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* statistics.c - * - * core logic for the Info & Stats page - - * void calculate_stats_summary(struct stats_summary *out, bool selected_only); - * void calculate_stats_selected(stats_t *stats_selection); - */ - -#include "statistics.h" -#include "dive.h" -#include "divelog.h" -#include "event.h" -#include "gettext.h" -#include "sample.h" -#include "subsurface-time.h" -#include "trip.h" -#include "units.h" - -#include -#include -#include - -static void process_temperatures(struct dive *dp, stats_t *stats) -{ - temperature_t min_temp, mean_temp, max_temp = {.mkelvin = 0}; - - max_temp.mkelvin = dp->maxtemp.mkelvin; - if (max_temp.mkelvin && (!stats->max_temp.mkelvin || max_temp.mkelvin > stats->max_temp.mkelvin)) - stats->max_temp.mkelvin = max_temp.mkelvin; - - min_temp.mkelvin = dp->mintemp.mkelvin; - if (min_temp.mkelvin && (!stats->min_temp.mkelvin || min_temp.mkelvin < stats->min_temp.mkelvin)) - stats->min_temp.mkelvin = min_temp.mkelvin; - - if (min_temp.mkelvin || max_temp.mkelvin) { - mean_temp.mkelvin = min_temp.mkelvin; - if (mean_temp.mkelvin) - mean_temp.mkelvin = (mean_temp.mkelvin + max_temp.mkelvin) / 2; - else - mean_temp.mkelvin = max_temp.mkelvin; - stats->combined_temp.mkelvin += mean_temp.mkelvin; - stats->combined_count++; - } -} - -static void process_dive(struct dive *dive, stats_t *stats) -{ - int old_tadt, sac_time = 0; - int32_t duration = dive->duration.seconds; - - old_tadt = stats->total_average_depth_time.seconds; - stats->total_time.seconds += duration; - if (duration > stats->longest_time.seconds) - stats->longest_time.seconds = duration; - if (stats->shortest_time.seconds == 0 || duration < stats->shortest_time.seconds) - stats->shortest_time.seconds = duration; - if (dive->maxdepth.mm > stats->max_depth.mm) - stats->max_depth.mm = dive->maxdepth.mm; - if (stats->min_depth.mm == 0 || dive->maxdepth.mm < stats->min_depth.mm) - stats->min_depth.mm = dive->maxdepth.mm; - stats->combined_max_depth.mm += dive->maxdepth.mm; - - process_temperatures(dive, stats); - - /* Maybe we should drop zero-duration dives */ - if (!duration) - return; - if (dive->meandepth.mm) { - stats->total_average_depth_time.seconds += duration; - stats->avg_depth.mm = lrint((1.0 * old_tadt * stats->avg_depth.mm + - duration * dive->meandepth.mm) / - stats->total_average_depth_time.seconds); - } - if (dive->sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */ - sac_time = stats->total_sac_time.seconds + duration; - stats->avg_sac.mliter = lrint((1.0 * stats->total_sac_time.seconds * stats->avg_sac.mliter + - duration * dive->sac) / - sac_time); - if (dive->sac > stats->max_sac.mliter) - stats->max_sac.mliter = dive->sac; - if (stats->min_sac.mliter == 0 || dive->sac < stats->min_sac.mliter) - stats->min_sac.mliter = dive->sac; - stats->total_sac_time.seconds = sac_time; - } -} - -/* - * Calculate a summary of the statistics and put in the stats_summary - * structure provided in the first parameter. - * Before first use, it should be initialized with init_stats_summary(). - * After use, memory must be released with free_stats_summary(). - */ -void calculate_stats_summary(struct stats_summary *out, bool selected_only) -{ - int idx; - int t_idx, d_idx, r; - struct dive *dp; - struct tm tm; - int current_year = 0; - int current_month = 0; - int year_iter = 0; - int month_iter = 0; - int prev_month = 0, prev_year = 0; - int trip_iter = 0; - dive_trip_t *trip_ptr = 0; - size_t size, tsize, dsize, tmsize; - stats_t stats = { 0 }; - - if (divelog.dives->nr > 0) { - stats.shortest_time.seconds = divelog.dives->dives[0]->duration.seconds; - stats.min_depth.mm = divelog.dives->dives[0]->maxdepth.mm; - stats.selection_size = divelog.dives->nr; - } - - /* allocate sufficient space to hold the worst - * case (one dive per year or all dives during - * one month) for yearly and monthly statistics*/ - - size = sizeof(stats_t) * (divelog.dives->nr + 1); - tsize = sizeof(stats_t) * (NUM_DIVEMODE + 1); - dsize = sizeof(stats_t) * ((STATS_MAX_DEPTH / STATS_DEPTH_BUCKET) + 1); - tmsize = sizeof(stats_t) * ((STATS_MAX_TEMP / STATS_TEMP_BUCKET) + 1); - free_stats_summary(out); - out->stats_yearly = malloc(size); - out->stats_monthly = malloc(size); - out->stats_by_trip = malloc(size); - out->stats_by_type = malloc(tsize); - out->stats_by_depth = malloc(dsize); - out->stats_by_temp = malloc(tmsize); - if (!out->stats_yearly || !out->stats_monthly || !out->stats_by_trip || - !out->stats_by_type || !out->stats_by_depth || !out->stats_by_temp) - return; - memset(out->stats_yearly, 0, size); - memset(out->stats_monthly, 0, size); - memset(out->stats_by_trip, 0, size); - memset(out->stats_by_type, 0, tsize); - memset(out->stats_by_depth, 0, dsize); - memset(out->stats_by_temp, 0, tmsize); - out->stats_yearly[0].is_year = true; - - /* Setting the is_trip to true to show the location as first - * field in the statistics window */ - out->stats_by_type[0].location = strdup(translate("gettextFromC", "All (by type stats)")); - out->stats_by_type[0].is_trip = true; - out->stats_by_type[1].location = strdup(translate("gettextFromC", divemode_text_ui[OC])); - out->stats_by_type[1].is_trip = true; - out->stats_by_type[2].location = strdup(translate("gettextFromC", divemode_text_ui[CCR])); - out->stats_by_type[2].is_trip = true; - out->stats_by_type[3].location = strdup(translate("gettextFromC", divemode_text_ui[PSCR])); - out->stats_by_type[3].is_trip = true; - out->stats_by_type[4].location = strdup(translate("gettextFromC", divemode_text_ui[FREEDIVE])); - out->stats_by_type[4].is_trip = true; - - out->stats_by_depth[0].location = strdup(translate("gettextFromC", "All (by max depth stats)")); - out->stats_by_depth[0].is_trip = true; - - out->stats_by_temp[0].location = strdup(translate("gettextFromC", "All (by min. temp stats)")); - out->stats_by_temp[0].is_trip = true; - - /* this relies on the fact that the dives in the dive_table - * are in chronological order */ - for_each_dive (idx, dp) { - if (selected_only && !dp->selected) - continue; - if (dp->invalid) - continue; - process_dive(dp, &stats); - - /* yearly statistics */ - utc_mkdate(dp->when, &tm); - if (current_year == 0) - current_year = tm.tm_year; - - if (current_year != tm.tm_year) { - current_year = tm.tm_year; - process_dive(dp, &(out->stats_yearly[++year_iter])); - out->stats_yearly[year_iter].is_year = true; - } else { - process_dive(dp, &(out->stats_yearly[year_iter])); - } - out->stats_yearly[year_iter].selection_size++; - out->stats_yearly[year_iter].period = current_year; - - /* stats_by_type[0] is all the dives combined */ - out->stats_by_type[0].selection_size++; - process_dive(dp, &(out->stats_by_type[0])); - - process_dive(dp, &(out->stats_by_type[dp->dc.divemode + 1])); - out->stats_by_type[dp->dc.divemode + 1].selection_size++; - - /* stats_by_depth[0] is all the dives combined */ - out->stats_by_depth[0].selection_size++; - process_dive(dp, &(out->stats_by_depth[0])); - - d_idx = dp->maxdepth.mm / (STATS_DEPTH_BUCKET * 1000); - if (d_idx < 0) - d_idx = 0; - if (d_idx >= STATS_MAX_DEPTH / STATS_DEPTH_BUCKET) - d_idx = STATS_MAX_DEPTH / STATS_DEPTH_BUCKET - 1; - process_dive(dp, &(out->stats_by_depth[d_idx + 1])); - out->stats_by_depth[d_idx + 1].selection_size++; - - /* stats_by_temp[0] is all the dives combined */ - out->stats_by_temp[0].selection_size++; - process_dive(dp, &(out->stats_by_temp[0])); - - t_idx = ((int)mkelvin_to_C(dp->mintemp.mkelvin)) / STATS_TEMP_BUCKET; - if (t_idx < 0) - t_idx = 0; - if (t_idx >= STATS_MAX_TEMP / STATS_TEMP_BUCKET) - t_idx = STATS_MAX_TEMP / STATS_TEMP_BUCKET - 1; - process_dive(dp, &(out->stats_by_temp[t_idx + 1])); - out->stats_by_temp[t_idx + 1].selection_size++; - - if (dp->divetrip != NULL) { - if (trip_ptr != dp->divetrip) { - trip_ptr = dp->divetrip; - trip_iter++; - } - - /* stats_by_trip[0] is all the dives combined */ - out->stats_by_trip[0].selection_size++; - process_dive(dp, &(out->stats_by_trip[0])); - out->stats_by_trip[0].is_trip = true; - out->stats_by_trip[0].location = strdup(translate("gettextFromC", "All (by trip stats)")); - - process_dive(dp, &(out->stats_by_trip[trip_iter])); - out->stats_by_trip[trip_iter].selection_size++; - out->stats_by_trip[trip_iter].is_trip = true; - out->stats_by_trip[trip_iter].location = dp->divetrip->location; - } - - /* monthly statistics */ - if (current_month == 0) { - current_month = tm.tm_mon + 1; - } else { - if (current_month != tm.tm_mon + 1) - current_month = tm.tm_mon + 1; - if (prev_month != current_month || prev_year != current_year) - month_iter++; - } - process_dive(dp, &(out->stats_monthly[month_iter])); - out->stats_monthly[month_iter].selection_size++; - out->stats_monthly[month_iter].period = current_month; - prev_month = current_month; - prev_year = current_year; - } - - /* add labels for depth ranges up to maximum depth seen */ - if (out->stats_by_depth[0].selection_size) { - d_idx = out->stats_by_depth[0].max_depth.mm; - if (d_idx > STATS_MAX_DEPTH * 1000) - d_idx = STATS_MAX_DEPTH * 1000; - for (r = 0; r * (STATS_DEPTH_BUCKET * 1000) < d_idx; ++r) - out->stats_by_depth[r+1].is_trip = true; - } - - /* add labels for depth ranges up to maximum temperature seen */ - if (out->stats_by_temp[0].selection_size) { - t_idx = (int)mkelvin_to_C(out->stats_by_temp[0].max_temp.mkelvin); - if (t_idx > STATS_MAX_TEMP) - t_idx = STATS_MAX_TEMP; - for (r = 0; r * STATS_TEMP_BUCKET < t_idx; ++r) - out->stats_by_temp[r+1].is_trip = true; - } -} - -void free_stats_summary(struct stats_summary *stats) -{ - free(stats->stats_yearly); - free(stats->stats_monthly); - free(stats->stats_by_trip); - free(stats->stats_by_type); - free(stats->stats_by_depth); - free(stats->stats_by_temp); -} - -void init_stats_summary(struct stats_summary *stats) -{ - stats->stats_yearly = NULL; - stats->stats_monthly = NULL; - stats->stats_by_trip = NULL; - stats->stats_by_type = NULL; - stats->stats_by_depth = NULL; - stats->stats_by_temp = NULL; -} - -/* make sure we skip the selected summary entries */ -void calculate_stats_selected(stats_t *stats_selection) -{ - struct dive *dive; - unsigned int i, nr; - - memset(stats_selection, 0, sizeof(*stats_selection)); - - nr = 0; - for_each_dive(i, dive) { - if (dive->selected && !dive->invalid) { - process_dive(dive, stats_selection); - nr++; - } - } - stats_selection->selection_size = nr; -} - -#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used - -bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx) -{ - bool first_gas_explicit = false; - const struct event *event = get_next_event(dc->events, "gaschange"); - while (event) { - if (dc->sample && (event->time.seconds == 0 || - (dc->samples && dc->sample[0].time.seconds == event->time.seconds))) - first_gas_explicit = true; - if (get_cylinder_index(dive, event) == idx) - return true; - event = get_next_event(event->next, "gaschange"); - } - return !first_gas_explicit && idx == 0; -} - -bool is_cylinder_used(const struct dive *dive, int idx) -{ - const struct divecomputer *dc; - cylinder_t *cyl; - if (idx < 0 || idx >= dive->cylinders.nr) - return false; - - cyl = get_cylinder(dive, idx); - if ((cyl->start.mbar - cyl->end.mbar) > SOME_GAS) - return true; - - if ((cyl->sample_start.mbar - cyl->sample_end.mbar) > SOME_GAS) - return true; - - for_each_dc(dive, dc) { - if (has_gaschange_event(dive, dc, idx)) - return true; - else if (dc->divemode == CCR && idx == get_cylinder_idx_by_use(dive, OXYGEN)) - return true; - } - return false; -} - -bool is_cylinder_prot(const struct dive *dive, int idx) -{ - const struct divecomputer *dc; - if (idx < 0 || idx >= dive->cylinders.nr) - return false; - - for_each_dc(dive, dc) { - if (has_gaschange_event(dive, dc, idx)) - return true; - } - return false; -} - -/* Returns a dynamically allocated array with dive->cylinders.nr entries, - * which has to be freed by the caller */ -volume_t *get_gas_used(struct dive *dive) -{ - int idx; - - volume_t *gases = malloc(dive->cylinders.nr * sizeof(volume_t)); - for (idx = 0; idx < dive->cylinders.nr; idx++) { - cylinder_t *cyl = get_cylinder(dive, idx); - pressure_t start, end; - - start = cyl->start.mbar ? cyl->start : cyl->sample_start; - end = cyl->end.mbar ? cyl->end : cyl->sample_end; - if (end.mbar && start.mbar > end.mbar) - gases[idx].mliter = gas_volume(cyl, start) - gas_volume(cyl, end); - else - gases[idx].mliter = 0; - } - - return gases; -} - -/* Quite crude reverse-blender-function, but it produces a approx result */ -static void get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup, volume_t *o2, volume_t *he) -{ - volume_t air = {}; - - if (gasmix_is_air(mix)) { - o2->mliter = 0; - he->mliter = 0; - return; - } - - air.mliter = lrint(((double)vol.mliter * get_n2(mix)) / (1000 - o2_in_topup)); - he->mliter = lrint(((double)vol.mliter * get_he(mix)) / 1000.0); - o2->mliter += vol.mliter - he->mliter - air.mliter; -} - -void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot) -{ - int i, j; - struct dive *d; - for_each_dive (i, d) { - if (!d->selected || d->invalid) - continue; - volume_t *diveGases = get_gas_used(d); - for (j = 0; j < d->cylinders.nr; j++) { - if (diveGases[j].mliter) { - volume_t o2 = {}, he = {}; - get_gas_parts(get_cylinder(d, j)->gasmix, diveGases[j], O2_IN_AIR, &o2, &he); - o2_tot->mliter += o2.mliter; - he_tot->mliter += he.mliter; - } - } - free(diveGases); - } -} diff --git a/core/statistics.cpp b/core/statistics.cpp new file mode 100644 index 000000000..121d86b77 --- /dev/null +++ b/core/statistics.cpp @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0 +/* statistics.cpp + * + * core logic for the Info & Stats page + */ + +#include "statistics.h" +#include "dive.h" +#include "divelog.h" +#include "event.h" +#include "gettext.h" +#include "sample.h" +#include "subsurface-time.h" +#include "trip.h" +#include "units.h" + +#include +#include +#include + +static void process_temperatures(struct dive *dp, stats_t &stats) +{ + temperature_t min_temp, mean_temp, max_temp = {.mkelvin = 0}; + + max_temp.mkelvin = dp->maxtemp.mkelvin; + if (max_temp.mkelvin && (!stats.max_temp.mkelvin || max_temp.mkelvin > stats.max_temp.mkelvin)) + stats.max_temp.mkelvin = max_temp.mkelvin; + + min_temp.mkelvin = dp->mintemp.mkelvin; + if (min_temp.mkelvin && (!stats.min_temp.mkelvin || min_temp.mkelvin < stats.min_temp.mkelvin)) + stats.min_temp.mkelvin = min_temp.mkelvin; + + if (min_temp.mkelvin || max_temp.mkelvin) { + mean_temp.mkelvin = min_temp.mkelvin; + if (mean_temp.mkelvin) + mean_temp.mkelvin = (mean_temp.mkelvin + max_temp.mkelvin) / 2; + else + mean_temp.mkelvin = max_temp.mkelvin; + stats.combined_temp.mkelvin += mean_temp.mkelvin; + stats.combined_count++; + } +} + +static void process_dive(struct dive *dive, stats_t &stats) +{ + int old_tadt, sac_time = 0; + int32_t duration = dive->duration.seconds; + + old_tadt = stats.total_average_depth_time.seconds; + stats.total_time.seconds += duration; + if (duration > stats.longest_time.seconds) + stats.longest_time.seconds = duration; + if (stats.shortest_time.seconds == 0 || duration < stats.shortest_time.seconds) + stats.shortest_time.seconds = duration; + if (dive->maxdepth.mm > stats.max_depth.mm) + stats.max_depth.mm = dive->maxdepth.mm; + if (stats.min_depth.mm == 0 || dive->maxdepth.mm < stats.min_depth.mm) + stats.min_depth.mm = dive->maxdepth.mm; + stats.combined_max_depth.mm += dive->maxdepth.mm; + + process_temperatures(dive, stats); + + /* Maybe we should drop zero-duration dives */ + if (!duration) + return; + if (dive->meandepth.mm) { + stats.total_average_depth_time.seconds += duration; + stats.avg_depth.mm = lrint((1.0 * old_tadt * stats.avg_depth.mm + + duration * dive->meandepth.mm) / + stats.total_average_depth_time.seconds); + } + if (dive->sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */ + sac_time = stats.total_sac_time.seconds + duration; + stats.avg_sac.mliter = lrint((1.0 * stats.total_sac_time.seconds * stats.avg_sac.mliter + + duration * dive->sac) / + sac_time); + if (dive->sac > stats.max_sac.mliter) + stats.max_sac.mliter = dive->sac; + if (stats.min_sac.mliter == 0 || dive->sac < stats.min_sac.mliter) + stats.min_sac.mliter = dive->sac; + stats.total_sac_time.seconds = sac_time; + } +} + +/* + * Calculate a summary of the statistics and put in the stats_summary + * structure provided in the first parameter. + * Before first use, it should be initialized with init_stats_summary(). + * After use, memory must be released with free_stats_summary(). + */ +stats_summary calculate_stats_summary(bool selected_only) +{ + int idx; + struct dive *dp; + struct tm tm; + int current_year = -1; + int current_month = 0; + int prev_month = 0, prev_year = 0; + dive_trip_t *trip_ptr = nullptr; + //stats_t stats = { 0 }; + + //if (divelog.dives->nr > 0) { + // stats.shortest_time.seconds = divelog.dives->dives[0]->duration.seconds; + // stats.min_depth.mm = divelog.dives->dives[0]->maxdepth.mm; + // stats.selection_size = divelog.dives->nr; + //} + + stats_summary out; + + /* stats_by_trip[0] is all the dives combined */ + out.stats_by_trip.emplace_back(); + + /* Setting the is_trip to true to show the location as first + * field in the statistics window */ + out.stats_by_type.resize(NUM_DIVEMODE + 1); + out.stats_by_type[0].location = translate("gettextFromC", "All (by type stats)"); + out.stats_by_type[0].is_trip = true; + out.stats_by_type[1].location = translate("gettextFromC", divemode_text_ui[OC]); + out.stats_by_type[1].is_trip = true; + out.stats_by_type[2].location = translate("gettextFromC", divemode_text_ui[CCR]); + out.stats_by_type[2].is_trip = true; + out.stats_by_type[3].location = translate("gettextFromC", divemode_text_ui[PSCR]); + out.stats_by_type[3].is_trip = true; + out.stats_by_type[4].location = translate("gettextFromC", divemode_text_ui[FREEDIVE]); + out.stats_by_type[4].is_trip = true; + + out.stats_by_depth.resize((STATS_MAX_DEPTH / STATS_DEPTH_BUCKET) + 1); + out.stats_by_depth[0].location = translate("gettextFromC", "All (by max depth stats)"); + out.stats_by_depth[0].is_trip = true; + + out.stats_by_temp.resize((STATS_MAX_TEMP / STATS_TEMP_BUCKET) + 1); + out.stats_by_temp[0].location = translate("gettextFromC", "All (by min. temp stats)"); + out.stats_by_temp[0].is_trip = true; + + /* this relies on the fact that the dives in the dive_table + * are in chronological order */ + for_each_dive (idx, dp) { + if (selected_only && !dp->selected) + continue; + if (dp->invalid) + continue; + //process_dive(dp, &stats); + + /* yearly statistics */ + utc_mkdate(dp->when, &tm); + + if (current_year != tm.tm_year || out.stats_yearly.empty()) { + current_year = tm.tm_year; + out.stats_yearly.emplace_back(); + out.stats_yearly.back().is_year = true; + } + process_dive(dp, out.stats_yearly.back()); + + out.stats_yearly.back().selection_size++; + out.stats_yearly.back().period = current_year; + + /* stats_by_type[0] is all the dives combined */ + out.stats_by_type[0].selection_size++; + process_dive(dp, out.stats_by_type[0]); + + process_dive(dp, out.stats_by_type[dp->dc.divemode + 1]); + out.stats_by_type[dp->dc.divemode + 1].selection_size++; + + /* stats_by_depth[0] is all the dives combined */ + out.stats_by_depth[0].selection_size++; + process_dive(dp, out.stats_by_depth[0]); + + int d_idx = dp->maxdepth.mm / (STATS_DEPTH_BUCKET * 1000); + d_idx = std::clamp(d_idx, 0, STATS_MAX_DEPTH / STATS_DEPTH_BUCKET); + process_dive(dp, out.stats_by_depth[d_idx + 1]); + out.stats_by_depth[d_idx + 1].selection_size++; + + /* stats_by_temp[0] is all the dives combined */ + out.stats_by_temp[0].selection_size++; + process_dive(dp, out.stats_by_temp[0]); + + int t_idx = ((int)mkelvin_to_C(dp->mintemp.mkelvin)) / STATS_TEMP_BUCKET; + t_idx = std::clamp(t_idx, 0, STATS_MAX_TEMP / STATS_TEMP_BUCKET); + process_dive(dp, out.stats_by_temp[t_idx + 1]); + out.stats_by_temp[t_idx + 1].selection_size++; + + if (dp->divetrip != NULL) { + if (trip_ptr != dp->divetrip) { + trip_ptr = dp->divetrip; + out.stats_by_trip.emplace_back(); + } + + /* stats_by_trip[0] is all the dives combined */ + /* TODO: yet, this doesn't seem to consider dives outside of trips !? */ + out.stats_by_trip[0].selection_size++; + process_dive(dp, out.stats_by_trip[0]); + out.stats_by_trip[0].is_trip = true; + out.stats_by_trip[0].location = translate("gettextFromC", "All (by trip stats)"); + + process_dive(dp, out.stats_by_trip.back()); + out.stats_by_trip.back().selection_size++; + out.stats_by_trip.back().is_trip = true; + out.stats_by_trip.back().location = dp->divetrip->location; + } + + /* monthly statistics */ + if (current_month == 0 || out.stats_monthly.empty()) { + current_month = tm.tm_mon + 1; + out.stats_monthly.emplace_back(); + } else { + if (current_month != tm.tm_mon + 1) + current_month = tm.tm_mon + 1; + if (prev_month != current_month || prev_year != current_year) + out.stats_monthly.emplace_back(); + } + process_dive(dp, out.stats_monthly.back()); + out.stats_monthly.back().selection_size++; + out.stats_monthly.back().period = current_month; + prev_month = current_month; + prev_year = current_year; + } + + /* add labels for depth ranges up to maximum depth seen */ + if (out.stats_by_depth[0].selection_size) { + int d_idx = out.stats_by_depth[0].max_depth.mm; + if (d_idx > STATS_MAX_DEPTH * 1000) + d_idx = STATS_MAX_DEPTH * 1000; + for (int r = 0; r * (STATS_DEPTH_BUCKET * 1000) < d_idx; ++r) + out.stats_by_depth[r+1].is_trip = true; + } + + /* add labels for depth ranges up to maximum temperature seen */ + if (out.stats_by_temp[0].selection_size) { + int t_idx = (int)mkelvin_to_C(out.stats_by_temp[0].max_temp.mkelvin); + if (t_idx > STATS_MAX_TEMP) + t_idx = STATS_MAX_TEMP; + for (int r = 0; r * STATS_TEMP_BUCKET < t_idx; ++r) + out.stats_by_temp[r+1].is_trip = true; + } + + return out; +} + +stats_summary::stats_summary() +{ +} + +stats_summary::~stats_summary() +{ +} + +/* make sure we skip the selected summary entries */ +stats_t calculate_stats_selected() +{ + stats_t stats_selection; + struct dive *dive; + unsigned int i, nr; + + nr = 0; + for_each_dive(i, dive) { + if (dive->selected && !dive->invalid) { + process_dive(dive, stats_selection); + nr++; + } + } + stats_selection.selection_size = nr; + return stats_selection; +} + +#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used + +bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx) +{ + bool first_gas_explicit = false; + const struct event *event = get_next_event(dc->events, "gaschange"); + while (event) { + if (dc->sample && (event->time.seconds == 0 || + (dc->samples && dc->sample[0].time.seconds == event->time.seconds))) + first_gas_explicit = true; + if (get_cylinder_index(dive, event) == idx) + return true; + event = get_next_event(event->next, "gaschange"); + } + return !first_gas_explicit && idx == 0; +} + +bool is_cylinder_used(const struct dive *dive, int idx) +{ + const struct divecomputer *dc; + cylinder_t *cyl; + if (idx < 0 || idx >= dive->cylinders.nr) + return false; + + cyl = get_cylinder(dive, idx); + if ((cyl->start.mbar - cyl->end.mbar) > SOME_GAS) + return true; + + if ((cyl->sample_start.mbar - cyl->sample_end.mbar) > SOME_GAS) + return true; + + for_each_dc(dive, dc) { + if (has_gaschange_event(dive, dc, idx)) + return true; + else if (dc->divemode == CCR && idx == get_cylinder_idx_by_use(dive, OXYGEN)) + return true; + } + return false; +} + +bool is_cylinder_prot(const struct dive *dive, int idx) +{ + const struct divecomputer *dc; + if (idx < 0 || idx >= dive->cylinders.nr) + return false; + + for_each_dc(dive, dc) { + if (has_gaschange_event(dive, dc, idx)) + return true; + } + return false; +} + +/* Returns a vector with dive->cylinders.nr entries */ +std::vector get_gas_used(struct dive *dive) +{ + std::vector gases(dive->cylinders.nr, volume_t { 0 }); + for (int idx = 0; idx < dive->cylinders.nr; idx++) { + cylinder_t *cyl = get_cylinder(dive, idx); + pressure_t start, end; + + start = cyl->start.mbar ? cyl->start : cyl->sample_start; + end = cyl->end.mbar ? cyl->end : cyl->sample_end; + if (end.mbar && start.mbar > end.mbar) + gases[idx].mliter = gas_volume(cyl, start) - gas_volume(cyl, end); + else + gases[idx].mliter = 0; + } + + return gases; +} + +/* Quite crude reverse-blender-function, but it produces an approx result. + * Returns an (O2, He) pair. */ +static std::pair get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup) +{ + if (gasmix_is_air(mix)) + return { {0}, {0} }; + + volume_t air = { (int)lrint(((double)vol.mliter * get_n2(mix)) / (1000 - o2_in_topup)) }; + volume_t he = { (int)lrint(((double)vol.mliter * get_he(mix)) / 1000.0) }; + volume_t o2 = { vol.mliter - he.mliter - air.mliter }; + return std::make_pair(o2, he); +} + +std::pair selected_dives_gas_parts() +{ + int i; + struct dive *d; + volume_t o2_tot = { 0 }, he_tot = { 0 }; + for_each_dive (i, d) { + if (!d->selected || d->invalid) + continue; + int j = 0; + for (auto &gas: get_gas_used(d)) { + if (gas.mliter) { + auto [o2, he] = get_gas_parts(get_cylinder(d, j)->gasmix, gas, O2_IN_AIR); + o2_tot.mliter += o2.mliter; + he_tot.mliter += he.mliter; + } + j++; + } + } + return std::make_pair(o2_tot, he_tot); +} diff --git a/core/statistics.h b/core/statistics.h index 4b2b0b1bb..afe7f6da7 100644 --- a/core/statistics.h +++ b/core/statistics.h @@ -13,81 +13,59 @@ #define STATS_MAX_DEPTH 300 /* Max depth for stats bucket is 300m */ #define STATS_DEPTH_BUCKET 10 /* Size of buckets for depth range */ -#define STATS_MAX_TEMP 40 /* Max temp for stats bucket is 40C */ -#define STATS_TEMP_BUCKET 5 /* Size of buckets for temp range */ +#define STATS_MAX_TEMP 40 /* Max temp for stats bucket is 40C */ +#define STATS_TEMP_BUCKET 5 /* Size of buckets for temp range */ struct dive; -typedef struct +#ifdef __cplusplus + +#include +#include + +struct stats_t { - int period; - duration_t total_time; + int period = 0; + duration_t total_time = { 0 }; /* total time of dives with non-zero average depth */ - duration_t total_average_depth_time; + duration_t total_average_depth_time = { 0 }; /* avg_time is simply total_time / nr -- let's not keep this */ - duration_t shortest_time; - duration_t longest_time; - depth_t max_depth; - depth_t min_depth; - depth_t avg_depth; - depth_t combined_max_depth; - volume_t max_sac; - volume_t min_sac; - volume_t avg_sac; - temperature_t max_temp; - temperature_t min_temp; - temperature_sum_t combined_temp; - unsigned int combined_count; - unsigned int selection_size; - duration_t total_sac_time; - bool is_year; - bool is_trip; - char *location; -} stats_t; + duration_t shortest_time = { 0 }; + duration_t longest_time = { 0 }; + depth_t max_depth = { 0 }; + depth_t min_depth = { 0 }; + depth_t avg_depth = { 0 }; + depth_t combined_max_depth = { 0 }; + volume_t max_sac = { 0 }; + volume_t min_sac = { 0 }; + volume_t avg_sac = { 0 }; + temperature_t max_temp = { 0 }; + temperature_t min_temp = { 0 }; + temperature_sum_t combined_temp = { 0 }; + unsigned int combined_count = 0; + unsigned int selection_size = 0; + duration_t total_sac_time = { 0 }; + bool is_year = false; + bool is_trip = false; + std::string location; +}; struct stats_summary { - stats_t *stats_yearly; - stats_t *stats_monthly; - stats_t *stats_by_trip; - stats_t *stats_by_type; - stats_t *stats_by_depth; - stats_t *stats_by_temp; + stats_summary(); + ~stats_summary(); + std::vector stats_yearly; + std::vector stats_monthly; + std::vector stats_by_trip; + std::vector stats_by_type; + std::vector stats_by_depth; + std::vector stats_by_temp; }; -#ifdef __cplusplus -extern "C" { -#endif +extern stats_summary calculate_stats_summary(bool selected_only); +extern stats_t calculate_stats_selected(); +extern std::vector get_gas_used(struct dive *dive); +extern std::pair selected_dives_gas_parts(); // returns (O2, He) tuple -extern void init_stats_summary(struct stats_summary *stats); -extern void free_stats_summary(struct stats_summary *stats); -extern void calculate_stats_summary(struct stats_summary *stats, bool selected_only); -extern void calculate_stats_selected(stats_t *stats_selection); -extern volume_t *get_gas_used(struct dive *dive); -extern void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot); - -#ifdef __cplusplus -} -#endif - -/* - * For C++ code, provide a convenience version of stats_summary - * that initializes the structure on construction and frees - * resources when it goes out of scope. Apart from that, it - * can be used as a stats_summary replacement. - */ -#ifdef __cplusplus -struct stats_summary_auto_free : public stats_summary { - stats_summary_auto_free(); - ~stats_summary_auto_free(); -}; -inline stats_summary_auto_free::stats_summary_auto_free() -{ - init_stats_summary(this); -} -inline stats_summary_auto_free::~stats_summary_auto_free() -{ - free_stats_summary(this); -} #endif #endif // STATISTICS_H diff --git a/desktop-widgets/tab-widgets/TabDiveInformation.cpp b/desktop-widgets/tab-widgets/TabDiveInformation.cpp index a0a84b66f..386e88b04 100644 --- a/desktop-widgets/tab-widgets/TabDiveInformation.cpp +++ b/desktop-widgets/tab-widgets/TabDiveInformation.cpp @@ -124,7 +124,7 @@ void TabDiveInformation::updateProfile() ui->maximumDepthText->setText(get_depth_string(currentDive->maxdepth, true)); ui->averageDepthText->setText(get_depth_string(currentDive->meandepth, true)); - volume_t *gases = get_gas_used(currentDive); + std::vector gases = get_gas_used(currentDive); QString volumes; std::vector mean(currentDive->cylinders.nr), duration(currentDive->cylinders.nr); struct divecomputer *currentdc = parent.getCurrentDC(); @@ -148,7 +148,6 @@ void TabDiveInformation::updateProfile() SACs.append(get_volume_string(sac, true).append(tr("/min"))); } } - free(gases); ui->gasUsedText->setText(volumes); ui->oxygenHeliumText->setText(gaslist); diff --git a/desktop-widgets/tab-widgets/TabDiveStatistics.cpp b/desktop-widgets/tab-widgets/TabDiveStatistics.cpp index c52701328..76b113b11 100644 --- a/desktop-widgets/tab-widgets/TabDiveStatistics.cpp +++ b/desktop-widgets/tab-widgets/TabDiveStatistics.cpp @@ -72,8 +72,7 @@ void TabDiveStatistics::cylinderChanged(dive *d) void TabDiveStatistics::updateData(const std::vector &, dive *currentDive, int) { - stats_t stats_selection; - calculate_stats_selected(&stats_selection); + stats_t stats_selection = calculate_stats_selected(); clear(); if (amount_selected > 1 && stats_selection.selection_size >= 1) { ui->depthLimits->setMaximum(get_depth_string(stats_selection.max_depth, true)); @@ -135,13 +134,12 @@ void TabDiveStatistics::updateData(const std::vector &, dive *currentDiv 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); + auto [o2_tot, he_tot] = selected_dives_gas_parts(); /* No need to show the gas mixing information if diving - * with pure air, and only display the he / O2 part when - * it is used. - */ + * 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")); if (he_tot.mliter) { diff --git a/desktop-widgets/templatelayout.cpp b/desktop-widgets/templatelayout.cpp index 5bae08e3b..ecfde679b 100644 --- a/desktop-widgets/templatelayout.cpp +++ b/desktop-widgets/templatelayout.cpp @@ -123,10 +123,9 @@ QString TemplateLayout::generateStatistics() State state; int i = 0; - stats_summary_auto_free stats; - calculate_stats_summary(&stats, false); - while (stats.stats_yearly != NULL && stats.stats_yearly[i].period) { - state.years.append(&stats.stats_yearly[i]); + stats_summary stats = calculate_stats_summary(false); + for (auto &s: stats.stats_yearly) { + state.years.append(&s); i++; } diff --git a/qt-models/yearlystatisticsmodel.cpp b/qt-models/yearlystatisticsmodel.cpp index b6613d253..7fd5cbb9e 100644 --- a/qt-models/yearlystatisticsmodel.cpp +++ b/qt-models/yearlystatisticsmodel.cpp @@ -42,77 +42,63 @@ YearStatisticsItem::YearStatisticsItem(const stats_t &interval) : stats_interval QVariant YearStatisticsItem::data(int column, int role) const { - QVariant ret; - if (role == Qt::FontRole) { QFont font = defaultModelFont(); font.setBold(stats_interval.is_year); return font; } else if (role != Qt::DisplayRole) { - return ret; + return QVariant(); } switch (column) { case YEAR: if (stats_interval.is_trip) { - ret = QString(stats_interval.location); + return QString::fromStdString(stats_interval.location); } else { - ret = stats_interval.period; + return stats_interval.period; } - break; case DIVES: - ret = stats_interval.selection_size; - break; + return stats_interval.selection_size; case TOTAL_TIME: - ret = get_dive_duration_string(stats_interval.total_time.seconds, tr("h"), tr("min"), tr("sec"), " "); - break; + return get_dive_duration_string(stats_interval.total_time.seconds, tr("h"), tr("min"), tr("sec"), " "); case AVERAGE_TIME: - ret = formatMinutes(stats_interval.total_time.seconds / stats_interval.selection_size); - break; + return formatMinutes(stats_interval.total_time.seconds / stats_interval.selection_size); case SHORTEST_TIME: - ret = formatMinutes(stats_interval.shortest_time.seconds); - break; + return formatMinutes(stats_interval.shortest_time.seconds); case LONGEST_TIME: - ret = formatMinutes(stats_interval.longest_time.seconds); - break; + return formatMinutes(stats_interval.longest_time.seconds); case AVG_DEPTH: - ret = get_depth_string(stats_interval.avg_depth); - break; + return get_depth_string(stats_interval.avg_depth); case AVG_MAX_DEPTH: if (stats_interval.selection_size) - ret = get_depth_string(stats_interval.combined_max_depth.mm / stats_interval.selection_size); + return get_depth_string(stats_interval.combined_max_depth.mm / stats_interval.selection_size); break; case MIN_DEPTH: - ret = get_depth_string(stats_interval.min_depth); - break; + return get_depth_string(stats_interval.min_depth); case MAX_DEPTH: - ret = get_depth_string(stats_interval.max_depth); - break; + return get_depth_string(stats_interval.max_depth); case AVG_SAC: - ret = get_volume_string(stats_interval.avg_sac); - break; + return get_volume_string(stats_interval.avg_sac); case MIN_SAC: - ret = get_volume_string(stats_interval.min_sac); - break; + return get_volume_string(stats_interval.min_sac); case MAX_SAC: - ret = get_volume_string(stats_interval.max_sac); - break; + return get_volume_string(stats_interval.max_sac); case AVG_TEMP: if (stats_interval.combined_temp.mkelvin && stats_interval.combined_count) { temperature_t avg_temp; avg_temp.mkelvin = stats_interval.combined_temp.mkelvin / stats_interval.combined_count; - ret = get_temperature_string(avg_temp); + return get_temperature_string(avg_temp); } break; case MIN_TEMP: if (stats_interval.min_temp.mkelvin) - ret = get_temperature_string(stats_interval.min_temp); + return get_temperature_string(stats_interval.min_temp); break; case MAX_TEMP: if (stats_interval.max_temp.mkelvin) - ret = get_temperature_string(stats_interval.max_temp); + return get_temperature_string(stats_interval.max_temp); break; } - return ret; + return QVariant(); } YearlyStatisticsModel::YearlyStatisticsModel(QObject *parent) : TreeModel(parent) @@ -184,17 +170,15 @@ QVariant YearlyStatisticsModel::headerData(int section, Qt::Orientation orientat void YearlyStatisticsModel::update_yearly_stats() { - int i, month = 0; - unsigned int j, combined_months; - stats_summary_auto_free stats; QString label; temperature_t t_range_min,t_range_max; - calculate_stats_summary(&stats, false); + stats_summary stats = calculate_stats_summary(false); - for (i = 0; stats.stats_yearly != NULL && stats.stats_yearly[i].period; ++i) { - YearStatisticsItem *item = new YearStatisticsItem(stats.stats_yearly[i]); - combined_months = 0; - for (j = 0; combined_months < stats.stats_yearly[i].selection_size; ++j) { + int month = 0; + for (const auto &s: stats.stats_yearly) { + YearStatisticsItem *item = new YearStatisticsItem(s); + size_t combined_months = 0; + while (combined_months < s.selection_size) { combined_months += stats.stats_monthly[month].selection_size; YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_monthly[month]); item->children.append(iChild); @@ -205,10 +189,10 @@ void YearlyStatisticsModel::update_yearly_stats() item->parent = rootItem.get(); } - if (stats.stats_by_trip != NULL && stats.stats_by_trip[0].is_trip == true) { + if (stats.stats_by_trip[0].is_trip == true) { YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_trip[0]); - for (i = 1; stats.stats_by_trip != NULL && stats.stats_by_trip[i].is_trip; ++i) { - YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_trip[i]); + for (auto it = std::next(stats.stats_by_trip.begin()); it != stats.stats_by_trip.end(); ++it) { + YearStatisticsItem *iChild = new YearStatisticsItem(*it); item->children.append(iChild); iChild->parent = item; } @@ -217,12 +201,12 @@ void YearlyStatisticsModel::update_yearly_stats() } /* Show the statistic sorted by dive type */ - if (stats.stats_by_type != NULL && stats.stats_by_type[0].selection_size) { + if (stats.stats_by_type[0].selection_size) { YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_type[0]); - for (i = 1; i <= NUM_DIVEMODE; ++i) { - if (stats.stats_by_type[i].selection_size == 0) + for (auto it = std::next(stats.stats_by_type.begin()); it != stats.stats_by_type.end(); ++it) { + if (it->selection_size == 0) continue; - YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_type[i]); + YearStatisticsItem *iChild = new YearStatisticsItem(*it); item->children.append(iChild); iChild->parent = item; } @@ -231,35 +215,41 @@ void YearlyStatisticsModel::update_yearly_stats() } /* Show the statistic sorted by dive depth */ - if (stats.stats_by_depth != NULL && stats.stats_by_depth[0].selection_size) { + if (stats.stats_by_depth[0].selection_size) { YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_depth[0]); - for (i = 1; stats.stats_by_depth[i].is_trip; ++i) - if (stats.stats_by_depth[i].selection_size) { - label = QString(tr("%1 - %2")).arg(get_depth_string((i - 1) * (STATS_DEPTH_BUCKET * 1000), true, false), - get_depth_string(i * (STATS_DEPTH_BUCKET * 1000), true, false)); - stats.stats_by_depth[i].location = strdup(label.toUtf8().data()); - YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_depth[i]); + int i = 0; + for (auto it = std::next(stats.stats_by_depth.begin()); it != stats.stats_by_depth.end(); ++it) { + if (it->selection_size) { + QString label = QString(tr("%1 - %2")).arg(get_depth_string(i * (STATS_DEPTH_BUCKET * 1000), true, false), + get_depth_string((i + 1) * (STATS_DEPTH_BUCKET * 1000), true, false)); + it->location = label.toStdString(); + YearStatisticsItem *iChild = new YearStatisticsItem(*it); item->children.append(iChild); iChild->parent = item; } + i++; + } rootItem->children.append(item); item->parent = rootItem.get(); } /* Show the statistic sorted by dive temperature */ - if (stats.stats_by_temp != NULL && stats.stats_by_temp[0].selection_size) { + if (stats.stats_by_temp[0].selection_size) { YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_temp[0]); - for (i = 1; stats.stats_by_temp[i].is_trip; ++i) - if (stats.stats_by_temp[i].selection_size) { - t_range_min.mkelvin = C_to_mkelvin((i - 1) * STATS_TEMP_BUCKET); - t_range_max.mkelvin = C_to_mkelvin(i * STATS_TEMP_BUCKET); + int i = 0; + for (auto it = std::next(stats.stats_by_temp.begin()); it != stats.stats_by_temp.end(); ++it) { + if (it->selection_size) { + t_range_min.mkelvin = C_to_mkelvin(i * STATS_TEMP_BUCKET); + t_range_max.mkelvin = C_to_mkelvin((i + 1) * STATS_TEMP_BUCKET); label = QString(tr("%1 - %2")).arg(get_temperature_string(t_range_min, true), get_temperature_string(t_range_max, true)); - stats.stats_by_temp[i].location = strdup(label.toUtf8().data()); - YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_temp[i]); + it->location = label.toStdString(); + YearStatisticsItem *iChild = new YearStatisticsItem(*it); item->children.append(iChild); iChild->parent = item; } + i++; + } rootItem->children.append(item); item->parent = rootItem.get(); }