aboutsummaryrefslogtreecommitdiffstats
path: root/core/divelist.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/divelist.c')
-rw-r--r--core/divelist.c1207
1 files changed, 1207 insertions, 0 deletions
diff --git a/core/divelist.c b/core/divelist.c
new file mode 100644
index 000000000..543d9e17b
--- /dev/null
+++ b/core/divelist.c
@@ -0,0 +1,1207 @@
+/* divelist.c */
+/* core logic for the dive list -
+ * accessed through the following interfaces:
+ *
+ * dive_trip_t *dive_trip_list;
+ * unsigned int amount_selected;
+ * void dump_selection(void)
+ * dive_trip_t *find_trip_by_idx(int idx)
+ * int trip_has_selected_dives(dive_trip_t *trip)
+ * void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
+ * int total_weight(struct dive *dive)
+ * int get_divenr(struct dive *dive)
+ * double init_decompression(struct dive *dive)
+ * void update_cylinder_related_info(struct dive *dive)
+ * void dump_trip_list(void)
+ * dive_trip_t *find_matching_trip(timestamp_t when)
+ * void insert_trip(dive_trip_t **dive_trip_p)
+ * void remove_dive_from_trip(struct dive *dive)
+ * void add_dive_to_trip(struct dive *dive, dive_trip_t *trip)
+ * dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive)
+ * void autogroup_dives(void)
+ * void delete_single_dive(int idx)
+ * void add_single_dive(int idx, struct dive *dive)
+ * void merge_two_dives(struct dive *a, struct dive *b)
+ * void select_dive(int idx)
+ * void deselect_dive(int idx)
+ * void mark_divelist_changed(int changed)
+ * int unsaved_changes()
+ * void remove_autogen_trips()
+ */
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include "gettext.h"
+#include <assert.h>
+#include <zip.h>
+#include <libxslt/transform.h>
+
+#include "dive.h"
+#include "divelist.h"
+#include "display.h"
+#include "planner.h"
+#include "qthelperfromc.h"
+#include "git-access.h"
+
+static short dive_list_changed = false;
+
+short autogroup = false;
+
+dive_trip_t *dive_trip_list;
+
+unsigned int amount_selected;
+
+#if DEBUG_SELECTION_TRACKING
+void dump_selection(void)
+{
+ int i;
+ struct dive *dive;
+
+ printf("currently selected are %u dives:", amount_selected);
+ for_each_dive(i, dive) {
+ if (dive->selected)
+ printf(" %d", i);
+ }
+ printf("\n");
+}
+#endif
+
+void set_autogroup(bool value)
+{
+ /* if we keep the UI paradigm, this needs to toggle
+ * the checkbox on the autogroup menu item */
+ autogroup = value;
+}
+
+dive_trip_t *find_trip_by_idx(int idx)
+{
+ dive_trip_t *trip = dive_trip_list;
+
+ if (idx >= 0)
+ return NULL;
+ idx = -idx;
+ while (trip) {
+ if (trip->index == idx)
+ return trip;
+ trip = trip->next;
+ }
+ return NULL;
+}
+
+int trip_has_selected_dives(dive_trip_t *trip)
+{
+ struct dive *dive;
+ for (dive = trip->dives; dive; dive = dive->next) {
+ if (dive->selected)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Get "maximal" dive gas for a dive.
+ * Rules:
+ * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
+ * - Nitrox trumps air (even if hypoxic)
+ * These are the same rules as the inter-dive sorting rules.
+ */
+void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2max_p)
+{
+ int i;
+ int maxo2 = -1, maxhe = -1, mino2 = 1000;
+
+
+ for (i = 0; i < MAX_CYLINDERS; i++) {
+ cylinder_t *cyl = dive->cylinder + i;
+ int o2 = get_o2(&cyl->gasmix);
+ int he = get_he(&cyl->gasmix);
+
+ if (!is_cylinder_used(dive, i))
+ continue;
+ if (cylinder_none(cyl))
+ continue;
+ if (o2 > maxo2)
+ maxo2 = o2;
+ if (he > maxhe)
+ goto newmax;
+ if (he < maxhe)
+ continue;
+ if (o2 <= maxo2)
+ continue;
+ newmax:
+ maxhe = he;
+ mino2 = o2;
+ }
+ /* All air? Show/sort as "air"/zero */
+ if ((!maxhe && maxo2 == O2_IN_AIR && mino2 == maxo2) ||
+ (maxo2 == -1 && maxhe == -1 && mino2 == 1000))
+ maxo2 = mino2 = 0;
+ *o2_p = mino2;
+ *he_p = maxhe;
+ *o2max_p = maxo2;
+}
+
+int total_weight(struct dive *dive)
+{
+ int i, total_grams = 0;
+
+ if (dive)
+ for (i = 0; i < MAX_WEIGHTSYSTEMS; i++)
+ total_grams += dive->weightsystem[i].weight.grams;
+ return total_grams;
+}
+
+static int active_o2(struct dive *dive, struct divecomputer *dc, duration_t time)
+{
+ struct gasmix gas;
+ get_gas_at_time(dive, dc, time, &gas);
+ return get_o2(&gas);
+}
+
+/* calculate OTU for a dive - this only takes the first divecomputer into account */
+static int calculate_otu(struct dive *dive)
+{
+ int i;
+ double otu = 0.0;
+ struct divecomputer *dc = &dive->dc;
+
+ for (i = 1; i < dc->samples; i++) {
+ int t;
+ int po2;
+ struct sample *sample = dc->sample + i;
+ struct sample *psample = sample - 1;
+ t = sample->time.seconds - psample->time.seconds;
+ if (sample->setpoint.mbar) {
+ po2 = sample->setpoint.mbar;
+ } else {
+ int o2 = active_o2(dive, dc, sample->time);
+ po2 = o2 * depth_to_atm(sample->depth.mm, dive);
+ }
+ if (po2 >= 500)
+ otu += pow((po2 - 500) / 1000.0, 0.83) * t / 30.0;
+ }
+ return rint(otu);
+}
+/* calculate CNS for a dive - this only takes the first divecomputer into account */
+int const cns_table[][3] = {
+ /* po2, Maximum Single Exposure, Maximum 24 hour Exposure */
+ { 1600, 45 * 60, 150 * 60 },
+ { 1500, 120 * 60, 180 * 60 },
+ { 1400, 150 * 60, 180 * 60 },
+ { 1300, 180 * 60, 210 * 60 },
+ { 1200, 210 * 60, 240 * 60 },
+ { 1100, 240 * 60, 270 * 60 },
+ { 1000, 300 * 60, 300 * 60 },
+ { 900, 360 * 60, 360 * 60 },
+ { 800, 450 * 60, 450 * 60 },
+ { 700, 570 * 60, 570 * 60 },
+ { 600, 720 * 60, 720 * 60 }
+};
+
+/* this only gets called if dive->maxcns == 0 which means we know that
+ * none of the divecomputers has tracked any CNS for us
+ * so we calculated it "by hand" */
+static int calculate_cns(struct dive *dive)
+{
+ int i, divenr;
+ size_t j;
+ double cns = 0.0;
+ struct divecomputer *dc = &dive->dc;
+ struct dive *prev_dive;
+ timestamp_t endtime;
+
+ /* shortcut */
+ if (dive->cns)
+ return dive->cns;
+ /*
+ * Do we start with a cns loading from a previous dive?
+ * Check if we did a dive 12 hours prior, and what cns we had from that.
+ * Then apply ha 90min halftime to see whats left.
+ */
+ divenr = get_divenr(dive);
+ if (divenr) {
+ prev_dive = get_dive(divenr - 1);
+ if (prev_dive) {
+ endtime = prev_dive->when + prev_dive->duration.seconds;
+ if (dive->when < (endtime + 3600 * 12)) {
+ cns = calculate_cns(prev_dive);
+ cns = cns * 1 / pow(2, (dive->when - endtime) / (90.0 * 60.0));
+ }
+ }
+ }
+ /* Caclulate the cns for each sample in this dive and sum them */
+ for (i = 1; i < dc->samples; i++) {
+ int t;
+ int po2;
+ struct sample *sample = dc->sample + i;
+ struct sample *psample = sample - 1;
+ t = sample->time.seconds - psample->time.seconds;
+ if (sample->setpoint.mbar) {
+ po2 = sample->setpoint.mbar;
+ } else {
+ int o2 = active_o2(dive, dc, sample->time);
+ po2 = o2 * depth_to_atm(sample->depth.mm, dive);
+ }
+ /* CNS don't increse when below 500 matm */
+ if (po2 < 500)
+ continue;
+ /* Find what table-row we should calculate % for */
+ for (j = 1; j < sizeof(cns_table) / (sizeof(int) * 3); j++)
+ if (po2 > cns_table[j][0])
+ break;
+ j--;
+ cns += ((double)t) / ((double)cns_table[j][1]) * 100;
+ }
+ /* save calculated cns in dive struct */
+ dive->cns = cns;
+ return dive->cns;
+}
+/*
+ * Return air usage (in liters).
+ */
+static double calculate_airuse(struct dive *dive)
+{
+ int airuse = 0;
+ int i;
+
+ for (i = 0; i < MAX_CYLINDERS; i++) {
+ pressure_t start, end;
+ cylinder_t *cyl = dive->cylinder + i;
+
+ 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)
+ continue;
+
+ airuse += gas_volume(cyl, start) - gas_volume(cyl, end);
+ }
+ return airuse / 1000.0;
+}
+
+/* this only uses the first divecomputer to calculate the SAC rate */
+static int calculate_sac(struct dive *dive)
+{
+ struct divecomputer *dc = &dive->dc;
+ double airuse, pressure, sac;
+ int duration, meandepth;
+
+ airuse = calculate_airuse(dive);
+ if (!airuse)
+ return 0;
+
+ duration = dc->duration.seconds;
+ if (!duration)
+ return 0;
+
+ meandepth = dc->meandepth.mm;
+ if (!meandepth)
+ return 0;
+
+ /* Mean pressure in ATM (SAC calculations are in atm*l/min) */
+ pressure = depth_to_atm(meandepth, dive);
+ sac = airuse / pressure * 60 / duration;
+
+ /* milliliters per minute.. */
+ return sac * 1000;
+}
+
+/* for now we do this based on the first divecomputer */
+static void add_dive_to_deco(struct dive *dive)
+{
+ struct divecomputer *dc = &dive->dc;
+ int i;
+
+ if (!dc)
+ return;
+ for (i = 1; i < dc->samples; i++) {
+ struct sample *psample = dc->sample + i - 1;
+ struct sample *sample = dc->sample + i;
+ int t0 = psample->time.seconds;
+ int t1 = sample->time.seconds;
+ int j;
+
+ for (j = t0; j < t1; j++) {
+ int depth = interpolate(psample->depth.mm, sample->depth.mm, j - t0, t1 - t0);
+ add_segment(depth_to_bar(depth, dive),
+ &dive->cylinder[sample->sensor].gasmix, 1, sample->setpoint.mbar, dive, dive->sac);
+ }
+ }
+}
+
+int get_divenr(struct dive *dive)
+{
+ int i;
+ struct dive *d;
+ // tempting as it may be, don't die when called with dive=NULL
+ if (dive)
+ for_each_dive(i, d) {
+ if (d->id == dive->id) // don't compare pointers, we could be passing in a copy of the dive
+ return i;
+ }
+ return -1;
+}
+
+int get_divesite_idx(struct dive_site *ds)
+{
+ int i;
+ struct dive_site *d;
+ // tempting as it may be, don't die when called with dive=NULL
+ if (ds)
+ for_each_dive_site(i, d) {
+ if (d->uuid == ds->uuid) // don't compare pointers, we could be passing in a copy of the dive
+ return i;
+ }
+ return -1;
+}
+
+static struct gasmix air = { .o2.permille = O2_IN_AIR, .he.permille = 0 };
+
+/* take into account previous dives until there is a 48h gap between dives */
+double init_decompression(struct dive *dive)
+{
+ int i, divenr = -1;
+ unsigned int surface_time;
+ timestamp_t when, lasttime = 0, laststart = 0;
+ bool deco_init = false;
+ double surface_pressure;
+
+ if (!dive)
+ return 0.0;
+
+ surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0;
+ divenr = get_divenr(dive);
+ when = dive->when;
+ i = divenr;
+ if (i < 0) {
+ i = dive_table.nr - 1;
+ while (i >= 0 && get_dive(i)->when > when)
+ --i;
+ i++;
+ }
+ while (i--) {
+ struct dive *pdive = get_dive(i);
+ /* we don't want to mix dives from different trips as we keep looking
+ * for how far back we need to go */
+ if (dive->divetrip && pdive->divetrip != dive->divetrip)
+ continue;
+ if (!pdive || pdive->when >= when || pdive->when + pdive->duration.seconds + 48 * 60 * 60 < when)
+ break;
+ /* For simultaneous dives, only consider the first */
+ if (pdive->when == laststart)
+ continue;
+ when = pdive->when;
+ lasttime = when + pdive->duration.seconds;
+ }
+ while (++i < (divenr >= 0 ? divenr : dive_table.nr)) {
+ struct dive *pdive = get_dive(i);
+ /* again skip dives from different trips */
+ if (dive->divetrip && dive->divetrip != pdive->divetrip)
+ continue;
+ surface_pressure = get_surface_pressure_in_mbar(pdive, true) / 1000.0;
+ if (!deco_init) {
+ clear_deco(surface_pressure);
+ deco_init = true;
+#if DECO_CALC_DEBUG & 2
+ dump_tissues();
+#endif
+ }
+ add_dive_to_deco(pdive);
+ laststart = pdive->when;
+#if DECO_CALC_DEBUG & 2
+ printf("added dive #%d\n", pdive->number);
+ dump_tissues();
+#endif
+ if (pdive->when > lasttime) {
+ surface_time = pdive->when - lasttime;
+ lasttime = pdive->when + pdive->duration.seconds;
+ add_segment(surface_pressure, &air, surface_time, 0, dive, prefs.decosac);
+#if DECO_CALC_DEBUG & 2
+ printf("after surface intervall of %d:%02u\n", FRACTION(surface_time, 60));
+ dump_tissues();
+#endif
+ }
+ }
+ /* add the final surface time */
+ if (lasttime && dive->when > lasttime) {
+ surface_time = dive->when - lasttime;
+ surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0;
+ add_segment(surface_pressure, &air, surface_time, 0, dive, prefs.decosac);
+#if DECO_CALC_DEBUG & 2
+ printf("after surface intervall of %d:%02u\n", FRACTION(surface_time, 60));
+ dump_tissues();
+#endif
+ }
+ if (!deco_init) {
+ surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0;
+ clear_deco(surface_pressure);
+#if DECO_CALC_DEBUG & 2
+ printf("no previous dive\n");
+ dump_tissues();
+#endif
+ }
+ return tissue_tolerance_calc(dive, surface_pressure);
+}
+
+void update_cylinder_related_info(struct dive *dive)
+{
+ if (dive != NULL) {
+ dive->sac = calculate_sac(dive);
+ dive->otu = calculate_otu(dive);
+ if (dive->maxcns == 0)
+ dive->maxcns = calculate_cns(dive);
+ }
+}
+
+#define MAX_GAS_STRING 80
+#define UTF8_ELLIPSIS "\xE2\x80\xA6"
+
+/* callers needs to free the string */
+char *get_dive_gas_string(struct dive *dive)
+{
+ int o2, he, o2max;
+ char *buffer = malloc(MAX_GAS_STRING);
+
+ if (buffer) {
+ get_dive_gas(dive, &o2, &he, &o2max);
+ o2 = (o2 + 5) / 10;
+ he = (he + 5) / 10;
+ o2max = (o2max + 5) / 10;
+
+ if (he)
+ if (o2 == o2max)
+ snprintf(buffer, MAX_GAS_STRING, "%d/%d", o2, he);
+ else
+ snprintf(buffer, MAX_GAS_STRING, "%d/%d" UTF8_ELLIPSIS "%d%%", o2, he, o2max);
+ else if (o2)
+ if (o2 == o2max)
+ snprintf(buffer, MAX_GAS_STRING, "%d%%", o2);
+ else
+ snprintf(buffer, MAX_GAS_STRING, "%d" UTF8_ELLIPSIS "%d%%", o2, o2max);
+ else
+ strcpy(buffer, translate("gettextFromC", "air"));
+ }
+ return buffer;
+}
+
+/*
+ * helper functions for dive_trip handling
+ */
+#ifdef DEBUG_TRIP
+void dump_trip_list(void)
+{
+ dive_trip_t *trip;
+ int i = 0;
+ timestamp_t last_time = 0;
+
+ for (trip = dive_trip_list; trip; trip = trip->next) {
+ struct tm tm;
+ utc_mkdate(trip->when, &tm);
+ if (trip->when < last_time)
+ printf("\n\ndive_trip_list OUT OF ORDER!!!\n\n\n");
+ printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n",
+ trip->autogen ? "autogen " : "",
+ ++i, trip->location,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ trip->nrdives, trip);
+ last_time = trip->when;
+ }
+ printf("-----\n");
+}
+#endif
+
+/* this finds the last trip that at or before the time given */
+dive_trip_t *find_matching_trip(timestamp_t when)
+{
+ dive_trip_t *trip = dive_trip_list;
+
+ if (!trip || trip->when > when) {
+#ifdef DEBUG_TRIP
+ printf("no matching trip\n");
+#endif
+ return NULL;
+ }
+ while (trip->next && trip->next->when <= when)
+ trip = trip->next;
+#ifdef DEBUG_TRIP
+ {
+ struct tm tm;
+ utc_mkdate(trip->when, &tm);
+ printf("found trip %p @ %04d-%02d-%02d %02d:%02d:%02d\n",
+ trip,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ }
+#endif
+ return trip;
+}
+
+/* insert the trip into the dive_trip_list - but ensure you don't have
+ * two trips for the same date; but if you have, make sure you don't
+ * keep the one with less information */
+void insert_trip(dive_trip_t **dive_trip_p)
+{
+ dive_trip_t *dive_trip = *dive_trip_p;
+ dive_trip_t **p = &dive_trip_list;
+ dive_trip_t *trip;
+ struct dive *divep;
+
+ /* Walk the dive trip list looking for the right location.. */
+ while ((trip = *p) != NULL && trip->when < dive_trip->when)
+ p = &trip->next;
+
+ if (trip && trip->when == dive_trip->when) {
+ if (!trip->location)
+ trip->location = dive_trip->location;
+ if (!trip->notes)
+ trip->notes = dive_trip->notes;
+ divep = dive_trip->dives;
+ while (divep) {
+ add_dive_to_trip(divep, trip);
+ divep = divep->next;
+ }
+ *dive_trip_p = trip;
+ } else {
+ dive_trip->next = trip;
+ *p = dive_trip;
+ }
+#ifdef DEBUG_TRIP
+ dump_trip_list();
+#endif
+}
+
+static void delete_trip(dive_trip_t *trip)
+{
+ dive_trip_t **p, *tmp;
+
+ assert(!trip->dives);
+
+ /* Remove the trip from the list of trips */
+ p = &dive_trip_list;
+ while ((tmp = *p) != NULL) {
+ if (tmp == trip) {
+ *p = trip->next;
+ break;
+ }
+ p = &tmp->next;
+ }
+
+ /* .. and free it */
+ free(trip->location);
+ free(trip->notes);
+ free(trip);
+}
+
+void find_new_trip_start_time(dive_trip_t *trip)
+{
+ struct dive *dive = trip->dives;
+ timestamp_t when = dive->when;
+
+ while ((dive = dive->next) != NULL) {
+ if (dive->when < when)
+ when = dive->when;
+ }
+ trip->when = when;
+}
+
+/* check if we have a trip right before / after this dive */
+bool is_trip_before_after(struct dive *dive, bool before)
+{
+ int idx = get_idx_by_uniq_id(dive->id);
+ if (before) {
+ if (idx > 0 && get_dive(idx - 1)->divetrip)
+ return true;
+ } else {
+ if (idx < dive_table.nr - 1 && get_dive(idx + 1)->divetrip)
+ return true;
+ }
+ return false;
+}
+
+struct dive *first_selected_dive()
+{
+ int idx;
+ struct dive *d;
+
+ for_each_dive (idx, d) {
+ if (d->selected)
+ return d;
+ }
+ return NULL;
+}
+
+struct dive *last_selected_dive()
+{
+ int idx;
+ struct dive *d, *ret = NULL;
+
+ for_each_dive (idx, d) {
+ if (d->selected)
+ ret = d;
+ }
+ return ret;
+}
+
+void remove_dive_from_trip(struct dive *dive, short was_autogen)
+{
+ struct dive *next, **pprev;
+ dive_trip_t *trip = dive->divetrip;
+
+ if (!trip)
+ return;
+
+ /* Remove the dive from the trip's list of dives */
+ next = dive->next;
+ pprev = dive->pprev;
+ *pprev = next;
+ if (next)
+ next->pprev = pprev;
+
+ dive->divetrip = NULL;
+ if (was_autogen)
+ dive->tripflag = TF_NONE;
+ else
+ dive->tripflag = NO_TRIP;
+ assert(trip->nrdives > 0);
+ if (!--trip->nrdives)
+ delete_trip(trip);
+ else if (trip->when == dive->when)
+ find_new_trip_start_time(trip);
+}
+
+void add_dive_to_trip(struct dive *dive, dive_trip_t *trip)
+{
+ if (dive->divetrip == trip)
+ return;
+ remove_dive_from_trip(dive, false);
+ trip->nrdives++;
+ dive->divetrip = trip;
+ dive->tripflag = ASSIGNED_TRIP;
+
+ /* Add it to the trip's list of dives*/
+ dive->next = trip->dives;
+ if (dive->next)
+ dive->next->pprev = &dive->next;
+ trip->dives = dive;
+ dive->pprev = &trip->dives;
+
+ if (dive->when && trip->when > dive->when)
+ trip->when = dive->when;
+}
+
+dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive)
+{
+ dive_trip_t *dive_trip = calloc(1, sizeof(dive_trip_t));
+
+ dive_trip->when = dive->when;
+ dive_trip->location = copy_string(get_dive_location(dive));
+ insert_trip(&dive_trip);
+
+ dive->tripflag = IN_TRIP;
+ add_dive_to_trip(dive, dive_trip);
+ return dive_trip;
+}
+
+/*
+ * Walk the dives from the oldest dive, and see if we can autogroup them
+ */
+void autogroup_dives(void)
+{
+ int i;
+ struct dive *dive, *lastdive = NULL;
+
+ for_each_dive(i, dive) {
+ dive_trip_t *trip;
+
+ if (dive->divetrip) {
+ lastdive = dive;
+ continue;
+ }
+
+ if (!DIVE_NEEDS_TRIP(dive)) {
+ lastdive = NULL;
+ continue;
+ }
+
+ /* Do we have a trip we can combine this into? */
+ if (lastdive && dive->when < lastdive->when + TRIP_THRESHOLD) {
+ dive_trip_t *trip = lastdive->divetrip;
+ add_dive_to_trip(dive, trip);
+ if (get_dive_location(dive) && !trip->location)
+ trip->location = copy_string(get_dive_location(dive));
+ lastdive = dive;
+ continue;
+ }
+
+ lastdive = dive;
+ trip = create_and_hookup_trip_from_dive(dive);
+ trip->autogen = 1;
+ }
+
+#ifdef DEBUG_TRIP
+ dump_trip_list();
+#endif
+}
+
+/* this implements the mechanics of removing the dive from the table,
+ * but doesn't deal with updating dive trips, etc */
+void delete_single_dive(int idx)
+{
+ int i;
+ struct dive *dive = get_dive(idx);
+ if (!dive)
+ return; /* this should never happen */
+ remove_dive_from_trip(dive, false);
+ if (dive->selected)
+ deselect_dive(idx);
+ for (i = idx; i < dive_table.nr - 1; i++)
+ dive_table.dives[i] = dive_table.dives[i + 1];
+ dive_table.dives[--dive_table.nr] = NULL;
+ /* free all allocations */
+ free(dive->dc.sample);
+ free((void *)dive->notes);
+ free((void *)dive->divemaster);
+ free((void *)dive->buddy);
+ free((void *)dive->suit);
+ taglist_free(dive->tag_list);
+ free(dive);
+}
+
+struct dive **grow_dive_table(struct dive_table *table)
+{
+ int nr = table->nr, allocated = table->allocated;
+ struct dive **dives = table->dives;
+
+ if (nr >= allocated) {
+ allocated = (nr + 32) * 3 / 2;
+ dives = realloc(dives, allocated * sizeof(struct dive *));
+ if (!dives)
+ exit(1);
+ table->dives = dives;
+ table->allocated = allocated;
+ }
+ return dives;
+}
+
+void add_single_dive(int idx, struct dive *dive)
+{
+ int i;
+ grow_dive_table(&dive_table);
+ dive_table.nr++;
+ if (dive->selected)
+ amount_selected++;
+
+ if (idx < 0) {
+ // convert an idx of -1 so we do insert-in-chronological-order
+ idx = dive_table.nr - 1;
+ for (int i = 0; i < dive_table.nr - 1; i++) {
+ if (dive->when <= dive_table.dives[i]->when) {
+ idx = i;
+ break;
+ }
+ }
+ }
+
+ for (i = idx; i < dive_table.nr; i++) {
+ struct dive *tmp = dive_table.dives[i];
+ dive_table.dives[i] = dive;
+ dive = tmp;
+ }
+}
+
+bool consecutive_selected()
+{
+ struct dive *d;
+ int i;
+ bool consecutive = true;
+ bool firstfound = false;
+ bool lastfound = false;
+
+ if (amount_selected == 0 || amount_selected == 1)
+ return true;
+
+ for_each_dive(i, d) {
+ if (d->selected) {
+ if (!firstfound)
+ firstfound = true;
+ else if (lastfound)
+ consecutive = false;
+ } else if (firstfound) {
+ lastfound = true;
+ }
+ }
+ return consecutive;
+}
+
+/*
+ * Merge two dives. 'a' is always before 'b' in the dive list
+ * (and thus in time).
+ */
+struct dive *merge_two_dives(struct dive *a, struct dive *b)
+{
+ struct dive *res;
+ int i, j, nr, nrdiff;
+ int id;
+
+ if (!a || !b)
+ return NULL;
+
+ id = a->id;
+ i = get_divenr(a);
+ j = get_divenr(b);
+ if (i < 0 || j < 0)
+ // something is wrong with those dives. Bail
+ return NULL;
+ res = merge_dives(a, b, b->when - a->when, false);
+ if (!res)
+ return NULL;
+
+ /*
+ * If 'a' and 'b' were numbered, and in proper order,
+ * then the resulting dive will get the first number,
+ * and the subsequent dives will be renumbered by the
+ * difference.
+ *
+ * So if you had a dive list 1 3 6 7 8, and you
+ * merge 1 and 3, the resulting numbered list will
+ * be 1 4 5 6, because we assume that there were
+ * some missing dives (originally dives 4 and 5),
+ * that now will still be missing (dives 2 and 3
+ * in the renumbered world).
+ *
+ * Obviously the normal case is that everything is
+ * consecutive, and the difference will be 1, so the
+ * above example is not supposed to be normal.
+ */
+ nrdiff = 0;
+ nr = a->number;
+ if (a->number && b->number > a->number) {
+ res->number = nr;
+ nrdiff = b->number - nr;
+ }
+
+ add_single_dive(i, res);
+ delete_single_dive(i + 1);
+ delete_single_dive(j);
+ // now make sure that we keep the id of the first dive.
+ // why?
+ // because this way one of the previously selected ids is still around
+ res->id = id;
+
+ // renumber dives from merged one in advance by difference between
+ // merged dives numbers. Do not renumber if actual number is zero.
+ for (; j < dive_table.nr; j++) {
+ struct dive *dive = dive_table.dives[j];
+ int newnr;
+
+ if (!dive->number)
+ continue;
+ newnr = dive->number - nrdiff;
+
+ /*
+ * Don't renumber stuff that isn't in order!
+ *
+ * So if the new dive number isn't larger than the
+ * previous dive number, just stop here.
+ */
+ if (newnr <= nr)
+ break;
+ dive->number = newnr;
+ nr = newnr;
+ }
+
+ mark_divelist_changed(true);
+ return res;
+}
+
+void select_dive(int idx)
+{
+ struct dive *dive = get_dive(idx);
+ if (dive) {
+ /* never select an invalid dive that isn't displayed */
+ if (!dive->selected) {
+ dive->selected = 1;
+ amount_selected++;
+ }
+ selected_dive = idx;
+ }
+}
+
+void deselect_dive(int idx)
+{
+ struct dive *dive = get_dive(idx);
+ if (dive && dive->selected) {
+ dive->selected = 0;
+ if (amount_selected)
+ amount_selected--;
+ if (selected_dive == idx && amount_selected > 0) {
+ /* pick a different dive as selected */
+ while (--selected_dive >= 0) {
+ dive = get_dive(selected_dive);
+ if (dive && dive->selected)
+ return;
+ }
+ selected_dive = idx;
+ while (++selected_dive < dive_table.nr) {
+ dive = get_dive(selected_dive);
+ if (dive && dive->selected)
+ return;
+ }
+ }
+ if (amount_selected == 0)
+ selected_dive = -1;
+ }
+}
+
+void deselect_dives_in_trip(struct dive_trip *trip)
+{
+ struct dive *dive;
+ if (!trip)
+ return;
+ for (dive = trip->dives; dive; dive = dive->next)
+ deselect_dive(get_divenr(dive));
+}
+
+void select_dives_in_trip(struct dive_trip *trip)
+{
+ struct dive *dive;
+ if (!trip)
+ return;
+ for (dive = trip->dives; dive; dive = dive->next)
+ if (!dive->hidden_by_filter)
+ select_dive(get_divenr(dive));
+}
+
+void filter_dive(struct dive *d, bool shown)
+{
+ if (!d)
+ return;
+ d->hidden_by_filter = !shown;
+ if (!shown && d->selected)
+ deselect_dive(get_divenr(d));
+}
+
+
+/* This only gets called with non-NULL trips.
+ * It does not combine notes or location, just picks the first one
+ * (or the second one if the first one is empty */
+void combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b)
+{
+ if (same_string(trip_a->location, "") && trip_b->location) {
+ free(trip_a->location);
+ trip_a->location = strdup(trip_b->location);
+ }
+ if (same_string(trip_a->notes, "") && trip_b->notes) {
+ free(trip_a->notes);
+ trip_a->notes = strdup(trip_b->notes);
+ }
+ /* this also removes the dives from trip_b and eventually
+ * calls delete_trip(trip_b) when the last dive has been moved */
+ while (trip_b->dives)
+ add_dive_to_trip(trip_b->dives, trip_a);
+}
+
+void mark_divelist_changed(int changed)
+{
+ dive_list_changed = changed;
+ updateWindowTitle();
+}
+
+int unsaved_changes()
+{
+ return dive_list_changed;
+}
+
+void remove_autogen_trips()
+{
+ int i;
+ struct dive *dive;
+
+ for_each_dive(i, dive) {
+ dive_trip_t *trip = dive->divetrip;
+
+ if (trip && trip->autogen)
+ remove_dive_from_trip(dive, true);
+ }
+}
+
+/*
+ * When adding dives to the dive table, we try to renumber
+ * the new dives based on any old dives in the dive table.
+ *
+ * But we only do it if:
+ *
+ * - there are no dives in the dive table
+ *
+ * OR
+ *
+ * - the last dive in the old dive table was numbered
+ *
+ * - all the new dives are strictly at the end (so the
+ * "last dive" is at the same location in the dive table
+ * after re-sorting the dives.
+ *
+ * - none of the new dives have any numbers
+ *
+ * This catches the common case of importing new dives from
+ * a dive computer, and gives them proper numbers based on
+ * your old dive list. But it tries to be very conservative
+ * and not give numbers if there is *any* question about
+ * what the numbers should be - in which case you need to do
+ * a manual re-numbering.
+ */
+static void try_to_renumber(struct dive *last, int preexisting)
+{
+ int i, nr;
+
+ /*
+ * If the new dives aren't all strictly at the end,
+ * we're going to expect the user to do a manual
+ * renumbering.
+ */
+ if (preexisting && get_dive(preexisting - 1) != last)
+ return;
+
+ /*
+ * If any of the new dives already had a number,
+ * we'll have to do a manual renumbering.
+ */
+ for (i = preexisting; i < dive_table.nr; i++) {
+ struct dive *dive = get_dive(i);
+ if (dive->number)
+ return;
+ }
+
+ /*
+ * Ok, renumber..
+ */
+ if (last)
+ nr = last->number;
+ else
+ nr = 0;
+ for (i = preexisting; i < dive_table.nr; i++) {
+ struct dive *dive = get_dive(i);
+ dive->number = ++nr;
+ }
+}
+
+void process_dives(bool is_imported, bool prefer_imported)
+{
+ int i;
+ int preexisting = dive_table.preexisting;
+ bool did_merge = false;
+ struct dive *last;
+
+ /* check if we need a nickname for the divecomputer for newly downloaded dives;
+ * since we know they all came from the same divecomputer we just check for the
+ * first one */
+ if (preexisting < dive_table.nr && dive_table.dives[preexisting]->downloaded)
+ set_dc_nickname(dive_table.dives[preexisting]);
+ else
+ /* they aren't downloaded, so record / check all new ones */
+ for (i = preexisting; i < dive_table.nr; i++)
+ set_dc_nickname(dive_table.dives[i]);
+
+ for (i = preexisting; i < dive_table.nr; i++)
+ dive_table.dives[i]->downloaded = true;
+
+ /* This does the right thing for -1: NULL */
+ last = get_dive(preexisting - 1);
+
+ sort_table(&dive_table);
+
+ for (i = 1; i < dive_table.nr; i++) {
+ struct dive **pp = &dive_table.dives[i - 1];
+ struct dive *prev = pp[0];
+ struct dive *dive = pp[1];
+ struct dive *merged;
+ int id;
+
+ /* only try to merge overlapping dives - or if one of the dives has
+ * zero duration (that might be a gps marker from the webservice) */
+ if (prev->duration.seconds && dive->duration.seconds &&
+ prev->when + prev->duration.seconds < dive->when)
+ continue;
+
+ merged = try_to_merge(prev, dive, prefer_imported);
+ if (!merged)
+ continue;
+
+ // remember the earlier dive's id
+ id = prev->id;
+
+ /* careful - we might free the dive that last points to. Oops... */
+ if (last == prev || last == dive)
+ last = merged;
+
+ /* Redo the new 'i'th dive */
+ i--;
+ add_single_dive(i, merged);
+ delete_single_dive(i + 1);
+ delete_single_dive(i + 1);
+ // keep the id or the first dive for the merged dive
+ merged->id = id;
+
+ /* this means the table was changed */
+ did_merge = true;
+ }
+ /* make sure no dives are still marked as downloaded */
+ for (i = 1; i < dive_table.nr; i++)
+ dive_table.dives[i]->downloaded = false;
+
+ if (is_imported) {
+ /* If there are dives in the table, are they numbered */
+ if (!last || last->number)
+ try_to_renumber(last, preexisting);
+
+ /* did we add dives or divecomputers to the dive table? */
+ if (did_merge || preexisting < dive_table.nr) {
+ mark_divelist_changed(true);
+ }
+ }
+}
+
+void set_dive_nr_for_current_dive()
+{
+ if (dive_table.nr == 1)
+ current_dive->number = 1;
+ else if (selected_dive == dive_table.nr - 1 && get_dive(dive_table.nr - 2)->number)
+ current_dive->number = get_dive(dive_table.nr - 2)->number + 1;
+}
+
+static int min_datafile_version;
+
+int get_min_datafile_version()
+{
+ return min_datafile_version;
+}
+
+void reset_min_datafile_version()
+{
+ min_datafile_version = 0;
+}
+
+void report_datafile_version(int version)
+{
+ if (min_datafile_version == 0 || min_datafile_version > version)
+ min_datafile_version = version;
+}
+
+void clear_dive_file_data()
+{
+ while (dive_table.nr)
+ delete_single_dive(0);
+ while (dive_site_table.nr)
+ delete_dive_site(get_dive_site(0)->uuid);
+
+ clear_dive(&displayed_dive);
+ clear_dive_site(&displayed_dive_site);
+
+ free((void *)existing_filename);
+ existing_filename = NULL;
+
+ reset_min_datafile_version();
+ saved_git_id = "";
+}