// SPDX-License-Identifier: GPL-2.0 #include "trip.h" #include "dive.h" #include "divelog.h" #include "errorhelper.h" #include "range.h" #include "subsurface-time.h" #include "subsurface-string.h" #include "selection.h" dive_trip::dive_trip() : id(dive_getUniqID()) { } dive_trip::~dive_trip() = default; timestamp_t dive_trip::date() const { if (dives.empty()) return 0; return dives[0]->when; } static timestamp_t trip_enddate(const struct dive_trip &trip) { if (trip.dives.empty()) return 0; return trip.dives.back()->endtime(); } /* Add dive to a trip. Caller is responsible for removing dive * from trip beforehand. */ void dive_trip::add_dive(struct dive *dive) { if (dive->divetrip == this) return; if (dive->divetrip) report_info("Warning: adding dive to trip, which already has a trip set"); range_insert_sorted(dives, dive, comp_dives_ptr); dive->divetrip = this; } /* remove a dive from the trip it's associated to, but don't delete the * trip if this was the last dive in the trip. the caller is responsible * for removing the trip, if the trip->dives.size() went to 0. */ struct dive_trip *unregister_dive_from_trip(struct dive *dive) { dive_trip *trip = dive->divetrip; if (!trip) return NULL; range_remove(trip->dives, dive); dive->divetrip = NULL; return trip; } std::unique_ptr create_trip_from_dive(const struct dive *dive) { auto trip = std::make_unique(); trip->location = dive->get_location(); return trip; } /* random threshold: three days without diving -> new trip * this works very well for people who usually dive as part of a trip and don't * regularly dive at a local facility; this is why trips are an optional feature */ #define TRIP_THRESHOLD 3600 * 24 * 3 /* * Find a trip a new dive should be autogrouped with. If no such trips * exist, allocate a new trip. A unique_ptr is returned if a new trip * was allocated. The caller has to store it. */ std::pair> get_trip_for_new_dive(const struct divelog &log, const struct dive *new_dive) { /* Find dive that is within TRIP_THRESHOLD of current dive */ for (auto &d: log.dives) { /* Check if we're past the range of possible dives */ if (d->when >= new_dive->when + TRIP_THRESHOLD) break; if (d->when + TRIP_THRESHOLD >= new_dive->when && d->divetrip) return { d->divetrip, nullptr }; /* Found a dive with trip in the range */ } /* Didn't find a trip -> allocate a new one */ auto trip = create_trip_from_dive(new_dive); trip->autogen = true; auto t = trip.get(); return { t, std::move(trip) }; } /* Check if two trips overlap time-wise up to trip threshold. */ bool trips_overlap(const struct dive_trip &t1, const struct dive_trip &t2) { /* First, handle the empty-trip cases. */ if (t1.dives.empty() || t2.dives.empty()) return 0; if (t1.date() < t2.date()) return trip_enddate(t1) + TRIP_THRESHOLD >= t2.date(); else return trip_enddate(t2) + TRIP_THRESHOLD >= t1.date(); } /* * Collect dives for auto-grouping. Pass in first dive which should be checked. * Returns range of dives that should be autogrouped and trip it should be * associated to. If the returned trip was newly allocated, a std::unique_ptr<> * to the trip is returned. * is set to true. Caller still has to register it in the system. Note * whereas this looks complicated - it is needed by the undo-system, which * manually injects the new trips. If there are no dives to be autogrouped, * return NULL. */ std::vector get_dives_to_autogroup(const struct dive_table &table) { std::vector res; struct dive *lastdive = NULL; /* Find first dive that should be merged and remember any previous * dive that could be merged into. */ for (size_t i = 0; i < table.size(); ++i) { auto &dive = table[i]; if (dive->divetrip) { lastdive = dive.get(); continue; } /* Only consider dives that have not been explicitly removed from * a dive trip by the user. */ if (dive->notrip) { lastdive = NULL; continue; } /* We found a dive, let's see if we have to allocate a new trip */ std::unique_ptr allocated; dive_trip *trip; if (!lastdive || dive->when >= lastdive->when + TRIP_THRESHOLD) { /* allocate new trip */ allocated = create_trip_from_dive(dive.get()); allocated->autogen = true; trip = allocated.get(); } else { /* use trip of previous dive */ trip = lastdive->divetrip; } // Now, find all dives that will be added to this trip lastdive = dive.get(); size_t to; for (to = i + 1; to < table.size(); to++) { auto &dive = table[to]; if (dive->divetrip || dive->notrip || dive->when >= lastdive->when + TRIP_THRESHOLD) break; if (trip->location.empty()) trip->location = dive->get_location(); lastdive = dive.get(); } res.push_back({ i, to, trip, std::move(allocated) }); i = to - 1; } return res; } /* Out of two strings, get the string that is not empty (if any). */ static std::string non_empty_string(const std::string &a, const std::string &b) { return b.empty() ? a : b; } /* This combines the information of two trips, generating a * new trip. To support undo, we have to preserve the old trips. */ std::unique_ptr combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b) { auto trip = std::make_unique(); trip->location = non_empty_string(trip_a->location, trip_b->location); trip->notes = non_empty_string(trip_a->notes, trip_b->notes); return trip; } /* Trips are compared according to the first dive in the trip. */ int comp_trips(const struct dive_trip &a, const struct dive_trip &b) { // To make sure that trips never compare equal, compare by // address if both are empty. if (&a == &b) return 0; // reflexivity. shouldn't happen. if (a.dives.empty() && b.dives.empty()) return &a < &b ? -1 : 1; if (a.dives.empty()) return -1; if (b.dives.empty()) return 1; return comp_dives(*a.dives[0], *b.dives[0]); } static bool is_same_day(timestamp_t trip_when, timestamp_t dive_when) { static timestamp_t twhen = (timestamp_t) 0; static struct tm tmt; struct tm tmd; utc_mkdate(dive_when, &tmd); if (twhen != trip_when) { twhen = trip_when; utc_mkdate(twhen, &tmt); } return (tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year); } bool dive_trip::is_single_day() const { if (dives.size() <= 1) return true; return is_same_day(dives.front()->when, dives.back()->when); } int dive_trip::shown_dives() const { return std::count_if(dives.begin(), dives.end(), [](const dive *d) { return !d->hidden_by_filter; }); } void dive_trip::sort_dives() { std::sort(dives.begin(), dives.end(), [] (dive *d1, dive *d2) { return comp_dives(*d1, *d2) < 0; }); }