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 <github@ike.ch>
This commit is contained in:
Michael Keller 2024-09-22 23:24:25 +12:00
parent de12d3a6ea
commit 07898f277c
16 changed files with 183 additions and 121 deletions

View file

@ -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<const struct gasmix, divemode_t> 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<size_t>(index) < cylinders.size() + 1)
return get_cylinder(index)->gasmix;
return ev.gas.mix;
if (index >= 0 && static_cast<size_t>(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<divemode_t, int, const struct gasmix *> 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<int, int> 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<int, int> gasmix_loop::next_cylinder_index()
std::pair<gasmix, int> 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<int, int> gasmix_loop::cylinder_index_at(int time)
@ -2630,13 +2659,9 @@ std::pair<int, int> gasmix_loop::cylinder_index_at(int time)
std::pair<gasmix, int> 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, int> 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

View file

@ -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<const struct gasmix, divemode_t> 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<divemode_t, int, const struct gasmix *> 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 {

View file

@ -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_t, int> divemode_loop::at(int time)
{
while (ev && ev->time.seconds <= time) {
last = static_cast<divemode_t>(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

View file

@ -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);
}
}

View file

@ -73,19 +73,20 @@ class gasmix_loop {
const struct event *next_event;
int last_cylinder_index;
int last_time;
std::pair<gasmix, int> 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<int, int> next_cylinder_index(); // -1 -> end
std::pair<gasmix, int> next(); // gasmix_invalid -> end
std::pair<int, int> next_cylinder_index(); // <-1, 0> => implicit air cylinder, <-1, INT_MAX> => end
std::pair<gasmix, int> next(); // <gasmix_invalid, INT_MAX> => 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<int, int> cylinder_index_at(int time); // -1 -> end
std::pair<gasmix, int> at(int time); // gasmix_invalid -> end
std::pair<int, int> cylinder_index_at(int time); // <-1, 0> => implicit air cylinder
std::pair<gasmix, int> 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<divemode_t, int> at(int time);
};
extern const struct event *get_first_event(const struct divecomputer &dc, const std::string &name);

View file

@ -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)

View file

@ -83,7 +83,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;
}
@ -124,7 +124,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;
@ -156,7 +157,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;
@ -606,7 +608,7 @@ std::vector<decostop> 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;
@ -622,7 +624,6 @@ std::vector<decostop> 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);
@ -661,11 +662,9 @@ std::vector<decostop> 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;

View file

@ -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);

View file

@ -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) *

View file

@ -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)

View file

@ -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, " <event time='%d:%02d min'", FRACTION_TUPLE(ev.time.seconds, 60));
show_index(b, ev.type, "type='", "'");
@ -353,7 +353,7 @@ static void save_one_event(struct membuffer *b, const struct dive &dive, const s
show_index(b, ev.value, "value='", "'");
show_utf8(b, ev.name.c_str(), " name='", "'", 1);
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);
@ -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)

View file

@ -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) {

View file

@ -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;

View file

@ -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));

View file

@ -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<DiveEventItem *>(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);

View file

@ -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();