From 0ac70a0febaa83ab09e0889764b18ef9d33e4869 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 22 Sep 2024 23:24:25 +1200 Subject: [PATCH] Profile: Improve Display of Bailout / Loop Events. For dives in CCR mode, show 'bailout' and 'on loop' events whenever a gas switch from a diluent gas to a bailout gas and vice versa happens. Signed-off-by: Michael Keller --- core/dive.cpp | 103 ++++++++++++++++++++---------- core/dive.h | 7 +- core/divecomputer.cpp | 7 +- core/divelist.cpp | 10 +-- core/event.h | 13 ++-- core/gaspressures.cpp | 11 ++-- core/planner.cpp | 17 +++-- core/plannernotes.cpp | 2 +- core/profile.cpp | 26 ++++---- core/save-git.cpp | 6 +- core/save-xml.cpp | 6 +- profile-widget/diveeventitem.cpp | 24 ++++--- profile-widget/diveeventitem.h | 6 +- profile-widget/profilescene.cpp | 24 +++++-- profile-widget/profilewidget2.cpp | 32 +++++----- qt-models/diveplannermodel.cpp | 10 +-- 16 files changed, 183 insertions(+), 121 deletions(-) diff --git a/core/dive.cpp b/core/dive.cpp index 721b4f2f5..014a9eafd 100644 --- a/core/dive.cpp +++ b/core/dive.cpp @@ -56,7 +56,7 @@ dive::~dive() = default; * equals the appropriate enum value [oxygen, diluent, bailout] given by cylinder_use_type. * A negative number returned indicates that a match could not be found. * Call parameters: dive = the dive being processed - * cylinder_use_type = an enum, one of {oxygen, diluent, bailout} */ + * cylinder_use_type = an enum, one of {oxygen, diluent, bailout} */ static int get_cylinder_idx_by_use(const struct dive &dive, enum cylinderuse cylinder_use_type) { auto it = std::find_if(dive.cylinders.begin(), dive.cylinders.end(), [cylinder_use_type] @@ -156,16 +156,18 @@ void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int second add_event_to_dc(dc, std::move(ev)); } -struct gasmix dive::get_gasmix_from_event(const struct event &ev) const +std::pair dive::get_gasmix_from_event(const struct event &ev, const struct divecomputer &dc) const { if (ev.is_gaschange()) { int index = ev.gas.index; // FIXME: The planner uses one past cylinder-count to signify "surface air". Remove in due course. - if (index >= 0 && static_cast(index) < cylinders.size() + 1) - return get_cylinder(index)->gasmix; - return ev.gas.mix; + if (index >= 0 && static_cast(index) < cylinders.size() + 1) { + const cylinder_t *cylinder = get_cylinder(index); + return std::make_pair(cylinder->gasmix, get_effective_divemode(dc, *cylinder)); + } + return std::make_pair(ev.gas.mix, dc.divemode); } - return gasmix_air; + return std::make_pair(gasmix_air, dc.divemode); } // we need this to be uniq. oh, and it has no meaning whatsoever @@ -307,7 +309,7 @@ static int get_cylinder_used(const struct dive *dive, bool used[]) * Some dive computers give cylinder indices, some * give just the gas mix. */ -int dive::get_cylinder_index(const struct event &ev) const +int dive::get_cylinder_index(const struct event &ev, const struct divecomputer &dc) const { if (ev.gas.index >= 0) return ev.gas.index; @@ -320,7 +322,7 @@ int dive::get_cylinder_index(const struct event &ev) const */ report_info("Still looking up cylinder based on gas mix in get_cylinder_index()!"); - gasmix mix = get_gasmix_from_event(ev); + gasmix mix = get_gasmix_from_event(ev, dc).first; int best = find_best_gasmix_match(mix, cylinders); return best < 0 ? 0 : best; } @@ -1629,6 +1631,38 @@ bool is_cylinder_use_appropriate(const struct divecomputer &dc, const cylinder_t return true; } +divemode_t get_effective_divemode(const struct divecomputer &dc, const struct cylinder_t &cylinder) +{ + divemode_t divemode = dc.divemode; + if (divemode == CCR && cylinder.cylinder_use == OC_GAS) + divemode = OC; + + return divemode; +} + +std::tuple get_dive_status_at(const struct dive &dive, const struct divecomputer &dc, int seconds, divemode_loop *loop_mode, gasmix_loop *loop_gas) +{ + if (!loop_mode) + loop_mode = new divemode_loop(dc); + if (!loop_gas) + loop_gas = new gasmix_loop(dive, dc); + auto [divemode, divemode_time] = loop_mode->at(seconds); + auto [cylinder_index, gasmix_time] = loop_gas->cylinder_index_at(seconds); + const struct gasmix *gasmix; + if (cylinder_index == -1) { + gasmix = &gasmix_air; + if (gasmix_time >= divemode_time) + divemode = OC; + } else { + const struct cylinder_t *cylinder = dive.get_cylinder(cylinder_index); + gasmix = &cylinder->gasmix; + if (gasmix_time >= divemode_time) + divemode = get_effective_divemode(dc, *cylinder); + } + + return std::make_tuple(divemode, cylinder_index, gasmix); +} + /* * Merging cylinder information is non-trivial, because the two dive computers * may have different ideas of what the different cylinder indexing is. @@ -2573,25 +2607,27 @@ gasmix_loop::gasmix_loop(const struct dive &d, const struct divecomputer &dc) : std::pair gasmix_loop::next_cylinder_index() { - if (dive.cylinders.empty()) - return std::make_pair(-1, INT_MAX); - if (first_run) { next_event = loop.next(); - last_cylinder_index = 0; // default to first cylinder - last_time = 0; - if (next_event && ((!dc.samples.empty() && next_event->time.seconds == dc.samples[0].time.seconds) || next_event->time.seconds <= 1)) { - last_cylinder_index = dive.get_cylinder_index(*next_event); - last_time = next_event->time.seconds; - next_event = loop.next(); - } else if (dc.divemode == CCR) { - last_cylinder_index = std::max(get_cylinder_idx_by_use(dive, DILUENT), last_cylinder_index); - } - first_run = false; + + if (dive.cylinders.empty()) { + last_cylinder_index = -1; + last_time = 0; + } else { + last_cylinder_index = 0; // default to first cylinder + last_time = 0; + if (next_event && ((!dc.samples.empty() && next_event->time.seconds == dc.samples[0].time.seconds) || next_event->time.seconds <= 1)) { + last_cylinder_index = dive.get_cylinder_index(*next_event, dc); + last_time = next_event->time.seconds; + next_event = loop.next(); + } else if (dc.divemode == CCR) { + last_cylinder_index = std::max(get_cylinder_idx_by_use(dive, DILUENT), last_cylinder_index); + } + } } else { if (next_event) { - last_cylinder_index = dive.get_cylinder_index(*next_event); + last_cylinder_index = dive.get_cylinder_index(*next_event, dc); last_time = next_event->time.seconds; next_event = loop.next(); } else { @@ -2605,16 +2641,9 @@ std::pair gasmix_loop::next_cylinder_index() std::pair gasmix_loop::next() { - if (first_run && dive.cylinders.empty()) { - first_run = false; - - // return one cylinder of air if we don't have any cylinders - return std::make_pair(gasmix_air, 0); - } - next_cylinder_index(); - return std::make_pair(last_cylinder_index < 0 ? gasmix_invalid : dive.get_cylinder(last_cylinder_index)->gasmix, last_time); + return get_last_gasmix(); } std::pair gasmix_loop::cylinder_index_at(int time) @@ -2630,13 +2659,9 @@ std::pair gasmix_loop::cylinder_index_at(int time) std::pair gasmix_loop::at(int time) { - if (dive.cylinders.empty()) - // return air if we don't have any cylinders - return std::make_pair(gasmix_air, 0); - cylinder_index_at(time); - return std::make_pair(last_cylinder_index < 0 ? gasmix_invalid : dive.get_cylinder(last_cylinder_index)->gasmix, last_time); + return get_last_gasmix(); } bool gasmix_loop::has_next() const @@ -2644,6 +2669,14 @@ bool gasmix_loop::has_next() const return first_run || (!dive.cylinders.empty() && next_event); } +std::pair gasmix_loop::get_last_gasmix() +{ + if (last_cylinder_index < 0 && last_time == 0) + return std::make_pair(gasmix_air, 0); + + return std::make_pair(last_cylinder_index < 0 ? gasmix_invalid : dive.get_cylinder(last_cylinder_index)->gasmix, last_time); +} + /* get the gas at a certain time during the dive */ /* If there is a gasswitch at that time, it returns the new gasmix */ struct gasmix dive::get_gasmix_at_time(const struct divecomputer &dc, duration_t time) const diff --git a/core/dive.h b/core/dive.h index 03a2bbd57..476b63716 100644 --- a/core/dive.h +++ b/core/dive.h @@ -7,6 +7,7 @@ #include "divemode.h" #include "divecomputer.h" #include "equipment.h" +#include "event.h" #include "picture.h" // TODO: remove #include "tag.h" @@ -103,9 +104,9 @@ struct dive { bool likely_same(const struct dive &b) const; bool is_cylinder_used(int idx) const; bool is_cylinder_prot(int idx) const; - int get_cylinder_index(const struct event &ev) const; + int get_cylinder_index(const struct event &ev, const struct divecomputer &dc) const; bool has_gaschange_event(const struct divecomputer *dc, int idx) const; - struct gasmix get_gasmix_from_event(const struct event &ev) const; + std::pair get_gasmix_from_event(const struct event &ev, const struct divecomputer &dc) const; struct gasmix get_gasmix_at_time(const struct divecomputer &dc, duration_t time) const; cylinder_t *get_cylinder(int idx); cylinder_t *get_or_create_cylinder(int idx); @@ -152,6 +153,8 @@ struct dive_or_trip { extern void cylinder_renumber(struct dive &dive, int mapping[]); extern int same_gasmix_cylinder(const cylinder_t &cyl, int cylid, const struct dive *dive, bool check_unused); extern bool is_cylinder_use_appropriate(const struct divecomputer &dc, const cylinder_t &cyl, bool allowNonUsable); +extern divemode_t get_effective_divemode(const struct divecomputer &dc, const struct cylinder_t &cylinder); +extern std::tuple get_dive_status_at(const struct dive &dive, const struct divecomputer &dc, int seconds, divemode_loop *loop_mode = nullptr, gasmix_loop *loop_gas = nullptr); /* Data stored when copying a dive */ struct dive_paste_data { diff --git a/core/divecomputer.cpp b/core/divecomputer.cpp index 2a5acba21..6e274eb25 100644 --- a/core/divecomputer.cpp +++ b/core/divecomputer.cpp @@ -200,19 +200,20 @@ void fake_dc(struct divecomputer *dc) } divemode_loop::divemode_loop(const struct divecomputer &dc) : - last(dc.divemode), loop("modechange", dc) + last(dc.divemode), last_time(0), loop("modechange", dc) { /* on first invocation, get first event (if any) */ ev = loop.next(); } -divemode_t divemode_loop::at(int time) +std::pair divemode_loop::at(int time) { while (ev && ev->time.seconds <= time) { last = static_cast(ev->value); + last_time = ev->time.seconds; ev = loop.next(); } - return last; + return std::make_pair(last, last_time); } /* helper function to make it easier to work with our structures diff --git a/core/divelist.cpp b/core/divelist.cpp index 35679cf61..54c67e3af 100644 --- a/core/divelist.cpp +++ b/core/divelist.cpp @@ -424,8 +424,8 @@ static void add_dive_to_deco(struct deco_state *ds, const struct dive &dive, boo { const struct divecomputer *dc = &dive.dcs[0]; - gasmix_loop loop(dive, dive.dcs[0]); - divemode_loop loop_d(dive.dcs[0]); + gasmix_loop loop_gas(dive, dive.dcs[0]); + divemode_loop loop_mode(dive.dcs[0]); for (auto [psample, sample]: pairwise_range(dc->samples)) { int t0 = psample.time.seconds; int t1 = sample.time.seconds; @@ -433,9 +433,9 @@ static void add_dive_to_deco(struct deco_state *ds, const struct dive &dive, boo for (j = t0; j < t1; j++) { depth_t depth = interpolate(psample.depth, sample.depth, j - t0, t1 - t0); - auto gasmix = loop.at(j).first; - add_segment(ds, dive.depth_to_bar(depth), gasmix, 1, sample.setpoint.mbar, - loop_d.at(j), dive.sac, + [[maybe_unused]] auto [divemode, _cylinder_index, gasmix] = get_dive_status_at(dive, dive.dcs[0], j); + add_segment(ds, dive.depth_to_bar(depth), *gasmix, 1, sample.setpoint.mbar, + divemode, dive.sac, in_planner); } } diff --git a/core/event.h b/core/event.h index d96df5733..2294a64b6 100644 --- a/core/event.h +++ b/core/event.h @@ -73,19 +73,20 @@ class gasmix_loop { const struct event *next_event; int last_cylinder_index; int last_time; + std::pair get_last_gasmix(); public: gasmix_loop(const struct dive &dive, const struct divecomputer &dc); // Return the next cylinder index / gasmix from the list of gas switches // and the time in seconds when this gas switch happened // (including the potentially imaginary first gas switch to cylinder 0 / air) - std::pair next_cylinder_index(); // -1 -> end - std::pair next(); // gasmix_invalid -> end + std::pair next_cylinder_index(); // <-1, 0> => implicit air cylinder, <-1, INT_MAX> => end + std::pair next(); // => end // Return the cylinder index / gasmix at a given time during the dive // and the time in seconds when this switch to this gas happened // (including the potentially imaginary first gas switch to cylinder 0 / air) - std::pair cylinder_index_at(int time); // -1 -> end - std::pair at(int time); // gasmix_invalid -> end + std::pair cylinder_index_at(int time); // <-1, 0> => implicit air cylinder + std::pair at(int time); bool has_next() const; }; @@ -93,12 +94,14 @@ public: /* Get divemodes at increasing timestamps. */ class divemode_loop { divemode_t last; + int last_time; event_loop loop; const struct event *ev; public: divemode_loop(const struct divecomputer &dc); // Return the divemode at a given time during the dive - divemode_t at(int time); + // and the time in seconds when the switch to this divemode has happened + std::pair at(int time); }; extern const struct event *get_first_event(const struct divecomputer &dc, const std::string &name); diff --git a/core/gaspressures.cpp b/core/gaspressures.cpp index 3c94d496c..62660daa1 100644 --- a/core/gaspressures.cpp +++ b/core/gaspressures.cpp @@ -198,7 +198,7 @@ static void fill_missing_tank_pressures(const struct dive *dive, struct plot_inf dump_pr_track(cyl, track_pr); #endif - /* Transfer interpolated cylinder pressures from pr_track strucktures to plotdata + /* Transfer interpolated cylinder pressures from pr_track structures to plotdata * Go down the list of tank pressures in plot_info. Align them with the start & * end times of each profile segment represented by a pr_track_t structure. Get * the accumulated pressure_depths from the pr_track_t structures and then @@ -248,7 +248,7 @@ static void fill_missing_tank_pressures(const struct dive *dive, struct plot_inf last_segment = it; } - if(dive->get_cylinder(cyl)->cylinder_use == OC_GAS) { + if (dive->get_cylinder(cyl)->cylinder_use == OC_GAS) { /* if this segment has pressure_time, then calculate a new interpolated pressure */ if (interpolate.pressure_time) { @@ -358,16 +358,15 @@ void populate_pressure_information(const struct dive *dive, const struct divecom int pressure = get_plot_sensor_pressure(pi, i, sensor); int time = entry.sec; + [[maybe_unused]] auto [divemode, cylinder_index, _gasmix] = get_dive_status_at(*dive, *dc, time, &loop_mode, &loop_gas); if (has_gaschange) { - cyl = loop_gas.cylinder_index_at(time).first; + cyl = cylinder_index; if (cyl < 0) cyl = sensor; } - divemode_t dmode = loop_mode.at(time); - if (current != std::string::npos) { // calculate pressure-time, taking into account the dive mode for this specific segment. - entry.pressure_time = (int)(calc_pressure_time(dive, pi.entry[i - 1], entry) * gasfactor[dmode] + 0.5); + entry.pressure_time = (int)(calc_pressure_time(dive, pi.entry[i - 1], entry) * gasfactor[divemode] + 0.5); track[current].pressure_time += entry.pressure_time; track[current].t_end = entry.sec; if (pressure) diff --git a/core/planner.cpp b/core/planner.cpp index b7a99f08f..13430ace9 100644 --- a/core/planner.cpp +++ b/core/planner.cpp @@ -89,7 +89,7 @@ int get_cylinderid_at_time(struct dive *dive, struct divecomputer *dc, duration_ if (event.time.seconds > time.seconds) break; if (event.name == "gaschange") - cylinder_idx = dive->get_cylinder_index(event); + cylinder_idx = dive->get_cylinder_index(event, *dc); } return cylinder_idx; } @@ -130,7 +130,8 @@ static int tissue_at_end(struct deco_state *ds, struct dive *dive, const struct return 0; const struct sample *psample = nullptr; - divemode_loop loop(*dc); + gasmix_loop loop_gas(*dive, *dc); + divemode_loop loop_mode(*dc); for (auto &sample: dc->samples) { o2pressure_t setpoint = psample ? psample->setpoint : sample.setpoint; @@ -162,7 +163,8 @@ static int tissue_at_end(struct deco_state *ds, struct dive *dive, const struct ds->max_bottom_ceiling_pressure.mbar = ceiling_pressure.mbar; } - divemode_t divemode = loop.at(t0.seconds + 1); + [[maybe_unused]] auto [divemode, _cylinder_index, _gasmix] = get_dive_status_at(*dive, *dc, t0.seconds + 1, &loop_mode, &loop_gas); + interpolate_transition(ds, dive, t0, t1, lastdepth, sample.depth, gas, setpoint, divemode); psample = &sample; t0 = t1; @@ -612,7 +614,7 @@ std::vector plan(struct deco_state *ds, struct diveplan &diveplan, str deco_state_cache bottom_cache; int po2; int transitiontime, gi; - int current_cylinder, stop_cylinder; + int stop_cylinder; size_t stopidx; bool stopping = false; bool pendinggaschange = false; @@ -628,7 +630,6 @@ std::vector plan(struct deco_state *ds, struct diveplan &diveplan, str int laststoptime = timestep; bool o2breaking = false; struct divecomputer *dc = dive->get_dc(dcNr); - enum divemode_t divemode = dc->divemode; set_gf(diveplan.gflow, diveplan.gfhigh); set_vpmb_conservatism(diveplan.vpmb_conservatism); @@ -667,11 +668,9 @@ std::vector plan(struct deco_state *ds, struct diveplan &diveplan, str /* Keep time during the ascend */ bottom_time = clock = previous_point_time = sample.time.seconds; - current_cylinder = get_cylinderid_at_time(dive, dc, sample.time); // Find the divemode at the end of the dive - divemode_loop loop(*dc); - divemode = loop.at(bottom_time); - gas = dive->get_cylinder(current_cylinder)->gasmix; + [[maybe_unused]] auto [divemode, current_cylinder, gasmix] = get_dive_status_at(*dive, *dc, bottom_time); + gas = *gasmix; po2 = sample.setpoint.mbar; depth_t depth = sample.depth; diff --git a/core/plannernotes.cpp b/core/plannernotes.cpp index 8cea18468..718fe74db 100644 --- a/core/plannernotes.cpp +++ b/core/plannernotes.cpp @@ -588,7 +588,7 @@ void diveplan::add_plan_to_notes(struct dive &dive, bool show_disclaimer, planne std::string temp; struct gasmix gasmix = dive.get_cylinder(dp.cylinderid)->gasmix; - divemode_t current_divemode = loop.at(dp.time); + divemode_t current_divemode = loop.at(dp.time).first; amb = dive.depth_to_atm(dp.depth); gas_pressures pressures = fill_pressures(amb, gasmix, (current_divemode == OC) ? 0.0 : amb * gasmix.o2.permille / 1000.0, current_divemode); diff --git a/core/profile.cpp b/core/profile.cpp index 75e31836d..fb2320c2e 100644 --- a/core/profile.cpp +++ b/core/profile.cpp @@ -865,8 +865,8 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ if (decoMode(in_planner) == VPMB) ds->first_ceiling_pressure.mbar = dive->depth_to_mbar(first_ceiling); - gasmix_loop loop(*dive, *dc); - divemode_loop loop_d(*dc); + gasmix_loop loop_gas(*dive, *dc); + divemode_loop loop_mode(*dc); for (i = 1; i < pi.nr; i++) { struct plot_data &entry = pi.entry[i]; struct plot_data &prev = pi.entry[i - 1]; @@ -874,8 +874,8 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ int time_stepsize = 20; depth_t max_ceiling; - divemode_t current_divemode = loop_d.at(entry.sec); - struct gasmix gasmix = loop.at(t1).first; + [[maybe_unused]] auto [current_divemode, _cylinder_index, gasmix] = get_dive_status_at(*dive, *dc, entry.sec, &loop_mode, &loop_gas); + entry.ambpressure = dive->depth_to_bar(entry.depth); entry.gfline = get_gf(ds, entry.ambpressure, dive) * (100.0 - AMB_PERCENTAGE) + AMB_PERCENTAGE; if (t0 > t1) { @@ -887,7 +887,7 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ for (int j = t0 + time_stepsize; j <= t1; j += time_stepsize) { depth_t new_depth = interpolate(prev.depth, entry.depth, j - t0, t1 - t0); add_segment(ds, dive->depth_to_bar(new_depth), - gasmix, time_stepsize, entry.o2pressure.mbar, current_divemode, entry.sac, in_planner); + *gasmix, time_stepsize, entry.o2pressure.mbar, current_divemode, entry.sac, in_planner); entry.icd_warning = ds->icd_warning; if ((t1 - j < time_stepsize) && (j < t1)) time_stepsize = t1 - j; @@ -987,7 +987,7 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ /* We are going to mess up deco state, so store it for later restore */ deco_state_cache cache_data; cache_data.cache(ds); - calculate_ndl_tts(ds, dive, entry, gasmix, surface_pressure, current_divemode, in_planner); + calculate_ndl_tts(ds, dive, entry, *gasmix, surface_pressure, current_divemode, in_planner); if (decoMode(in_planner) == VPMB && !in_planner && i == pi.nr - 1) final_tts = entry.tts_calc; /* Restore "real" deco state for next real time step */ @@ -1104,21 +1104,19 @@ static void calculate_gas_information_new(const struct dive *dive, const struct int i; double amb_pressure; - gasmix_loop loop(*dive, *dc); - divemode_loop loop_d(*dc); + gasmix_loop loop_gas(*dive, *dc); + divemode_loop loop_mode(*dc); for (i = 1; i < pi.nr; i++) { double fn2, fhe; struct plot_data &entry = pi.entry[i]; - auto gasmix = loop.at(entry.sec).first; + [[maybe_unused]] auto [current_divemode, _cylinder_index, gasmix] = get_dive_status_at(*dive, *dc, entry.sec, &loop_mode, &loop_gas); amb_pressure = dive->depth_to_bar(entry.depth); - divemode_t current_divemode = loop_d.at(entry.sec); - entry.pressures = fill_pressures(amb_pressure, gasmix, (current_divemode == OC) ? 0.0 : entry.o2pressure.mbar / 1000.0, current_divemode); + entry.pressures = fill_pressures(amb_pressure, *gasmix, (current_divemode == OC) ? 0.0 : entry.o2pressure.mbar / 1000.0, current_divemode); fn2 = 1000.0 * entry.pressures.n2 / amb_pressure; fhe = 1000.0 * entry.pressures.he / amb_pressure; if (dc->divemode == PSCR) { // OC pO2 is calulated for PSCR with or without external PO2 monitoring. - struct gasmix gasmix2 = loop.at(entry.sec).first; - entry.scr_OC_pO2.mbar = (int) dive->depth_to_mbar(entry.depth) * get_o2(gasmix2) / 1000; + entry.scr_OC_pO2.mbar = (int) dive->depth_to_mbar(entry.depth) * get_o2(*gasmix) / 1000; } /* Calculate MOD, EAD, END and EADD based on partial pressures calculated before @@ -1126,7 +1124,7 @@ static void calculate_gas_information_new(const struct dive *dive, const struct * END takes O₂ + N₂ (air) into account ("Narcotic" for trimix dives) * EAD just uses N₂ ("Air" for nitrox dives) */ pressure_t modpO2 = { .mbar = (int)(prefs.modpO2 * 1000) }; - entry.mod = dive->gas_mod(gasmix, modpO2, 1_mm); + entry.mod = dive->gas_mod(*gasmix, modpO2, 1_mm); entry.end = dive->mbar_to_depth(lrint(dive->depth_to_mbarf(entry.depth) * (1000 - fhe) / 1000.0)); entry.ead = dive->mbar_to_depth(lrint(dive->depth_to_mbarf(entry.depth) * fn2 / (double)N2_IN_AIR)); entry.eadd = dive->mbar_to_depth(lrint(dive->depth_to_mbarf(entry.depth) * diff --git a/core/save-git.cpp b/core/save-git.cpp index b1c95b85b..8ab233f9c 100644 --- a/core/save-git.cpp +++ b/core/save-git.cpp @@ -373,7 +373,7 @@ static void save_samples(struct membuffer *b, const struct dive &dive, const str save_sample(b, s, dummy, o2sensor); } -static void save_one_event(struct membuffer *b, const struct dive &dive, const struct event &ev) +static void save_one_event(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc, const struct event &ev) { put_format(b, "event %d:%02d", FRACTION_TUPLE(ev.time.seconds, 60)); show_index(b, ev.type, "type=", ""); @@ -385,7 +385,7 @@ static void save_one_event(struct membuffer *b, const struct dive &dive, const s show_index(b, ev.value, "value=", ""); show_utf8(b, " name=", ev.name.c_str(), ""); if (ev.is_gaschange()) { - struct gasmix mix = dive.get_gasmix_from_event(ev); + struct gasmix mix = dive.get_gasmix_from_event(ev, dc).first; if (ev.gas.index >= 0) show_integer(b, ev.gas.index, "cylinder=", ""); put_gasmix(b, mix); @@ -396,7 +396,7 @@ static void save_one_event(struct membuffer *b, const struct dive &dive, const s static void save_events(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) { for (auto &ev: dc.events) - save_one_event(b, dive, ev); + save_one_event(b, dive, dc, ev); } static void save_dc(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) diff --git a/core/save-xml.cpp b/core/save-xml.cpp index 6f6924216..500c4bfbe 100644 --- a/core/save-xml.cpp +++ b/core/save-xml.cpp @@ -342,7 +342,7 @@ static void save_sample(struct membuffer *b, const struct sample &sample, struct put_format(b, " />\n"); } -static void save_one_event(struct membuffer *b, const struct dive &dive, const struct event &ev) +static void save_one_event(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc, const struct event &ev) { put_format(b, " = 0) show_integer(b, ev.gas.index, "cylinder='", "'"); put_gasmix(b, mix); @@ -365,7 +365,7 @@ static void save_one_event(struct membuffer *b, const struct dive &dive, const s static void save_events(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) { for (auto &ev: dc.events) - save_one_event(b, dive, ev); + save_one_event(b, dive, dc, ev); } static void save_tags(struct membuffer *b, const tag_list &tags) diff --git a/profile-widget/diveeventitem.cpp b/profile-widget/diveeventitem.cpp index ea6927c39..5fd2ed6e0 100644 --- a/profile-widget/diveeventitem.cpp +++ b/profile-widget/diveeventitem.cpp @@ -16,7 +16,7 @@ static int depthAtTime(const plot_info &pi, duration_t time); -DiveEventItem::DiveEventItem(const struct dive *d, int idx, const struct event &ev, struct gasmix lastgasmix, +DiveEventItem::DiveEventItem(const struct dive *d, const struct divecomputer *dc, int idx, const struct event &ev, const struct gasmix lastgasmix, divemode_t lastdivemode, const plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent) : DivePixmapItem(parent), vAxis(vAxis), @@ -28,8 +28,8 @@ DiveEventItem::DiveEventItem(const struct dive *d, int idx, const struct event & { setFlag(ItemIgnoresTransformations); - setupPixmap(lastgasmix, pixmaps); - setupToolTipString(lastgasmix); + setupPixmap(lastgasmix, lastdivemode, *dc, pixmaps); + setupToolTipString(lastgasmix, lastdivemode, *dc); recalculatePos(); } @@ -37,7 +37,7 @@ DiveEventItem::~DiveEventItem() { } -void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps) +void DiveEventItem::setupPixmap(const struct gasmix lastgasmix, divemode_t lastdivemode, const struct divecomputer &dc, const DivePixmaps &pixmaps) { event_severity severity = ev.get_severity(); if (ev.name.empty()) { @@ -51,10 +51,15 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix setPixmap(pixmaps.bookmark); setOffset(QPointF(0.0, -pixmap().height())); } else if (ev.is_gaschange()) { - struct gasmix mix = dive->get_gasmix_from_event(ev); + auto [mix, divemode] = dive->get_gasmix_from_event(ev, dc); struct icd_data icd_data; bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data); - if (mix.he.permille) { + if (divemode != lastdivemode) { + if (divemode == CCR) + setPixmap(pixmaps.onCCRLoop); + else + setPixmap(pixmaps.bailout); + } else if (mix.he.permille) { if (icd) setPixmap(pixmaps.gaschangeTrimixICD); else @@ -111,7 +116,7 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix } } -void DiveEventItem::setupToolTipString(struct gasmix lastgasmix) +void DiveEventItem::setupToolTipString(const struct gasmix lastgasmix, divemode_t lastdivemode, const struct divecomputer &dc) { // we display the event on screen - so translate QString name = gettextFromC::tr(ev.name.c_str()); @@ -120,7 +125,7 @@ void DiveEventItem::setupToolTipString(struct gasmix lastgasmix) if (ev.is_gaschange()) { struct icd_data icd_data; - struct gasmix mix = dive->get_gasmix_from_event(ev); + auto [mix, divemode] = dive->get_gasmix_from_event(ev, dc); name += ": "; name += QString::fromStdString(mix.name()); @@ -135,6 +140,9 @@ void DiveEventItem::setupToolTipString(struct gasmix lastgasmix) qPrintable(tr("ΔN₂")), icd_data.dN2 / 10.0, icd ? ">" : "<", lrint(-icd_data.dHe / 5.0) / 10.0); } + if (divemode != lastdivemode) + name += QString("\nmodechange: %1").arg(gettextFromC::tr(divemode_text_ui[divemode != OC])); + } else if (ev.name == "modechange") { name += QString(": %1").arg(gettextFromC::tr(divemode_text_ui[ev.value])); } else if (value) { diff --git a/profile-widget/diveeventitem.h b/profile-widget/diveeventitem.h index e09ae925d..93a19387c 100644 --- a/profile-widget/diveeventitem.h +++ b/profile-widget/diveeventitem.h @@ -13,7 +13,7 @@ struct plot_info; class DiveEventItem : public DivePixmapItem { Q_OBJECT public: - DiveEventItem(const struct dive *d, int idx, const struct event &ev, struct gasmix lastgasmix, + DiveEventItem(const struct dive *d, const struct divecomputer *dc, int idx, const struct event &ev, const struct gasmix lastgasmix, divemode_t lastdivemode, const struct plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent = nullptr); ~DiveEventItem(); @@ -25,8 +25,8 @@ public: int firstSecond, int lastSecond); private: - void setupToolTipString(struct gasmix lastgasmix); - void setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps); + void setupToolTipString(struct gasmix lastgasmix, divemode_t lastdivemode, const struct divecomputer &dc); + void setupPixmap(struct gasmix lastgasmix, divemode_t lastdivemode, const struct divecomputer &dc, const DivePixmaps &pixmaps); void recalculatePos(); DiveCartesianAxis *vAxis; DiveCartesianAxis *hAxis; diff --git a/profile-widget/profilescene.cpp b/profile-widget/profilescene.cpp index 07abd098c..6b2a9c582 100644 --- a/profile-widget/profilescene.cpp +++ b/profile-widget/profilescene.cpp @@ -551,8 +551,17 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM // while all other items are up there on the constructor. qDeleteAll(eventItems); eventItems.clear(); - struct gasmix lastgasmix = d->get_gasmix_at_time(*currentdc, 1_sec); - + const struct gasmix *lastgasmix; + divemode_t lastdivemode; + int cylinder_index = gasmix_loop(*d, *currentdc).next_cylinder_index().first; + if (cylinder_index == -1) { + lastgasmix = &gasmix_air; + lastdivemode = OC; + } else { + const cylinder_t *cylinder = d->get_cylinder(cylinder_index); + lastgasmix = &cylinder->gasmix; + lastdivemode = get_effective_divemode(*currentdc, *cylinder); + } for (auto [idx, event]: enumerated_range(currentdc->events)) { // if print mode is selected only draw headings, SP change, gas events or bookmark event if (printMode) { @@ -560,18 +569,23 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM !(event.name == "heading" || (event.name == "SP change" && event.time.seconds == 0) || event.is_gaschange() || + event.is_divemodechange() || event.type == SAMPLE_EVENT_BOOKMARK)) continue; } if (DiveEventItem::isInteresting(d, currentdc, event, plotInfo, firstSecond, lastSecond)) { - auto item = new DiveEventItem(d, idx, event, lastgasmix, plotInfo, + auto item = new DiveEventItem(d, currentdc, idx, event, *lastgasmix, lastdivemode, plotInfo, timeAxis, profileYAxis, animSpeed, *pixmaps); item->setZValue(2); addItem(item); eventItems.push_back(item); } - if (event.is_gaschange()) - lastgasmix = d->get_gasmix_from_event(event); + if (event.is_gaschange()) { + auto [gasmix, divemode] = d->get_gasmix_from_event(event, *currentdc); + lastgasmix = &gasmix; + lastdivemode = divemode; + } else if (event.is_divemodechange()) + lastdivemode = event.value ? CCR : OC; } QString dcText = QString::fromStdString(get_dc_nickname(currentdc)); diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index c58018969..49ff9a869 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -529,10 +529,11 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) QMenu m; if (!d) return; + + const struct divecomputer *currentdc = d->get_dc(dc); // figure out if we are ontop of the dive computer name in the profile QGraphicsItem *sceneItem = itemAt(mapFromGlobal(event->globalPos())); if (isDiveTextItem(sceneItem, profileScene->diveComputerText)) { - const struct divecomputer *currentdc = d->get_dc(dc); if (!currentdc->deviceid && dc == 0 && d->number_of_computers() == 1) // nothing to do, can't rename, delete or reorder return; @@ -561,7 +562,6 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) addGasChangeMenu(m, tr("Edit gas change"), *d, dc, item->ev.time.seconds); } else if (d && d->cylinders.size() > 1) { // if we have more than one gas, offer to switch to another one - const struct divecomputer *currentdc = d->get_dc(dc); if (seconds == 0 || (!currentdc->samples.empty() && seconds <= currentdc->samples[0].time.seconds)) addGasChangeMenu(m, tr("Set initial gas"), *d, dc, 0); else @@ -571,18 +571,21 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) m.addAction(tr("Add bookmark"), [this, seconds]() { addBookmark(seconds); }); m.addAction(tr("Split dive into two"), [this, seconds]() { splitDive(seconds); }); - divemode_loop loop(*d->get_dc(dc)); - divemode_t divemode = loop.at(seconds); - QMenu *changeMode = m.addMenu(tr("Change divemode")); - if (divemode != OC) - changeMode->addAction(gettextFromC::tr(divemode_text_ui[OC]), - [this, seconds](){ addDivemodeSwitch(seconds, OC); }); - if (divemode != CCR) - changeMode->addAction(gettextFromC::tr(divemode_text_ui[CCR]), - [this, seconds](){ addDivemodeSwitch(seconds, CCR); }); - if (divemode != PSCR) - changeMode->addAction(gettextFromC::tr(divemode_text_ui[PSCR]), - [this, seconds](){ addDivemodeSwitch(seconds, PSCR); }); + [[maybe_unused]] auto [divemode, cylinder_index, _gasmix] = get_dive_status_at(*d, *currentdc, seconds); + if (currentdc->divemode == PSCR || (currentdc->divemode == CCR && prefs.allowOcGasAsDiluent && (cylinder_index == -1 || d->get_cylinder(cylinder_index)->cylinder_use == OC_GAS))) { + QMenu *changeMode = m.addMenu(tr("Change divemode")); + if (divemode != OC) { + changeMode->addAction(gettextFromC::tr(divemode_text_ui[OC]), + [this, seconds](){ addDivemodeSwitch(seconds, OC); }); + } else { + if (currentdc->divemode == PSCR) + changeMode->addAction(gettextFromC::tr(divemode_text_ui[PSCR]), + [this, seconds](){ addDivemodeSwitch(seconds, PSCR); }); + else + changeMode->addAction(gettextFromC::tr(divemode_text_ui[CCR]), + [this, seconds](){ addDivemodeSwitch(seconds, CCR); }); + } + } if (DiveEventItem *item = dynamic_cast(sceneItem)) { m.addAction(tr("Remove event"), [this,item] { removeEvent(item); }); @@ -639,7 +642,6 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) } m2->addAction(tr("All event types"), this, &ProfileWidget2::unhideEventTypes); } - const struct divecomputer *currentdc = d->get_dc(dc); if (currentdc && std::any_of(currentdc->events.begin(), currentdc->events.end(), [] (auto &ev) { return ev.hidden; })) m.addAction(tr("Unhide individually hidden events of this dive"), this, &ProfileWidget2::unhideEvents); diff --git a/qt-models/diveplannermodel.cpp b/qt-models/diveplannermodel.cpp index 603d33557..ca6fac614 100644 --- a/qt-models/diveplannermodel.cpp +++ b/qt-models/diveplannermodel.cpp @@ -150,7 +150,8 @@ void DivePlannerPointsModel::loadFromDive(dive *dIn, int dcNrIn) int j = 0; int cylinderid = 0; - divemode_loop loop(*dc); + gasmix_loop loop_gas(*d, *dc); + divemode_loop loop_mode(*dc); for (int i = 0; i < plansamples - 1; i++) { if (dc->last_manual_time.seconds && dc->last_manual_time.seconds > 120 && lasttime.seconds >= dc->last_manual_time.seconds) break; @@ -172,7 +173,8 @@ void DivePlannerPointsModel::loadFromDive(dive *dIn, int dcNrIn) if (newtime.seconds - lastrecordedtime.seconds > 10 || cylinderid == get_cylinderid_at_time(d, dc, nexttime)) { if (newtime.seconds == lastrecordedtime.seconds) newtime.seconds += 10; - divemode_t current_divemode = loop.at(newtime.seconds - 1); + + [[maybe_unused]] auto [current_divemode, _cylinder_index, _gasmix] = get_dive_status_at(*d, *dc, newtime.seconds - 1, &loop_mode, &loop_gas); addStop(depthsum / samplecount, newtime.seconds, cylinderid, last_sp.mbar, true, current_divemode); lastrecordedtime = newtime; } @@ -182,9 +184,9 @@ void DivePlannerPointsModel::loadFromDive(dive *dIn, int dcNrIn) } } // make sure we get the last point right so the duration is correct - divemode_t current_divemode = loop.at(dc->duration.seconds); + [[maybe_unused]] auto [current_divemode, _cylinder_index, _gasmix] = get_dive_status_at(*d, *dc, dc->duration.seconds, &loop_mode, &loop_gas); if (!hasMarkedSamples && !dc->last_manual_time.seconds) - addStop(0_m, dc->duration.seconds,cylinderid, last_sp.mbar, true, current_divemode); + addStop(0_m, dc->duration.seconds, cylinderid, last_sp.mbar, true, current_divemode); preserved_until = d->duration; updateDiveProfile();