Planner: Improve Gas Handling in CCR Mode.

This has become a bit of a catch-all overhaul of a large portion of the
planner - I started out wanting to improve the CCR mode, but then as I
started pulling all the other threads that needed addressing started to
come with it.

Improve how the gas selection is handled when planning dives in CCR
mode, by making the type (OC / CCR) of segments dependent on the gas use
type that was set for the selected gas.
Add a preference to allow the user to chose to use OC gases as diluent,
in a similar fashion to the original implementation.
Hide gases that cannot be used in the currently selected dive mode in
all drop downs.
Include usage type in gas names if this is needed.
Hide columns and disable elements in the 'Dive planner points' table if
they can they can not be edited in the curently selected dive mode.
Visually identify gases and usage types that are not appropriate for the
currently selected dive mode.
Move the 'Dive mode' selection to the top of the planner view, to
accommodate the fact that this is a property of the dive and not a
planner setting.
Show a warning instead of the dive plan if the plan contains gases that
are not usable in the selected dive mode.
Fix the data entry for the setpoint in the 'Dive planner points' table.
Fix problems with enabling / disabling planner settings when switching
between dive modes.
Refactor some names to make them more appropriate for their current
usage.

One point that is still open is to hide gas usage graphs in the planner
profile if the gas isn't used for OC, as there is no way to meaningfully
interpolate such usage.

Signed-off-by: Michael Keller <github@ike.ch>
This commit is contained in:
Michael Keller 2024-05-15 17:23:39 +12:00
parent 7106c4d5f0
commit 2d8e343221
63 changed files with 678 additions and 383 deletions

3
.gitignore vendored
View file

@ -17,6 +17,9 @@ Documentation/docbook-xsl.css
Documentation/user-manual*.html
Documentation/user-manual*.pdf
Documentation/user-manual*.text
Documentation/mobile-manual*.html
Documentation/mobile-manual*.pdf
Documentation/mobile-manual*.text
Documentation/mobile-images/mobile-images
packaging/windows/subsurface.nsi
packaging/macos/Info.plist

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -3825,24 +3825,18 @@ user interface. It is explicitly used under the following conditions:
=== The _Subsurface_ dive planner screen
Like the _Subsurface_ dive log, the planner screen is divided into several sections (see image below). The *setup*
parameters for a dive are entered into the sections on the left hand and bottom side of the screen.
They are: Available Gases, Rates, Planning, Gas Options and Notes.
At the top right hand is a green *design panel* on which the profile of the dive can be
manipulated directly by dragging and clicking as explained below. This feature makes the
_Subsurface_ dive planner unique in ease of use.
At the bottom right is a text panel with a heading of _Dive Plan Details_. This is where the details of
the dive plan are provided in a way that can easily be copied to other software. This is also where
any warning messages about the dive plan are printed.
image::images/PlannerWindow1.jpg["FIGURE: Dive planner startup window",align="center"]
Like the _Subsurface_ dive log, the planner screen is divided into several sections (see image below):
- At the top left of the screen are the *dive parameters* that will be stored with the planned dive, like dive time, dive mode, and water type.
- Below this are tables that allow the user to configure the *cylinders* used on the dive, and the *depth / time and breathing gas* used for the individual parts of the planned dive.
- At the bottom of the screen are the *parameters used by the planner* to plan the dive, like ascent / descent rates, planning parameters, and gas options.
- At the top right hand is a green *design panel* on which the profile of the dive can be manipulated directly by dragging and clicking as explained below. This feature makes the _Subsurface_ dive planner unique in ease of use.
- On the bottom right is a field for the *dive plan details* - this is where the planner will show the dive plan in text form, which will also persisted to the dive notes when the planned dive is saved. This field is designe to be easily copied to other software. It also shows any warning messages about the dive plan.
image::images/PlannerWindow1.png["FIGURE: Dive planner startup window",align="center"]
=== Open circuit dives
- Towards the center bottom of the planner (circled in blue in the image above) is a dropdown list with three options. Select the appropriate one of these:
- In the top left of the planner (circled in blue in the image above) is a dropdown list with three options. Select the appropriate one of these:
** Open Circuit (the default)
** CCR
** pSCR
@ -3870,7 +3864,7 @@ image::images/PlannerWindow1.jpg["FIGURE: Dive planner startup window",align="ce
O~2~% according to the depth set. Set to ''*'' to calculate the best O~2~% for the dive maximum depth.
** MND: the gas Maximum Narcotic Depth (MND). Automatically calculated based on the Best Mix END
preference (default 30m / 98 ft). Editing this field will modify the He% according to the depth set.
Set to ''*'' to calculate the best He% for the dive maximum depth. Depending on the checkbox, oxygen
Set to ''*'' to calculate the best He% for the dive maximum depth. Depending on the checkbox, oxygen
is considered narcotic (the END is used) or not (the EAD is used).
- The profile of the planned dive can be created in two ways:
@ -3882,11 +3876,14 @@ image::images/PlannerWindow1.jpg["FIGURE: Dive planner startup window",align="ce
* The most efficient way to create a dive profile is to enter the appropriate values into the table
marked _Dive planner points_. The first line of the table represents the duration and the final
depth of the descent from the surface. Subsequent segments describe the bottom phase of the dive.
The _CC setpoint_ column is only relevant for closed circuit divers.
The ascent is usually not specified because this is what the planner is supposed to calculate.
Add additional segments to the profile by selecting the "+" icon at the top right hand of the
table. Segments entered into the _Dive planner points_ table automatically appear in the *Dive
Profile* diagram.
* If the _Dive mode_ of the planned dive is changed after gases and segments have been added, it can happen that the _Use_ of a gasmix or the _Used gas_ selected for a segment are not appropriate for the selected _Dive mode_. In this case _Subsurface_ will highlight the problematic selection with a red background to help the user identify and correct the issue. The dive plan will not be calculated until all issues are resolved.
image::images/Planner_issues.png["FIGURE: Dive planner: Issue display",align="center"]
==== Recreational dives
@ -3963,7 +3960,7 @@ the nitrogen load incurred during previous dives.
Below is an image of a dive plan for a recreational dive at 30 meters with gradient factors of 100. Because the no-deco limit (NDL) is 22
minutes, there remains a significant amount of air in the cylinder at the end of the dive.
image::images/Planner_OC_rec1.jpg["FIGURE: A recreational dive plan: setup",align="center"]
image::images/Planner_OC_rec1.png["FIGURE: A recreational dive plan: setup",align="center"]
The dive profile in the planner shows the maximum dive time within no-deco limits using the
Bühlmann ZH-L16 algorithm and the gas and depth settings specified as described above. The _Subsurface_ planner
@ -3976,7 +3973,7 @@ it means that recreational dive limits are exceeded and either the dive duration
Below is the same dive plan as above, but with a safety stop and reduced gradient factors for
a larger safety margin.
image::images/Planner_OC_rec2.jpg["FIGURE: A recreational dive plan: gradient factors setup",align="center"]
image::images/Planner_OC_rec2.png["FIGURE: A recreational dive plan: gradient factors setup",align="center"]
==== Non-recreational open circuit dives, including decompression
@ -4093,12 +4090,16 @@ _Type_ select the appropriate cylinder size by using the dropdown list that appe
double-clicking a cell in this column. By default, a large number of sizes are listed,
and a new cylinder size can be created by typing this into the text box. The cylinder size, start pressure
and default switch depths are initialized automatically. Specify the gas composition
(e.g. helium and oxygen content). A non-zero value in the "CC setpoint" column of the table of dive planner points
indicates a valid setpoint for oxygen partial pressure and that the segment
is dived using a closed circuit rebreather (CCR). If the last manually entered
segment is a CCR segment, the decompression phase is computed assuming the diver
(e.g. helium and oxygen content) and - in the case of planning a CCR dive, the intended use as diluent or OC bailout gas.
When planning CCR dives, a segment is calculated as a closed circuit segment if the gas that is used is a _diluent_, and as an open circuit segment if the gas is an _OC-gas_. If the user enables the _Allow open circuit gas to be used as bailout_ option in the _Tech setup_ preferences, then an additional column will be shown allowing the user to select the _Dive mode_ for each segment dived on an _OC-gas_.
image::images/Planner_OC_gas_for_CCR.png["FIGURE: Planning a dive: OC as CCR",align="center"]
For CCR dives, an additional column is shown in the _Dive planner points_ table, allowing the user to specify the _Setpoint_ for each closed circuit segment.
If the last manually entered
segment is a closed circuit segment, the decompression phase is computed assuming the diver
uses a CCR with the specified set-point. If the last segment (however
short) is on open circuit (OC, indicated by a zero set-point) the
short) is on open circuit the
decompression is computed in OC mode and the planner only considers gas
changes in OC mode.
@ -4112,16 +4113,19 @@ you wish to use this gas during the very start of the dive (the other gas is not
Upon pressing Enter on the keyboard, that segment is moved to the top of that table and the plan is adjusted
automatically to take into account this new segment of the dive plan (image B below).
image::images/planner1.jpg["FIGURE: Planning a dive: segments",align="center"]
*A:*
image::images/planner1.png["FIGURE: Planning a dive: segments 1/2",align="center"]
*B:*
image::images/planner2.png["FIGURE: Planning a dive: segments 2/2",align="center"]
Below is an example of a dive plan to 55m using Tx20/30 and the Bühlmann algorithm,
followed by an ascent using EAN50 and using the settings as described above.
image::images/Planner_OC_deco.jpg["FIGURE: Planning a dive: setup",align="center"]
image::images/Planner_OC_deco.png["FIGURE: Planning a dive: setup",align="center"]
Once the above steps have been completed, save by clicking the _Save_ button
towards the top middle of the planner. The saved dive plan will appear
in the *Dive List* panel of _Subsurface_.
in the _Dive List_ panel of _Subsurface_.
*The dive plan details*
@ -4238,7 +4242,7 @@ are specified for pSCR dives. Below is a dive plan for a pSCR dive. The dive is
to that of the CCR dive below, but note the longer ascent duration due to the lower oxygen
in the loop due to the oxygen drop across the mouthpiece of the pSCR equipment.
image::images/Planner_pSCR.jpg["FIGURE: Planning a pSCR dive: setup",align="center"]
image::images/Planner_pSCR.png["FIGURE: Planning a pSCR dive: setup",align="center"]
==== Planning for pSCR bailout
@ -4283,7 +4287,7 @@ columns, as in the cave example, above. See the example of bailout for a CCR div
=== Planning CCR dives
To plan a dive using a closed circuit rebreather, select the _CCR_ option in the dropdown
list, circled in blue in the image below.
list.
*Available gases*: In the _Available gases_ table, enter the cylinder information for the
diluent cylinder and for any bail-out cylinders. Do NOT enter the information for the oxygen
@ -4292,14 +4296,9 @@ cylinder since it is implied when the _CCR_ dropdown selection is made.
*Entering setpoints*: Specify a default setpoint in the Preferences tab, by selecting _File -> Preferences -> Tech setup_ from
the main menu. All user-entered segments in the _Dive planner points_ table
use the default setpoint value. Then, different setpoints can be specified for dive segments
in the _Dive planner points_ table. A zero setpoint
means the diver bails out to open circuit mode for that segment. Decompression is always calculated
using the setpoint of the last manually entered segment. So, to plan a bail out ascent for a
CCR dive, add a one-minute dive segment to the end with a setpoint value of 0. The decompression
algorithm does not switch deco-gases automatically while in CCR mode (i.e. when a positive setpoint is specified) but
this is calculated for bail out ascents.
in the _Dive planner points_ table.
If you want the setpoint to change during the planned ascent at a specified depth, you can do this
If you want the setpoint to change during the planned ascent at a specified depth, you can do this
using a "fake" cylinder that you add to the gas list: Give that cylinder a name of "SP 1.4" (or use
a different number) and set the "Deco switch value" to the depth at which you want to set the new
setpoint. This will make the planner stop at the specified depth and use the new setpoint from
@ -4307,36 +4306,30 @@ there on.
The dive profile for a CCR dive may look something like the image below.
image::images/Planner_CCR.jpg["FIGURE: Planning a CCR dive: setup",align="center"]
image::images/Planner_CCR.png["FIGURE: Planning a CCR dive: setup",align="center"]
Note that, in the _Dive plan details_, the gas consumption for a CCR segment is not calculated,
so gas consumptions of 0 liters are the norm.
==== Planning for CCR bailout
[icon="images/CCR_b1.jpg"]
[NOTE]
image::images/CCR_b1.png["FIGURE: Planning a CCR dive: closed circuit deco",align="center"]
It is often necessary to plan for a worst-case bailout event in order to ensure sufficient bailout gas to reach the
surface, taking into account decompression. This is done by 1) checking the _Bailout_ checkbox of the dive planner
(bailout will be calculated starting at the last segment of the dive specified in the _Dive planner points_ table);
2) defining a 1-minute segment at the end of the bottom part
of the dive, as in the image on the left where a CCR dive to 40m for 21 minutes is planned;
3) changing to an OC-gas during any segment in the _Dive planner points_ table.
surface, taking into account decompression. This is done by checking the _Rebreather: Bailout / Deco on OC_ checkbox of the dive planner
(bailout will be calculated starting at the last segment of the dive specified in the _Dive planner points_ table):
image::images/CCR_b2.png["FIGURE: Planning a CCR dive: open circuit bailout",align="center"]
[icon="images/CCR_b2.jpg"]
[NOTE]
In the _Dive planner points
table_, change the _Dive mode_ of this 1-minute segment to _OC_. This signifies bailout. In this case there is bailout to
the existing diluent cylinder (assuming this cylinder has sufficient gas). The appropriate pO~2~ and cylinder pressure
graphs are shown in the dive profile, as in the image on the left. Note that the setpoint drops to zero after bailout, since
this value does not apply to breathed bailout gas.
In the _Available gases_
table, change the _Use_ of the gas used for the last segment to _OC-gas_. This signifies bailout.
The appropriate pO~2~ and cylinder pressure
graphs are shown in the dive profile:
image::images/CCR_b3.png["FIGURE: Planning a CCR dive: open circuit bailout by cylinder selection",align="center"]
[icon="images/CCR_b3.jpg"]
[NOTE]
In order to plan for bailout to an external bailout cylinder, change the _Used gas_ for the 1-minute segment to the
appropriate cylinder, as in the example on the left. Note that the cylinder change as well as the bailout are indicated with
appropriate cylinder, as in the example above. Note that the cylinder change as well as the bailout are indicated with
overlapping icons.
The volumes of gases required for bailout can be found at the bottom of the *Dive plan details* panel.

View file

@ -1260,7 +1260,7 @@ EditCylinder::EditCylinder(int index, cylinder_t cylIn, EditCylinderType typeIn,
setText(Command::Base::tr("Edit cylinder (%n dive(s))", "", dives.size()));
// The base class copied the cylinders for us, let's edit them
for (int i = 0; i < (int)indexes.size(); ++i) {
for (int i = 0; i < (int)cyl.size(); ++i) {
switch (type) {
case EditCylinderType::TYPE:
cyl[i].type = cylIn.type;

View file

@ -1625,6 +1625,35 @@ static bool cylinder_in_use(const struct dive *dive, int idx)
return cylinder_has_data(dive->cylinders[idx]);
}
bool is_cylinder_use_appropriate(const struct divecomputer &dc, const cylinder_t &cyl, bool allowNonUsable)
{
switch (cyl.cylinder_use) {
case OC:
if (dc.divemode == FREEDIVE)
return false;
break;
case OXYGEN:
if (!allowNonUsable)
return false;
case DILUENT:
if (dc.divemode != CCR)
return false;
break;
case NOT_USED:
if (!allowNonUsable)
return false;
break;
default:
return false;
}
return true;
}
/*
* Merging cylinder information is non-trivial, because the two dive computers
* may have different ideas of what the different cylinder indexing is.
@ -2451,7 +2480,7 @@ int dive::mbar_to_depth(int mbar) const
if (!surface_pressure.mbar)
surface_pressure.mbar = SURFACE_PRESSURE;
return rel_mbar_to_depth(mbar - surface_pressure.mbar);
}

View file

@ -146,6 +146,7 @@ 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);
/* Data stored when copying a dive */
struct dive_paste_data {

View file

@ -35,7 +35,7 @@ struct divecomputer {
pressure_t surface_pressure;
enum divemode_t divemode = OC; // dive computer type: OC(default) or CCR
uint8_t no_o2sensors = 0; // rebreathers: number of O2 sensors used
int salinity = 0; // kg per 10000 l
int salinity = 0; // kg per 10000 l
std::string model, serial, fw_version;
uint32_t deviceid = 0, diveid = 0;
// Note: ve store samples, events and extra_data in std::vector<>s.

View file

@ -4,4 +4,6 @@
enum divemode_t {OC, CCR, PSCR, FREEDIVE, NUM_DIVEMODE, UNDEF_COMP_TYPE}; // Flags (Open-circuit and Closed-circuit-rebreather) for setting dive computer type
#define IS_REBREATHER_MODE(divemode) ((divemode) == CCR || (divemode) == PSCR)
#endif

View file

@ -380,14 +380,17 @@ static int setpoint_change(struct dive *dive, int cylinderid)
}
}
static std::vector<gaschanges> analyze_gaslist(struct diveplan *diveplan, struct dive *dive, int depth, int *asc_cylinder, bool ccr)
static std::vector<gaschanges> analyze_gaslist(struct diveplan *diveplan, struct dive *dive, int dcNr, int depth, int *asc_cylinder, bool ccr, bool &inappropriate_cylinder_use)
{
size_t nr = 0;
std::vector<gaschanges> gaschanges;
struct divedatapoint *dp = diveplan->dp;
struct divedatapoint *best_ascent_dp = NULL;
bool total_time_zero = true;
const divecomputer *dc = dive->get_dc(dcNr);
while (dp) {
inappropriate_cylinder_use = inappropriate_cylinder_use || !is_cylinder_use_appropriate(*dc, *dive->get_cylinder(dp->cylinderid), false);
if (dp->time == 0 && total_time_zero && (ccr == (bool) setpoint_change(dive, dp->cylinderid))) {
if (dp->depth.mm <= depth) {
int i = 0;
@ -661,7 +664,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
bool o2break_next = false;
int break_cylinder = -1, breakfrom_cylinder = 0;
bool last_segment_min_switch = false;
bool error = false;
planner_error_t error = PLAN_OK;
bool decodive = false;
int first_stop_depth = 0;
int laststoptime = timestep;
@ -744,7 +747,11 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
/* Find the gases available for deco */
std::vector<gaschanges> gaschanges = analyze_gaslist(diveplan, dive, depth, &best_first_ascend_cylinder, divemode == CCR && !prefs.dobailout);
bool inappropriate_cylinder_use = false;
std::vector<gaschanges> gaschanges = analyze_gaslist(diveplan, dive, dcNr, depth, &best_first_ascend_cylinder, divemode == CCR && !prefs.dobailout, inappropriate_cylinder_use);
if (inappropriate_cylinder_use) {
error = PLAN_ERROR_INAPPROPRIATE_GAS;
}
/* Find the first potential decostopdepth above current depth */
for (stopidx = 0; stopidx < decostoplevelcount; stopidx++)
@ -880,7 +887,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
current_cylinder = 0;
}
reset_regression(ds);
while (1) {
while (error == PLAN_OK) {
/* We will break out when we hit the surface */
do {
/* Ascend to next stop depth */
@ -1005,7 +1012,8 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
laststoptime = new_clock - clock;
/* Finish infinite deco */
if (laststoptime >= 48 * 3600 && depth >= 6000) {
error = true;
error = PLAN_ERROR_TIMEOUT;
break;
}
@ -1070,7 +1078,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
* if the ascent rate is slower, which is completely nonsensical.
* Assume final ascent takes 20s, which is the time taken to ascend at 9m/min from 3m */
ds->deco_time = clock - bottom_time - (M_OR_FT(3,10) * ( prefs.last_stop ? 2 : 1)) / last_ascend_rate + 20;
} while (!is_final_plan);
} while (!is_final_plan && error == PLAN_OK);
decostoptable[decostopcounter].depth = 0;
plan_add_segment(diveplan, clock - previous_point_time, 0, current_cylinder, po2, false, divemode);
@ -1112,24 +1120,31 @@ static int get_decimals(const char *begin, const char **endp, const unsigned dec
return -1;
/* Fraction? We only look at the first digit */
if (*end == '.') {
unsigned fraction = 0;
for (unsigned i = 0; i < decimals; i++) {
value *= 10;
if (*end == '.')
end++;
unsigned fraction = 0;
for (unsigned i = 0; i < decimals; i++) {
value *= 10;
unsigned digit = 0;
if (isdigit(*end)) {
digit = *end - '0';
end++;
if (!isdigit(*end))
return -1;
fraction = 10 * fraction + (*end - '0');
} else if (*end != '\0') {
return -1;
}
value += fraction;
do {
end++;
} while (isdigit(*end));
fraction = 10 * fraction + digit;
}
value += fraction;
do {
end++;
} while (isdigit(*end));
*endp = end;
return value;
}
@ -1209,5 +1224,9 @@ int validate_po2(const char *text, int *mbar_po2)
return 0;
*mbar_po2 = po2 * 10;
if (*mbar_po2 < 160)
*mbar_po2 = 160;
return 1;
}

View file

@ -34,11 +34,17 @@ struct diveplan {
struct deco_state_cache;
typedef enum {
PLAN_OK,
PLAN_ERROR_TIMEOUT,
PLAN_ERROR_INAPPROPRIATE_GAS,
} planner_error_t;
extern int validate_gas(const char *text, struct gasmix *gas);
extern int validate_po2(const char *text, int *mbar_po2);
extern int get_cylinderid_at_time(struct dive *dive, struct divecomputer *dc, duration_t time);
extern bool diveplan_empty(struct diveplan *diveplan);
extern void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, bool error);
extern void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, planner_error_t error);
extern const char *get_planner_disclaimer();
extern void free_dps(struct diveplan *diveplan);

View file

@ -96,7 +96,7 @@ extern std::string get_planner_disclaimer_formatted()
return format_string_std(get_planner_disclaimer(), deco);
}
void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, bool error)
void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, planner_error_t error)
{
std::string buf;
std::string icdbuf;
@ -122,12 +122,31 @@ void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_d
if (!dp)
return;
if (error) {
if (error != PLAN_OK) {
const char *message;
switch (error) {
case PLAN_ERROR_TIMEOUT:
message = translate("gettextFromC", "Decompression calculation aborted due to excessive time");
break;
case PLAN_ERROR_INAPPROPRIATE_GAS:
message = translate("gettextFromC", "One or more tanks with a tank use type inappropriate for the selected dive mode are included in the dive plan. "
"Please change them to appropriate tanks to enable the generation of a dive plan.");
break;
default:
message = translate("gettextFromC", "An error occurred during dive plan generation");
break;
}
buf += format_string_std("<span style='color: red;'>%s </span> %s<br/>",
translate("gettextFromC", "Warning:"),
translate("gettextFromC", "Decompression calculation aborted due to excessive time"));
translate("gettextFromC", "Warning:"), message);
// TODO: avoid copy
dive->notes = buf;
dive->notes = strdup(buf.c_str());
return;
}

View file

@ -93,6 +93,7 @@ preferences::preferences() :
vpmb_conservatism(3),
zoomed_plot(false),
infobox(true),
allowOcGasAsDiluent(false),
coordinates_traditional(true),
unit_system(METRIC),
units(SI_UNITS)

View file

@ -201,6 +201,7 @@ struct preferences {
int vpmb_conservatism;
bool zoomed_plot;
bool infobox;
bool allowOcGasAsDiluent;
// ********** Units **********
bool coordinates_traditional;

View file

@ -1151,19 +1151,51 @@ QString get_gas_string(struct gasmix gas)
return result;
}
QStringList get_dive_gas_list(const struct dive *d)
QString get_dive_gas(const struct dive *d, int dcNr, int cylinderId)
{
QStringList list;
for (auto [i, cyl]: enumerated_range(d->cylinders)) {
/* Check if we have the same gasmix two or more times
* If yes return more verbose string */
int same_gas = same_gasmix_cylinder(cyl, i, d, true);
if (same_gas == -1)
list.push_back(get_gas_string(cyl.gasmix));
else
list.push_back(get_gas_string(cyl.gasmix) + QStringLiteral(" (%1 %2 ").arg(gettextFromC::tr("cyl.")).arg(i + 1) +
QString::fromStdString(cyl.type.description) + ")");
const cylinder_t *cyl = d->get_cylinder(cylinderId);
const divecomputer *dc = d->get_dc(dcNr);
bool showUse = (dc->divemode == CCR) | !is_cylinder_use_appropriate(*dc, *cyl, false);
QString gasType = get_gas_string(cyl->gasmix);
QString gasName;
/* Check if we have the same gasmix two or more times
* If yes return more verbose string */
int same_gas = same_gasmix_cylinder(*cyl, cylinderId, d, true);
if (same_gas != -1) {
gasType += QString(" (%1 %2").arg(gettextFromC::tr("cyl.")).arg(cylinderId + 1);
gasName = QString::fromStdString(d->get_cylinder(cylinderId)->type.description);
}
if (showUse) {
if (gasName.isNull())
gasType += " (";
else
gasType += ", ";
gasType += gettextFromC::tr(cylinderuse_text[d->get_cylinder(cylinderId)->cylinder_use]);
}
if (!gasName.isNull())
gasType += QString(", %1").arg(gasName);
if (!gasName.isNull() || showUse)
gasType += ")";
return gasType;
}
std::vector<std::pair<int, QString>> get_dive_gas_list(const struct dive *d, int dcNr, bool showOnlyAppropriate)
{
const divecomputer *dc = d->get_dc(dcNr);
std::vector<std::pair<int, QString>> list;
for (unsigned int i = 0; i < d->cylinders.size(); i++) {
if (showOnlyAppropriate && !is_cylinder_use_appropriate(*dc, *d->get_cylinder(i), false))
continue;
list.push_back(std::pair(i, get_dive_gas(d, dcNr, i)));
}
return list;
}

View file

@ -28,7 +28,8 @@ enum watertypes {FRESHWATER, BRACKISHWATER, EN13319WATER, SALTWATER, DC_WATERTYP
QString distance_string(int distanceInMeters);
bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out = 0);
QString get_gas_string(struct gasmix gas);
QStringList get_dive_gas_list(const struct dive *d);
QString get_dive_gas(const struct dive *d, int dcNr, int cylinderId);
std::vector<std::pair<int, QString>> get_dive_gas_list(const struct dive *d, int dcNr, bool showOnlyAppropriate);
QStringList stringToList(const QString &s);
void read_hashes();
void write_hashes();

View file

@ -14,6 +14,7 @@ qPrefTechnicalDetails *qPrefTechnicalDetails::instance()
void qPrefTechnicalDetails::loadSync(bool doSync)
{
disk_allowOcGasAsDiluent(doSync);
disk_calcalltissues(doSync);
disk_calcceiling(doSync);
disk_calcceiling3m(doSync);
@ -100,6 +101,8 @@ void qPrefTechnicalDetails::disk_gflow(bool doSync)
}
}
HANDLE_PREFERENCE_BOOL(TechnicalDetails, "allowOcGasAsDiluent", allowOcGasAsDiluent);
HANDLE_PREFERENCE_BOOL(TechnicalDetails, "gf_low_at_maxdepth", gf_low_at_maxdepth);
HANDLE_PREFERENCE_BOOL(TechnicalDetails, "InfoBox", infobox);

View file

@ -7,6 +7,7 @@
class qPrefTechnicalDetails : public QObject {
Q_OBJECT
Q_PROPERTY(bool allowOcGasAsDiluent READ allowOcGasAsDiluent WRITE set_allowOcGasAsDiluent NOTIFY allowOcGasAsDiluentChanged)
Q_PROPERTY(bool calcalltissues READ calcalltissues WRITE set_calcalltissues NOTIFY calcalltissuesChanged)
Q_PROPERTY(bool calcceiling READ calcceiling WRITE set_calcceiling NOTIFY calcceilingChanged)
Q_PROPERTY(bool calcceiling3m READ calcceiling3m WRITE set_calcceiling3m NOTIFY calcceiling3mChanged)
@ -44,6 +45,7 @@ public:
static void sync() { loadSync(true); }
public:
static bool allowOcGasAsDiluent() { return prefs.allowOcGasAsDiluent; }
static bool calcalltissues() { return prefs.calcalltissues; }
static bool calcceiling() { return prefs.calcceiling; }
static bool calcceiling3m() { return prefs.calcceiling3m; }
@ -73,6 +75,7 @@ public:
static bool infobox() { return prefs.infobox; }
public slots:
static void set_allowOcGasAsDiluent(bool value);
static void set_calcalltissues(bool value);
static void set_calcceiling(bool value);
static void set_calcceiling3m(bool value);
@ -102,6 +105,7 @@ public slots:
static void set_infobox(bool value);
signals:
void allowOcGasAsDiluentChanged(bool value);
void calcalltissuesChanged(bool value);
void calcceilingChanged(bool value);
void calcceiling3mChanged(bool value);
@ -133,6 +137,7 @@ signals:
private:
qPrefTechnicalDetails() {}
static void disk_allowOcGasAsDiluent(bool doSync);
static void disk_calcalltissues(bool doSync);
static void disk_calcceiling(bool doSync);
static void disk_calcceiling3m(bool doSync);

View file

@ -23,27 +23,32 @@
#include <QBuffer>
#endif
DivePlannerWidget::DivePlannerWidget(dive &planned_dive, int dcNr, PlannerWidgets *parent)
DivePlannerWidget::DivePlannerWidget(const dive &planned_dive, int &dcNr, PlannerWidgets *parent)
{
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
CylindersModel *cylinders = DivePlannerPointsModel::instance()->cylindersModel();
ui.setupUi(this);
// should be the same order as in dive_comp_type!
QStringList divemodes = QStringList();
for (int i = 0; i < FREEDIVE; i++)
divemodes.append(gettextFromC::tr(divemode_text_ui[i]));
ui.divemode->insertItems(0, divemodes);
ui.tableWidget->setTitle(tr("Dive planner points"));
ui.tableWidget->setBtnToolTip(tr("Add dive data point"));
ui.tableWidget->setModel(plannerModel);
connect(ui.tableWidget, &TableView::itemClicked, plannerModel, &DivePlannerPointsModel::remove);
ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::GAS, new AirTypesDelegate(planned_dive, this));
ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::DIVEMODE, new DiveTypesDelegate(this));
connect(ui.tableWidget, &TableView::addButtonClicked, plannerModel, &DivePlannerPointsModel::addDefaultStop);
ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::GAS, new GasTypesDelegate(planned_dive, dcNr, this));
ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::DIVEMODE, new DiveTypesDelegate(planned_dive, dcNr, this));
ui.cylinderTableWidget->setTitle(tr("Available gases"));
ui.cylinderTableWidget->setBtnToolTip(tr("Add cylinder"));
ui.cylinderTableWidget->setModel(cylinders);
connect(ui.cylinderTableWidget, &TableView::itemClicked, cylinders, &CylindersModel::remove);
ui.waterType->setItemData(0, FRESHWATER_SALINITY);
ui.waterType->setItemData(1, SEAWATER_SALINITY);
ui.waterType->setItemData(2, EN13319_SALINITY);
waterTypeUpdateTexts();
QTableView *view = ui.cylinderTableWidget->view();
connect(ui.cylinderTableWidget, &TableView::itemClicked, cylinders, &CylindersModel::remove);
view->setColumnHidden(CylindersModel::START, true);
view->setColumnHidden(CylindersModel::END, true);
view->setColumnHidden(CylindersModel::DEPTH, false);
@ -52,18 +57,22 @@ DivePlannerWidget::DivePlannerWidget(dive &planned_dive, int dcNr, PlannerWidget
view->setColumnHidden(CylindersModel::SENSORS, true);
view->setItemDelegateForColumn(CylindersModel::TYPE, new TankInfoDelegate(this));
auto tankUseDelegate = new TankUseDelegate(this);
tankUseDelegate->setCurrentDC(planned_dive.get_dc(dcNr));
tankUseDelegate->setDiveDc(planned_dive, dcNr);
view->setItemDelegateForColumn(CylindersModel::USE, tankUseDelegate);
connect(ui.cylinderTableWidget, &TableView::addButtonClicked, plannerModel, &DivePlannerPointsModel::addCylinder_clicked);
connect(ui.tableWidget, &TableView::addButtonClicked, plannerModel, &DivePlannerPointsModel::addDefaultStop);
connect(cylinders, &CylindersModel::dataChanged, plannerModel, &DivePlannerPointsModel::emitDataChanged);
connect(cylinders, &CylindersModel::dataChanged, plannerModel, &DivePlannerPointsModel::cylinderModelEdited);
connect(cylinders, &CylindersModel::rowsInserted, plannerModel, &DivePlannerPointsModel::cylinderModelEdited);
connect(cylinders, &CylindersModel::rowsRemoved, plannerModel, &DivePlannerPointsModel::cylinderModelEdited);
ui.tableWidget->setBtnToolTip(tr("Add dive data point"));
ui.waterType->setItemData(0, FRESHWATER_SALINITY);
ui.waterType->setItemData(1, SEAWATER_SALINITY);
ui.waterType->setItemData(2, EN13319_SALINITY);
waterTypeUpdateTexts();
connect(ui.startTime, &QDateEdit::timeChanged, plannerModel, &DivePlannerPointsModel::setStartTime);
connect(ui.dateEdit, &QDateEdit::dateChanged, plannerModel, &DivePlannerPointsModel::setStartDate);
connect(ui.divemode, QOverload<int>::of(&QComboBox::currentIndexChanged), parent, &PlannerWidgets::setDiveMode);
connect(ui.ATMPressure, QOverload<int>::of(&QSpinBox::valueChanged), this, &DivePlannerWidget::atmPressureChanged);
connect(ui.atmHeight, QOverload<int>::of(&QSpinBox::valueChanged), this, &DivePlannerWidget::heightChanged);
connect(ui.waterType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DivePlannerWidget::waterTypeChanged);
@ -77,14 +86,14 @@ DivePlannerWidget::DivePlannerWidget(dive &planned_dive, int dcNr, PlannerWidget
QShortcut *closeKey = new QShortcut(QKeySequence(Qt::Key_Escape), this);
connect(closeKey, &QShortcut::activated, plannerModel, &DivePlannerPointsModel::cancelPlan);
// This makes shure the spinbox gets a setMinimum(0) on it so we can't have negative time or depth.
// This makes sure the spinbox gets a setMinimum(0) on it so we can't have negative time or depth.
// Limit segments to a depth of 1000 m/3300 ft and a duration of 100 h. Setting the limit for
// the depth will be done in settingChanged() since this depends on the chosen units.
ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::RUNTIME, new SpinBoxDelegate(0, INT_MAX, 1, this));
ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::DURATION, new SpinBoxDelegate(0, 6000, 1, this));
ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::CCSETPOINT, new DoubleSpinBoxDelegate(0, 2, 0.01, this));
connect(&diveListNotifier, &DiveListNotifier::settingsChanged, this, &DivePlannerWidget::settingsChanged);
connect(&diveListNotifier, &DiveListNotifier::settingsChanged, parent, &PlannerWidgets::settingsChanged);
/* set defaults. */
ui.ATMPressure->setValue(1013);
@ -116,11 +125,6 @@ void DivePlannerWidget::setSurfacePressure(int surface_pressure)
ui.ATMPressure->setValue(surface_pressure);
}
void PlannerSettingsWidget::setDiveMode(int mode)
{
ui.rebreathermode->setCurrentIndex(mode);
}
void DivePlannerWidget::setSalinity(int salinity)
{
bool mapped = false;
@ -212,7 +216,23 @@ void DivePlannerWidget::customSalinityChanged(double density)
}
}
void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreathermode)
void DivePlannerWidget::setDiveMode(int mode)
{
ui.divemode->setCurrentIndex(mode);
}
void DivePlannerWidget::setColumnVisibility(int mode)
{
ui.tableWidget->view()->setColumnHidden(DivePlannerPointsModel::CCSETPOINT, mode != CCR);
ui.tableWidget->view()->setColumnHidden(DivePlannerPointsModel::DIVEMODE, mode == OC || (mode == CCR && !prefs.allowOcGasAsDiluent));
// This is needed as Qt sets the column width to 0 when hiding a column
ui.tableWidget->view()->setVisible(false); // This will cause the resize to include rows outside the current viewport
ui.tableWidget->view()->resizeColumnsToContents();
ui.tableWidget->view()->setVisible(true);
}
void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t divemode)
{
if (mode == RECREATIONAL) {
ui.label_gflow->setDisabled(false);
@ -265,7 +285,7 @@ void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreatherm
ui.backgasBreaks->setChecked(false);
ui.backgasBreaks->blockSignals(false);
}
ui.bailout->setDisabled(!(rebreathermode == CCR || rebreathermode == PSCR));
ui.bailout->setDisabled(!IS_REBREATHER_MODE(divemode));
ui.bottompo2->setDisabled(false);
ui.decopo2->setDisabled(false);
ui.safetystop->setDisabled(true);
@ -277,7 +297,7 @@ void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreatherm
ui.min_switch_duration->setDisabled(false);
ui.surface_segment->setDisabled(false);
ui.label_min_switch_duration->setDisabled(false);
ui.sacfactor->setDisabled(false);
ui.sacfactor->setDisabled(IS_REBREATHER_MODE(divemode));
ui.problemsolvingtime->setDisabled(false);
ui.sacfactor->setValue(PlannerShared::sacfactor());
ui.problemsolvingtime->setValue(prefs.problemsolvingtime);
@ -299,7 +319,7 @@ void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreatherm
ui.backgasBreaks->setChecked(false);
ui.backgasBreaks->blockSignals(false);
}
ui.bailout->setDisabled(!(rebreathermode == CCR || rebreathermode == PSCR));
ui.bailout->setDisabled(!IS_REBREATHER_MODE(divemode));
ui.bottompo2->setDisabled(false);
ui.decopo2->setDisabled(false);
ui.safetystop->setDisabled(true);
@ -311,7 +331,7 @@ void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreatherm
ui.min_switch_duration->setDisabled(false);
ui.surface_segment->setDisabled(false);
ui.label_min_switch_duration->setDisabled(false);
ui.sacfactor->setDisabled(false);
ui.sacfactor->setDisabled(IS_REBREATHER_MODE(divemode));
ui.problemsolvingtime->setDisabled(false);
ui.sacfactor->setValue(PlannerShared::sacfactor());
ui.problemsolvingtime->setValue(prefs.problemsolvingtime);
@ -359,7 +379,6 @@ PlannerSettingsWidget::PlannerSettingsWidget(PlannerWidgets *parent)
ui.decopo2->setValue(PlannerShared::decopo2());
ui.backgasBreaks->setChecked(prefs.doo2breaks);
PlannerShared::set_dobailout(false);
setBailoutVisibility(false);
ui.o2narcotic->setChecked(prefs.o2narcotic);
ui.drop_stone_mode->setChecked(prefs.drop_stone_mode);
ui.switch_at_req_stop->setChecked(prefs.switch_at_req_stop);
@ -370,12 +389,6 @@ PlannerSettingsWidget::PlannerSettingsWidget(PlannerWidgets *parent)
ui.vpmb_deco->setChecked(prefs.planner_deco_mode == VPMB);
disableDecoElements((int) prefs.planner_deco_mode, OC);
// should be the same order as in dive_comp_type!
QStringList rebreather_modes = QStringList();
for (int i = 0; i < FREEDIVE; i++)
rebreather_modes.append(gettextFromC::tr(divemode_text_ui[i]));
ui.rebreathermode->insertItems(0, rebreather_modes);
connect(ui.recreational_deco, &QAbstractButton::clicked, [] { PlannerShared::set_planner_deco_mode(RECREATIONAL); });
connect(ui.buehlmann_deco, &QAbstractButton::clicked, [] { PlannerShared::set_planner_deco_mode(BUEHLMANN); });
connect(ui.vpmb_deco, &QAbstractButton::clicked, [] { PlannerShared::set_planner_deco_mode(VPMB); });
@ -404,12 +417,10 @@ PlannerSettingsWidget::PlannerSettingsWidget(PlannerWidgets *parent)
connect(ui.switch_at_req_stop, &QAbstractButton::toggled, plannerModel, &DivePlannerPointsModel::setSwitchAtReqStop);
connect(ui.min_switch_duration, QOverload<int>::of(&QSpinBox::valueChanged), &PlannerShared::set_min_switch_duration);
connect(ui.surface_segment, QOverload<int>::of(&QSpinBox::valueChanged), &PlannerShared::set_surface_segment);
connect(ui.rebreathermode, QOverload<int>::of(&QComboBox::currentIndexChanged), plannerModel, &DivePlannerPointsModel::setRebreatherMode);
connect(ui.rebreathermode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PlannerSettingsWidget::setBailoutVisibility);
connect(ui.recreational_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(RECREATIONAL, parent->getRebreatherMode()); });
connect(ui.buehlmann_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(BUEHLMANN, parent->getRebreatherMode()); });
connect(ui.vpmb_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(VPMB, parent->getRebreatherMode()); });
connect(ui.recreational_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(RECREATIONAL, parent->getDiveMode()); });
connect(ui.buehlmann_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(BUEHLMANN, parent->getDiveMode()); });
connect(ui.vpmb_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(VPMB, parent->getDiveMode()); });
connect(ui.sacfactor, QOverload<double>::of(&QDoubleSpinBox::valueChanged), &PlannerShared::set_sacfactor);
connect(ui.problemsolvingtime, QOverload<int>::of(&QSpinBox::valueChanged), plannerModel, &DivePlannerPointsModel::setProblemSolvingTime);
@ -519,8 +530,9 @@ void PlannerSettingsWidget::setBackgasBreaks(bool dobreaks)
void PlannerSettingsWidget::setBailoutVisibility(int mode)
{
ui.bailout->setDisabled(!(mode == CCR || mode == PSCR));
ui.sacFactor->setDisabled(mode == CCR);
bool isRebreatherMode = IS_REBREATHER_MODE(mode);
ui.bailout->setDisabled(!isRebreatherMode);
ui.sacfactor->setDisabled(isRebreatherMode);
}
PlannerDetails::PlannerDetails(QWidget *parent) : QWidget(parent)
@ -561,7 +573,7 @@ int PlannerWidgets::getDcNr()
return dcNr;
}
divemode_t PlannerWidgets::getRebreatherMode() const
divemode_t PlannerWidgets::getDiveMode() const
{
return planned_dive->get_dc(dcNr)->divemode;
}
@ -575,13 +587,14 @@ void PlannerWidgets::preparePlanDive(const dive *currentDive, int currentDcNr)
// plan the dive in the same mode as the currently selected one
if (currentDive) {
plannerSettingsWidget.setDiveMode(currentDive->get_dc(currentDcNr)->divemode);
plannerSettingsWidget.setBailoutVisibility(currentDive->get_dc(currentDcNr)->divemode);
planned_dive->get_dc(dcNr)->divemode = currentDive->get_dc(currentDcNr)->divemode;
if (currentDive->salinity)
plannerWidget.setSalinity(currentDive->salinity);
else // No salinity means salt water
plannerWidget.setSalinity(SEAWATER_SALINITY);
}
setDiveMode(getDiveMode());
}
void PlannerWidgets::planDive()
@ -589,6 +602,7 @@ void PlannerWidgets::planDive()
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN);
plannerWidget.setReplanButton(false);
plannerWidget.setupStartTime(timestampToDateTime(planned_dive->when)); // This will reload the profile!
}
@ -596,6 +610,8 @@ void PlannerWidgets::prepareReplanDive(const dive *currentDive, int currentDcNr)
{
copy_dive(currentDive, planned_dive.get());
dcNr = currentDcNr;
setDiveMode(getDiveMode());
}
void PlannerWidgets::replanDive()
@ -609,6 +625,7 @@ void PlannerWidgets::replanDive()
plannerWidget.setSurfacePressure(planned_dive->surface_pressure.mbar);
if (planned_dive->salinity)
plannerWidget.setSalinity(planned_dive->salinity);
reset_cylinders(planned_dive.get(), true);
DivePlannerPointsModel::instance()->cylindersModel()->updateDive(planned_dive.get(), dcNr);
}
@ -657,3 +674,16 @@ void PlannerWidgets::printDecoPlan()
plannerDetails.divePlanOutput()->setHtml(origPlan); // restore original plan
#endif
}
void PlannerWidgets::setDiveMode(int mode)
{
DivePlannerPointsModel::instance()->setDiveMode(mode);
plannerWidget.setColumnVisibility(mode);
plannerSettingsWidget.setBailoutVisibility(mode);
}
void PlannerWidgets::settingsChanged()
{
plannerWidget.settingsChanged();
setDiveMode(getDiveMode());
}

View file

@ -18,9 +18,11 @@ struct dive;
class DivePlannerWidget : public QWidget {
Q_OBJECT
public:
explicit DivePlannerWidget(dive &planned_dive, int dcNr, PlannerWidgets *parent);
explicit DivePlannerWidget(const dive &planned_dive, int &dcNr, PlannerWidgets *parent);
~DivePlannerWidget();
void setReplanButton(bool replan);
void setColumnVisibility(int mode);
void setDiveMode(int mode);
public
slots:
void setupStartTime(QDateTime startTime);
@ -48,9 +50,8 @@ public
slots:
void settingsChanged();
void setBackgasBreaks(bool dobreaks);
void disableDecoElements(int mode, divemode_t rebreathermode);
void disableDecoElements(int mode, divemode_t divemode);
void disableBackgasBreaks(bool enabled);
void setDiveMode(int mode);
void setBailoutVisibility(int mode);
private:
@ -86,10 +87,12 @@ public:
void replanDive();
struct dive *getDive() const;
int getDcNr();
divemode_t getRebreatherMode() const;
divemode_t getDiveMode() const;
void settingsChanged();
public
slots:
void printDecoPlan();
void setDiveMode(int mode);
private:
std::unique_ptr<dive> planned_dive;
int dcNr;

View file

@ -62,7 +62,7 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@ -98,7 +98,30 @@
</property>
</widget>
</item>
<item row="1" column="2" colspan="2">
<item row="0" column="2">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Dive mode</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="divemode">
<property name="currentText">
<string/>
</property>
<property name="maxVisibleItems">
<number>6</number>
</property>
</widget>
</item>
<item row="1" column="3" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -112,20 +135,20 @@
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Altitude</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>ATM pressure</string>
</property>
</widget>
</item>
<item row="2" column="2">
<item row="2" column="1">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Altitude</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Water type</string>
@ -133,6 +156,25 @@
</widget>
</item>
<item row="3" column="0">
<widget class="QSpinBox" name="ATMPressure">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>mbar</string>
</property>
<property name="minimum">
<number>689</number>
</property>
<property name="maximum">
<number>1100</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="atmHeight">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -154,26 +196,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="ATMPressure">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>mbar</string>
</property>
<property name="minimum">
<number>689</number>
</property>
<property name="maximum">
<number>1100</number>
</property>
</widget>
</item>
<item row="3" column="2">
<item row="3" column="3">
<widget class="QComboBox" name="waterType">
<item>
<property name="text">
@ -197,7 +220,7 @@
</item>
</widget>
</item>
<item row="3" column="3">
<item row="3" column="4">
<widget class="QDoubleSpinBox" name="customSalinity">
<property name="enabled">
<bool>false</bool>
@ -237,7 +260,7 @@
</property>
</widget>
</item>
<item row="4" column="0" colspan="4">
<item row="4" column="0" colspan="5">
<widget class="TableView" name="cylinderTableWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
@ -253,7 +276,7 @@
</property>
</widget>
</item>
<item row="5" column="0" colspan="4">
<item row="5" column="0" colspan="5">
<widget class="TableView" name="tableWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
@ -285,6 +308,7 @@
</customwidgets>
<tabstops>
<tabstop>startTime</tabstop>
<tabstop>divemode</tabstop>
<tabstop>buttonBox</tabstop>
<tabstop>scrollArea</tabstop>
</tabstops>

View file

@ -688,6 +688,7 @@ void MainWindow::on_actionDivePlanner_triggered()
setApplicationState(ApplicationState::PlanDive);
disableShortcuts(true);
profile->exitEditMode();
plannerWidgets->preparePlanDive(current_dive, profile->dc);
profile->setPlanState(plannerWidgets->getDive(), plannerWidgets->getDcNr());
plannerWidgets->planDive();

View file

@ -246,24 +246,32 @@ void TankInfoDelegate::editorClosed(QWidget *, QAbstractItemDelegate::EndEditHin
mymodel->setData(IDX(CylindersModel::TYPE), currCombo.activeText, CylindersModel::COMMIT_ROLE);
}
TankUseDelegate::TankUseDelegate(QObject *parent) : QStyledItemDelegate(parent), currentdc(nullptr)
TankUseDelegate::TankUseDelegate(QObject *parent) : QStyledItemDelegate(parent), currentDive(nullptr), currentDcNr(0)
{
}
void TankUseDelegate::setCurrentDC(divecomputer *dc)
void TankUseDelegate::setDiveDc(const dive &d, int &dcNr)
{
currentdc = dc;
currentDive = &d;
currentDcNr = &dcNr;
}
QWidget *TankUseDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const
{
QComboBox *comboBox = new QComboBox(parent);
if (!currentdc)
const divecomputer *dc = currentDive->get_dc(*currentDcNr);
if (!dc)
return comboBox;
bool isCcrDive = currentdc->divemode == CCR;
bool isCcrDive = dc->divemode == CCR;
bool isFreeDive = dc->divemode == FREEDIVE;
for (int i = 0; i < NUM_GAS_USE; i++) {
if (isCcrDive || (i != DILUENT && i != OXYGEN))
comboBox->addItem(gettextFromC::tr(cylinderuse_text[i]));
if (isFreeDive && i != NOT_USED)
continue;
if (!isCcrDive && (i == DILUENT || i == OXYGEN))
continue;
comboBox->addItem(gettextFromC::tr(cylinderuse_text[i]));
}
return comboBox;
}
@ -350,21 +358,20 @@ WSInfoDelegate::WSInfoDelegate(QObject *parent) : ComboBoxDelegate(&createWSInfo
{
}
void AirTypesDelegate::editorClosed(QWidget *, QAbstractItemDelegate::EndEditHint)
void GasTypesDelegate::editorClosed(QWidget *, QAbstractItemDelegate::EndEditHint)
{
}
void AirTypesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
void GasTypesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
if (!index.isValid())
return;
QComboBox *combo = qobject_cast<QComboBox *>(editor);
model->setData(index, QVariant(combo->currentIndex()));
model->setData(index, combo->currentData(Qt::UserRole));
}
AirTypesDelegate::AirTypesDelegate(const dive &d, QObject *parent) :
ComboBoxDelegate([&d] (QWidget *parent) { return new GasSelectionModel(d, parent); },
parent, false)
GasTypesDelegate::GasTypesDelegate(const dive &d, int &dcNr, QObject *parent) :
ComboBoxDelegate([&d, &dcNr] (QWidget *parent) { return new GasSelectionModel(d, dcNr, parent); }, parent, false)
{
}
@ -377,15 +384,11 @@ void DiveTypesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
if (!index.isValid())
return;
QComboBox *combo = qobject_cast<QComboBox *>(editor);
model->setData(index, QVariant(combo->currentIndex()));
model->setData(index, combo->currentData(Qt::UserRole));
}
static QAbstractItemModel *createDiveTypeSelectionModel(QWidget *parent)
{
return new DiveTypeSelectionModel(parent);
}
DiveTypesDelegate::DiveTypesDelegate(QObject *parent) : ComboBoxDelegate(&createDiveTypeSelectionModel, parent, false)
DiveTypesDelegate::DiveTypesDelegate(const dive &d, int &dcNr, QObject *parent) :
ComboBoxDelegate([&d, &dcNr] (QWidget *parent) { return new DiveTypeSelectionModel(d, dcNr, parent); }, parent, false)
{
}

View file

@ -75,12 +75,13 @@ class TankUseDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit TankUseDelegate(QObject *parent = 0);
void setCurrentDC(divecomputer *dc);
void setDiveDc(const dive &d, int &dcNr);
private:
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
divecomputer *currentdc;
const dive *currentDive;
int *currentDcNr;
};
class SensorDelegate : public QStyledItemDelegate {
@ -103,10 +104,10 @@ private:
void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) override;
};
class AirTypesDelegate : public ComboBoxDelegate {
class GasTypesDelegate : public ComboBoxDelegate {
Q_OBJECT
public:
explicit AirTypesDelegate(const dive &d, QObject *parent = 0);
explicit GasTypesDelegate(const dive &d, int &dcNr, QObject *parent = 0);
private:
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) override;
@ -115,7 +116,7 @@ private:
class DiveTypesDelegate : public ComboBoxDelegate {
Q_OBJECT
public:
explicit DiveTypesDelegate(QObject *parent = 0);
explicit DiveTypesDelegate(const dive &d, int &dcNr, QObject *parent = 0);
private:
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) override;

View file

@ -255,7 +255,7 @@
<property name="spacing">
<number>2</number>
</property>
<item row="4" column="2">
<item row="3" column="2">
<widget class="QSpinBox" name="reserve_gas">
<property name="suffix">
<string>bar</string>
@ -274,7 +274,7 @@
</property>
</widget>
</item>
<item row="6" column="1">
<item row="5" column="1">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -287,7 +287,7 @@
</property>
</spacer>
</item>
<item row="22" column="1" colspan="2">
<item row="21" column="1" colspan="2">
<widget class="QCheckBox" name="switch_at_req_stop">
<property name="toolTip">
<string>Postpone gas change if a stop is not required</string>
@ -297,14 +297,14 @@
</property>
</widget>
</item>
<item row="18" column="1" colspan="2">
<item row="17" column="1" colspan="2">
<widget class="QCheckBox" name="lastStop">
<property name="text">
<string>Last stop at 6m</string>
</property>
</widget>
</item>
<item row="15" column="2">
<item row="14" column="2">
<widget class="QSpinBox" name="vpmb_conservatism">
<property name="prefix">
<string>+</string>
@ -314,7 +314,7 @@
</property>
</widget>
</item>
<item row="12" column="2">
<item row="11" column="2">
<widget class="QSpinBox" name="gfhigh">
<property name="suffix">
<string>%</string>
@ -327,7 +327,7 @@
</property>
</widget>
</item>
<item row="23" column="2">
<item row="22" column="2">
<widget class="QSpinBox" name="min_switch_duration">
<property name="suffix">
<string>min</string>
@ -346,14 +346,14 @@
</property>
</widget>
</item>
<item row="20" column="1" colspan="2">
<item row="19" column="1" colspan="2">
<widget class="QCheckBox" name="backgasBreaks">
<property name="text">
<string>Plan backgas breaks</string>
</property>
</widget>
</item>
<item row="25" column="1">
<item row="24" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -366,21 +366,21 @@
</property>
</spacer>
</item>
<item row="23" column="1">
<item row="22" column="1">
<widget class="QLabel" name="label_min_switch_duration">
<property name="text">
<string>Min. switch duration O₂% below 100%</string>
</property>
</widget>
</item>
<item row="17" column="1" colspan="2">
<item row="16" column="1" colspan="2">
<widget class="QCheckBox" name="drop_stone_mode">
<property name="text">
<string>Drop to first depth</string>
</property>
</widget>
</item>
<item row="11" column="2">
<item row="10" column="2">
<widget class="QSpinBox" name="gflow">
<property name="suffix">
<string>%</string>
@ -393,7 +393,7 @@
</property>
</widget>
</item>
<item row="11" column="1">
<item row="10" column="1">
<widget class="QLabel" name="label_gflow">
<property name="text">
<string>GFLow</string>
@ -403,7 +403,7 @@
</property>
</widget>
</item>
<item row="12" column="1">
<item row="11" column="1">
<widget class="QLabel" name="label_gfhigh">
<property name="text">
<string>GFHigh</string>
@ -413,7 +413,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="2" column="1">
<widget class="QRadioButton" name="recreational_deco">
<property name="toolTip">
<string>Maximize bottom time allowed by gas and no decompression limits</string>
@ -423,7 +423,7 @@
</property>
</widget>
</item>
<item row="15" column="1">
<item row="14" column="1">
<widget class="QLabel" name="label_vpmb_conservatism">
<property name="text">
<string>Conservatism level</string>
@ -440,7 +440,7 @@
</property>
</widget>
</item>
<item row="5" column="1" alignment="Qt::AlignHCenter">
<item row="4" column="1" alignment="Qt::AlignHCenter">
<widget class="QCheckBox" name="safetystop">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
@ -453,7 +453,7 @@
</property>
</widget>
</item>
<item row="13" column="1">
<item row="12" column="1">
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -466,7 +466,7 @@
</property>
</spacer>
</item>
<item row="16" column="1">
<item row="15" column="1">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -479,24 +479,7 @@
</property>
</spacer>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="rebreathermode">
<property name="currentText">
<string/>
</property>
<property name="maxVisibleItems">
<number>6</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_dive_type">
<property name="text">
<string>Dive mode</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="6" column="1">
<widget class="QRadioButton" name="buehlmann_deco">
<property name="text">
<string>Bühlmann deco</string>
@ -506,7 +489,7 @@
</property>
</widget>
</item>
<item row="4" column="1">
<item row="3" column="1">
<widget class="QLabel" name="label_reserve_gas">
<property name="text">
<string>Reserve gas</string>
@ -516,21 +499,21 @@
</property>
</widget>
</item>
<item row="1" column="1">
<item row="0" column="1">
<widget class="QCheckBox" name="bailout">
<property name="text">
<string>Bailout: Deco on OC</string>
<string>Rebreather: Bailout / Deco on OC</string>
</property>
</widget>
</item>
<item row="24" column="1">
<item row="23" column="1">
<widget class="QLabel" name="label_surface_segment">
<property name="text">
<string>Surface segment</string>
</property>
</widget>
</item>
<item row="24" column="2">
<item row="23" column="2">
<widget class="QSpinBox" name="surface_segment">
<property name="suffix">
<string>min</string>
@ -777,7 +760,7 @@
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="sacFactor">
<widget class="QLabel" name="label_sacfactor">
<property name="text">
<string>SAC factor</string>
</property>
@ -857,7 +840,7 @@
<tabstop>ascRateStops</tabstop>
<tabstop>ascRateLast6m</tabstop>
<tabstop>descRate</tabstop>
<tabstop>rebreathermode</tabstop>
<tabstop>divemode</tabstop>
<tabstop>recreational_deco</tabstop>
<tabstop>reserve_gas</tabstop>
<tabstop>safetystop</tabstop>

View file

@ -50,6 +50,7 @@ void PreferencesGraph::refreshSettings()
ui->pscrfactor->setValue(lrint(1000.0 / prefs.pscr_ratio));
ui->show_icd->setChecked(prefs.show_icd);
ui->allowOcGasAsDiluent->setChecked(prefs.allowOcGasAsDiluent);
}
void PreferencesGraph::syncSettings()
@ -75,6 +76,7 @@ void PreferencesGraph::syncSettings()
qPrefTechnicalDetails::set_show_scr_ocpo2(ui->show_scr_ocpo2->isChecked());
qPrefTechnicalDetails::set_show_icd(ui->show_icd->isChecked());
qPrefTechnicalDetails::set_display_deco_mode(ui->vpmb->isChecked() ? VPMB : BUEHLMANN);
qPrefTechnicalDetails::set_allowOcGasAsDiluent(ui->allowOcGasAsDiluent->isChecked());
}
static const char *danger_gf(int gf)

View file

@ -19,7 +19,7 @@
<property name="title">
<string>Gas pressure display setup</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<layout class="QGridLayout" name="gridLayout_1">
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="enabled">
@ -125,21 +125,30 @@
</property>
</widget>
</item>
<item row="4" column="0">
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Rebreather setup</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>CCR options:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="0" column="1">
<widget class="QLabel" name="label_27">
<property name="text">
<string>Dive planner default setpoint</string>
</property>
</widget>
</item>
<item row="4" column="2">
<item row="0" column="2">
<widget class="QDoubleSpinBox" name="defaultSetpoint">
<property name="suffix">
<string>bar</string>
@ -155,38 +164,35 @@
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<item row="1" column="1" colspan="2">
<widget class="QCheckBox" name="show_ccr_sensors">
<property name="text">
<string>Show O₂ sensor values when viewing pO₂</string>
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<item row="2" column="1" colspan="2">
<widget class="QCheckBox" name="show_ccr_setpoint">
<property name="text">
<string>Show CCR setpoints when viewing pO₂</string>
</property>
</widget>
</item>
<item row="7" column="0">
<item row="3" column="0">
<widget class="QLabel" name="pSCR">
<property name="text">
<string>pSCR options:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="3" column="1">
<widget class="QLabel" name="MetabolicRate">
<property name="text">
<string>pSCR metabolic rate O₂</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="7" column="2">
<item row="3" column="2">
<widget class="QDoubleSpinBox" name="psro2rate">
<property name="suffix">
<string>/min</string>
@ -196,7 +202,7 @@
</property>
</widget>
</item>
<item row="7" column="3">
<item row="3" column="3">
<widget class="QLabel" name="label_28">
<property name="text">
<string>Dilution ratio</string>
@ -206,7 +212,7 @@
</property>
</widget>
</item>
<item row="7" column="4">
<item row="3" column="4">
<widget class="QSpinBox" name="pscrfactor">
<property name="suffix">
<string/>
@ -216,29 +222,45 @@
</property>
</widget>
</item>
<item row="8" column="1">
<item row="4" column="1">
<widget class="QCheckBox" name="show_scr_ocpo2">
<property name="text">
<string>Show equivalent OC pO₂ with pSCR pO₂</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="show_icd">
<property name="text">
<string>Show warnings for isobaric counterdiffusion</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Planner setup</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="show_icd">
<property name="text">
<string>Show warnings for isobaric counterdiffusion</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="allowOcGasAsDiluent">
<property name="text">
<string>Allow open circuit gas to be used as diluent for CCR</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Ceiling display setup</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<layout class="QGridLayout" name="gridLayout_4">
<item row="3" column="3">
<widget class="QLabel" name="label_GFhigh">
<property name="text">

View file

@ -306,7 +306,6 @@ void ProfileWidget::cylindersChanged(struct dive *changed, int pos)
void ProfileWidget::setPlanState(const struct dive *d, int dcNr)
{
exitEditMode();
dc = dcNr;
view->setPlanState(d, dcNr);
setDive(d, dcNr);

View file

@ -26,6 +26,7 @@ public:
void setEnabledToolbar(bool enabled);
void nextDC();
void prevDC();
void exitEditMode();
dive *d;
int dc;
private
@ -45,7 +46,6 @@ private:
QStackedWidget *stack;
void setDive(const struct dive *d, int dcNr);
void editDive();
void exitEditMode();
void rotateDC(int dir);
std::unique_ptr<dive> editedDive;
bool placingCommand;

View file

@ -143,7 +143,7 @@ void TabDiveEquipment::updateData(const std::vector<dive *> &, dive *currentDive
cylindersModel->updateDive(currentDive, currentDC);
weightModel->updateDive(currentDive);
sensorDelegate.setCurrentDC(dc);
tankUseDelegate.setCurrentDC(dc);
tankUseDelegate.setDiveDc(*currentDive, currentDC);
if (currentDive && !currentDive->suit.empty())
ui.suit->setText(QString::fromStdString(currentDive->suit));

View file

@ -235,7 +235,7 @@ public slots:
void set_ascrate75(int value) { DivePlannerPointsModel::instance()->setAscrate75Display(value); }
void set_descrate(int value) { DivePlannerPointsModel::instance()->setDescrateDisplay(value); }
void set_dive_mode(DIVE_MODE value) { DivePlannerPointsModel::instance()->setRebreatherMode((int)value); }
void set_dive_mode(DIVE_MODE value) { DivePlannerPointsModel::instance()->setDiveMode((int)value); }
void set_planner_deco_mode(DECO_MODE value) { PlannerShared::set_planner_deco_mode((deco_mode)value); }
void set_reserve_gas(int value) { PlannerShared::set_reserve_gas(value); }
void set_safetystop(bool value) { DivePlannerPointsModel::instance()->setSafetyStop(value); }

View file

@ -11,7 +11,7 @@
#include <QGraphicsSceneMouseEvent>
#include <QSettings>
DiveHandler::DiveHandler(const struct dive *d) : dive(d)
DiveHandler::DiveHandler(const struct dive *d, int currentDcNr) : dive(d), dcNr(currentDcNr)
{
setRect(-5, -5, 10, 10);
setFlags(ItemIgnoresTransformations | ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges);
@ -31,14 +31,15 @@ void DiveHandler::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
QMenu m;
// Don't have a gas selection for the last point
emit released();
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS);
if (index.sibling(index.row() + 1, index.column()).isValid()) {
QStringList gases = get_dive_gas_list(dive);
for (int i = 0; i < gases.size(); i++) {
std::vector<std::pair<int, QString>> gases = get_dive_gas_list(dive, dcNr, true);
for (unsigned i = 0; i < gases.size(); i++) {
QAction *action = new QAction(&m);
action->setText(gases[i]);
action->setData(i);
action->setText(gases[i].second);
action->setData(gases[i].first);
connect(action, &QAction::triggered, this, &DiveHandler::changeGas);
m.addAction(action);
}

View file

@ -10,7 +10,7 @@ struct dive;
class DiveHandler : public QObject, public QGraphicsEllipseItem {
Q_OBJECT
public:
DiveHandler(const struct dive *d);
DiveHandler(const struct dive *d, int currentDcNr);
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);
@ -29,6 +29,7 @@ slots:
void changeGas();
private:
const struct dive *dive;
int dcNr;
QElapsedTimer t;
};

View file

@ -656,7 +656,7 @@ void DiveGasPressureItem::plotGasValue(double mbar, double sec, const cylinder_t
QString gas = get_gas_string(cylinder->gasmix);
QString label;
if (showDescription)
label = QStringLiteral("(%1) %2").arg(QString::fromStdString(cylinder->type.description), gas);
label = QStringLiteral("(%1) %2").arg(QString::fromStdString(cylinder->type.description), std::move(gas));
else
label = gas;
auto text = std::make_unique<DiveTextItem>(dpr, 1.0, align, this);

View file

@ -500,16 +500,6 @@ struct int ProfileWidget2::getEntryFromPos(QPointF pos)
#endif
#ifndef SUBSURFACE_MOBILE
/// Prints cylinder information for display.
/// eg : "Cyl 1 (AL80 EAN32)"
static QString printCylinderDescription(int i, const cylinder_t &cylinder)
{
QString label = gettextFromC::tr("Cyl") + QString(" %1").arg(i+1);
QString mix = get_gas_string(cylinder.gasmix);
label += QString(" (%2 %3)").arg(QString::fromStdString(cylinder.type.description)).arg(mix);
return label;
}
static bool isDiveTextItem(const QGraphicsItem *item, const DiveTextItem *textItem)
{
while (item) {
@ -520,6 +510,16 @@ static bool isDiveTextItem(const QGraphicsItem *item, const DiveTextItem *textIt
return false;
}
void ProfileWidget2::addGasChangeMenu(QMenu &m, QString menuTitle, const struct dive &d, int dcNr, int changeTime)
{
QMenu *gasChange = m.addMenu(menuTitle);
std::vector<std::pair<int, QString>> gases = get_dive_gas_list(&d, dcNr, true);
for (unsigned i = 0; i < gases.size(); i++) {
int cylinderIndex = gases[i].first;
gasChange->addAction(gases[i].second, [this, cylinderIndex, changeTime] { addGasSwitch(cylinderIndex, changeTime); });
}
}
void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
{
if (currentState == EDIT || currentState == PLAN) {
@ -558,19 +558,10 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
// Add or edit Gas Change
if (d && item && item->ev.is_gaschange()) {
int eventTime = item->ev.time.seconds;
QMenu *gasChange = m.addMenu(tr("Edit Gas Change"));
for (auto [i, cyl]: enumerated_range(d->cylinders)) {
QString label = printCylinderDescription(i, cyl);
gasChange->addAction(label, [this, idx = i, eventTime] { addGasSwitch(idx, eventTime); });
}
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
QMenu *gasChange = m.addMenu(tr("Add gas change"));
for (auto [i, cyl]: enumerated_range(d->cylinders)) {
QString label = printCylinderDescription(i, cyl);
gasChange->addAction(label, [this, idx = i, seconds] { addGasSwitch(idx, seconds); });
}
addGasChangeMenu(m, tr("Add gas change"), *d, dc, seconds);
}
m.addAction(tr("Add setpoint change"), [this, seconds]() { ProfileWidget2::addSetpointChange(seconds); });
m.addAction(tr("Add bookmark"), [this, seconds]() { addBookmark(seconds); });
@ -837,7 +828,7 @@ int ProfileWidget2::handleIndex(const DiveHandler *h) const
DiveHandler *ProfileWidget2::createHandle()
{
DiveHandler *item = new DiveHandler(d);
DiveHandler *item = new DiveHandler(d, dc);
scene()->addItem(item);
connect(item, &DiveHandler::moved, this, &ProfileWidget2::divePlannerHandlerMoved);
connect(item, &DiveHandler::clicked, this, &ProfileWidget2::divePlannerHandlerClicked);

View file

@ -104,6 +104,7 @@ private:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void keyPressEvent(QKeyEvent *e) override;
void addGasChangeMenu(QMenu &m, QString menuTitle, const struct dive &d, int dcNr, int changeTime);
#endif
void dropEvent(QDropEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;

View file

@ -156,25 +156,34 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const
const cylinder_t *cyl = index.row() == tempRow ? &tempCyl : d->get_cylinder(index.row());
bool isInappropriateUse = !is_cylinder_use_appropriate(*d->get_dc(dcNr), *cyl, true);
switch (role) {
case Qt::BackgroundRole: {
case Qt::BackgroundRole:
switch (index.column()) {
// mark the cylinder start / end pressure in red if the values
// seem implausible
case START:
case END:
pressure_t startp, endp;
startp = cyl->start.mbar ? cyl->start : cyl->sample_start;
endp = cyl->end.mbar ? cyl->end : cyl->sample_end;
if ((startp.mbar && !endp.mbar) ||
(endp.mbar && startp.mbar <= endp.mbar))
{
pressure_t startp = cyl->start.mbar ? cyl->start : cyl->sample_start;
pressure_t endp = cyl->end.mbar ? cyl->end : cyl->sample_end;
if ((startp.mbar && !endp.mbar) ||
(endp.mbar && startp.mbar <= endp.mbar))
return REDORANGE1_HIGH_TRANS;
}
break;
case USE:
if (isInappropriateUse)
return REDORANGE1_HIGH_TRANS;
break;
}
break;
}
case Qt::FontRole: {
QFont font = defaultModelFont();
switch (index.column()) {
// if we don't have manually set pressure data use italic font
case START:
@ -184,6 +193,9 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const
font.setItalic(!cyl->end.mbar);
break;
}
font.setItalic(isInappropriateUse);
return font;
}
case Qt::TextAlignmentRole:

View file

@ -1,12 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
#include "diveplannermodel.h"
#include "core/color.h"
#include "core/dive.h"
#include "core/divelist.h"
#include "core/divelog.h"
#include "core/event.h"
#include "core/subsurface-string.h"
#include "qt-models/cylindermodel.h"
#include "qt-models/models.h" // For defaultModelFont().
#include "core/metrics.h" // For defaultModelFont().
#include "core/planner.h"
#include "core/device.h"
#include "core/qthelper.h"
@ -88,12 +89,12 @@ void DivePlannerPointsModel::createSimpleDive(struct dive *dIn)
// If we're in drop_stone_mode, don't add a first point.
// It will be added implicitly.
if (!prefs.drop_stone_mode)
addStop(M_OR_FT(15, 45), 1 * 60, cylinderid, 0, true, UNDEF_COMP_TYPE);
addStop(M_OR_FT(15, 45), 1 * 60, cylinderid, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE);
addStop(M_OR_FT(15, 45), 20 * 60, 0, 0, true, UNDEF_COMP_TYPE);
addStop(M_OR_FT(15, 45), 20 * 60, 0, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE);
if (!isPlanner()) {
addStop(M_OR_FT(5, 15), 42 * 60, 0, cylinderid, true, UNDEF_COMP_TYPE);
addStop(M_OR_FT(5, 15), 45 * 60, 0, cylinderid, true, UNDEF_COMP_TYPE);
addStop(M_OR_FT(5, 15), 42 * 60, cylinderid, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE);
addStop(M_OR_FT(5, 15), 45 * 60, cylinderid, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE);
}
updateDiveProfile();
}
@ -260,13 +261,40 @@ int DivePlannerPointsModel::columnCount(const QModelIndex&) const
return COLUMNS; // to disable CCSETPOINT subtract one
}
static divemode_t get_local_divemode(struct dive *d, int dcNr, const divedatapoint &p)
{
divemode_t divemode;
switch (d->get_dc(dcNr)->divemode) {
case OC:
default:
divemode = OC;
break;
case CCR:
divemode = d->get_cylinder(p.cylinderid)->cylinder_use == DILUENT ? CCR : OC;
if (prefs.allowOcGasAsDiluent && d->get_cylinder(p.cylinderid)->cylinder_use == OC_GAS && p.divemode == CCR)
divemode = CCR;
break;
case PSCR:
divemode = p.divemode == PSCR ? PSCR : OC;
break;
}
return divemode;
}
QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const
{
divedatapoint p = divepoints.at(index.row());
const divedatapoint p = divepoints.at(index.row());
bool isInappropriateCylinder = !is_cylinder_use_appropriate(*d->get_dc(dcNr), *d->get_cylinder(p.cylinderid), false);
divemode_t divemode = get_local_divemode(d, dcNr, p);
if (role == Qt::DisplayRole || role == Qt::EditRole) {
switch (index.column()) {
case CCSETPOINT:
return (double)p.setpoint / 1000;
return (divemode == CCR) ? (double)(p.setpoint / 1000.0) : QVariant();
case DEPTH:
return (int) lrint(get_depth_units(p.depth.mm, NULL, NULL));
case RUNTIME:
@ -277,18 +305,9 @@ QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const
else
return p.time / 60;
case DIVEMODE:
return gettextFromC::tr(divemode_text_ui[p.divemode]);
return gettextFromC::tr(divemode_text_ui[divemode]);
case GAS:
/* Check if we have the same gasmix two or more times
* If yes return more verbose string */
const cylinder_t &cyl = d->cylinders[p.cylinderid];
int same_gas = same_gasmix_cylinder(cyl, p.cylinderid, d, true);
if (same_gas == -1)
return get_gas_string(cyl.gasmix);
else
return get_gas_string(cyl.gasmix) +
QString(" (%1 %2 ").arg(tr("cyl.")).arg(p.cylinderid + 1) +
QString::fromStdString(cyl.type.description) + ")";
return get_dive_gas(d, dcNr, p.cylinderid);
}
} else if (role == Qt::DecorationRole) {
switch (index.column()) {
@ -307,14 +326,28 @@ QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const
return trashForbiddenIcon().size();
}
} else if (role == Qt::FontRole) {
if (divepoints.at(index.row()).entered) {
return defaultModelFont();
} else {
QFont font = defaultModelFont();
font.setBold(true);
return font;
QFont font = defaultModelFont();
font.setBold(!p.entered);
font.setItalic(isInappropriateCylinder);
return font;
} else if (role == Qt::BackgroundRole) {
switch (index.column()) {
case GAS:
if (isInappropriateCylinder)
return REDORANGE1_HIGH_TRANS;
break;
case CCSETPOINT:
if (divemode != CCR)
return MED_GRAY_HIGH_TRANS;
break;
}
}
return QVariant();
}
@ -377,7 +410,6 @@ bool DivePlannerPointsModel::setData(const QModelIndex &index, const QVariant &v
case DIVEMODE:
if (value.toInt() < FREEDIVE) {
p.divemode = (enum divemode_t) value.toInt();
p.setpoint = p.divemode == CCR ? prefs.defaultsetpoint : 0;
}
break;
}
@ -416,7 +448,7 @@ QVariant DivePlannerPointsModel::headerData(int section, Qt::Orientation orienta
case GAS:
return tr("Used gas");
case CCSETPOINT:
return tr("CC setpoint");
return tr("Setpoint");
case DIVEMODE:
return tr("Dive mode");
}
@ -428,10 +460,29 @@ QVariant DivePlannerPointsModel::headerData(int section, Qt::Orientation orienta
Qt::ItemFlags DivePlannerPointsModel::flags(const QModelIndex &index) const
{
if (index.column() != REMOVE)
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
else
if (!index.isValid())
return QAbstractItemModel::flags(index);
if (index.column() == REMOVE)
return Qt::ItemIsEnabled;
const divedatapoint p = divepoints.at(index.row());
switch (index.column()) {
case REMOVE:
return QAbstractItemModel::flags(index);
case CCSETPOINT:
if (get_local_divemode(d, dcNr, p) != CCR)
return QAbstractItemModel::flags(index) & ~Qt::ItemIsEditable & ~Qt::ItemIsEnabled;
break;
case DIVEMODE:
if (!((d->get_dc(dcNr)->divemode == CCR && prefs.allowOcGasAsDiluent && d->get_cylinder(p.cylinderid)->cylinder_use == OC_GAS) || d->get_dc(dcNr)->divemode == PSCR))
return QAbstractItemModel::flags(index) & ~Qt::ItemIsEditable & ~Qt::ItemIsEnabled;
break;
}
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
int DivePlannerPointsModel::rowCount(const QModelIndex&) const
@ -537,14 +588,16 @@ int DivePlannerPointsModel::gfLow() const
return diveplan.gflow;
}
void DivePlannerPointsModel::setRebreatherMode(int mode)
void DivePlannerPointsModel::setDiveMode(int mode)
{
d->get_dc(dcNr)->divemode = (divemode_t) mode;
for (int i = 0; i < rowCount(); i++) {
divepoints[i].setpoint = mode == CCR ? prefs.defaultsetpoint : 0;
divepoints[i].divemode = (enum divemode_t) mode;
}
struct divecomputer *dc = d->get_dc(dcNr);
if (dc)
dc->divemode = (divemode_t) mode;
cylinders.updateDive(d, dcNr);
emitDataChanged();
cylinders.emitDataChanged();
}
void DivePlannerPointsModel::setVpmbConservatism(int level)
@ -753,13 +806,13 @@ int DivePlannerPointsModel::lastEnteredPoint() const
void DivePlannerPointsModel::addDefaultStop()
{
removeDeco();
addStop(0, 0, -1, 0, true, UNDEF_COMP_TYPE);
addStop(0, 0, -1, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE);
}
void DivePlannerPointsModel::addStop(int milimeters, int seconds)
{
removeDeco();
addStop(milimeters, seconds, -1, 0, true, UNDEF_COMP_TYPE);
addStop(milimeters, seconds, -1, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE);
updateDiveProfile();
}
@ -775,18 +828,20 @@ int DivePlannerPointsModel::addStop(int milimeters, int seconds, int cylinderid_
usePrevious = true;
int row = divepoints.count();
if (seconds == 0 && milimeters == 0 && row != 0) {
/* this is only possible if the user clicked on the 'plus' sign on the DivePoints Table */
const divedatapoint t = divepoints.at(lastEnteredPoint());
milimeters = t.depth.mm;
seconds = t.time + 600; // 10 minutes.
cylinderid = t.cylinderid;
ccpoint = t.setpoint;
} else if (seconds == 0 && milimeters == 0 && row == 0) {
milimeters = M_OR_FT(5, 15); // 5m / 15ft
seconds = 600; // 10 min
// Default to the first cylinder
cylinderid = 0;
if (seconds == 0 && milimeters == 0) {
if (row == 0) {
milimeters = M_OR_FT(5, 15); // 5m / 15ft
seconds = 600; // 10 min
// Default to the first cylinder
cylinderid = 0;
} else {
/* this is only possible if the user clicked on the 'plus' sign on the DivePoints Table */
const divedatapoint t = divepoints.at(lastEnteredPoint());
milimeters = t.depth.mm;
seconds = t.time + 600; // 10 minutes.
cylinderid = t.cylinderid;
ccpoint = t.setpoint;
}
}
// check if there's already a new stop before this one:
@ -1037,16 +1092,17 @@ void DivePlannerPointsModel::createTemporaryPlan()
int lastIndex = -1;
for (int i = 0; i < rowCount(); i++) {
divedatapoint p = at(i);
const divedatapoint p = at(i);
divemode_t divemode = get_local_divemode(d, dcNr, p);
int deltaT = lastIndex != -1 ? p.time - at(lastIndex).time : p.time;
lastIndex = i;
if (i == 0 && mode == PLAN && prefs.drop_stone_mode) {
/* Okay, we add a first segment where we go down to depth */
plan_add_segment(&diveplan, p.depth.mm / prefs.descrate, p.depth.mm, p.cylinderid, p.setpoint, true, p.divemode);
plan_add_segment(&diveplan, p.depth.mm / prefs.descrate, p.depth.mm, p.cylinderid, divemode == CCR ? p.setpoint : 0, true, divemode);
deltaT -= p.depth.mm / prefs.descrate;
}
if (p.entered)
plan_add_segment(&diveplan, deltaT, p.depth.mm, p.cylinderid, p.setpoint, true, p.divemode);
plan_add_segment(&diveplan, deltaT, p.depth.mm, p.cylinderid, divemode == CCR ? p.setpoint : 0, true, divemode);
}
#if DEBUG_PLAN

View file

@ -66,6 +66,7 @@ public:
void loadFromDive(dive *d, int dcNr);
void addStop(int millimeters, int seconds);
void setDiveMode(int mode);
public
slots:
void addDefaultStop();
@ -96,7 +97,6 @@ slots:
void removeDeco();
void deleteTemporaryPlan();
void emitDataChanged();
void setRebreatherMode(int mode);
void setReserveGas(int reserve);
void setSwitchAtReqStop(bool value);
void setMinSwitchDuration(int duration);

View file

@ -13,44 +13,84 @@
#include <QDir>
#include <QLocale>
Qt::ItemFlags GasSelectionModel::flags(const QModelIndex&) const
GasSelectionModel::GasSelectionModel(const dive &d, int dcNr, QObject *parent)
: QAbstractListModel(parent)
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
GasSelectionModel::GasSelectionModel(const dive &d, QObject *parent)
: QStringListModel(parent)
{
setStringList(get_dive_gas_list(&d));
gasNames = get_dive_gas_list(&d, dcNr, true);
}
QVariant GasSelectionModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::FontRole)
if (!index.isValid())
return QVariant();
switch (role) {
case Qt::FontRole:
return defaultModelFont();
return QStringListModel::data(index, role);
case Qt::DisplayRole:
return gasNames.at(index.row()).second;
case Qt::UserRole:
return gasNames.at(index.row()).first;
}
return QVariant();
}
int GasSelectionModel::rowCount(const QModelIndex&) const
{
return gasNames.size();
}
// Dive Type Model for the divetype combo box
Qt::ItemFlags DiveTypeSelectionModel::flags(const QModelIndex&) const
DiveTypeSelectionModel::DiveTypeSelectionModel(const dive &d, int dcNr, QObject *parent) : QAbstractListModel(parent)
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
divemode_t mode = d.get_dc(dcNr)->divemode;
for (int i = 0; i < FREEDIVE; i++) {
switch (mode) {
case OC:
default:
if (i != OC)
continue;
DiveTypeSelectionModel::DiveTypeSelectionModel(QObject *parent) : QStringListModel(parent)
{
QStringList modes;
for (int i = 0; i < FREEDIVE; i++)
modes.append(gettextFromC::tr(divemode_text_ui[i]));
setStringList(modes);
break;
case CCR:
if (i != OC && i != CCR)
continue;
break;
case PSCR:
if (i != OC && i != PSCR)
continue;
break;
}
diveTypes.push_back(std::pair(i, gettextFromC::tr(divemode_text_ui[i])));
}
}
QVariant DiveTypeSelectionModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::FontRole)
if (!index.isValid())
return QVariant();
switch (role) {
case Qt::FontRole:
return defaultModelFont();
return QStringListModel::data(index, role);
case Qt::DisplayRole:
return diveTypes.at(index.row()).second;
case Qt::UserRole:
return diveTypes.at(index.row()).first;
}
return QVariant();
}
int DiveTypeSelectionModel::rowCount(const QModelIndex&) const
{
return diveTypes.size();
}
// Language Model, The Model to populate the list of possible Languages.

View file

@ -21,20 +21,24 @@
struct dive;
class GasSelectionModel : public QStringListModel {
class GasSelectionModel : public QAbstractListModel {
Q_OBJECT
public:
GasSelectionModel(const dive &d, QObject *parent);
Qt::ItemFlags flags(const QModelIndex &index) const;
GasSelectionModel(const dive &d, int dcNr, QObject *parent);
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
private:
std::vector<std::pair<int, QString>> gasNames;
};
class DiveTypeSelectionModel : public QStringListModel {
class DiveTypeSelectionModel : public QAbstractListModel {
Q_OBJECT
public:
DiveTypeSelectionModel(QObject *parent);
Qt::ItemFlags flags(const QModelIndex &index) const;
DiveTypeSelectionModel(const dive &d, int dcNr, QObject *parent);
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
private:
std::vector<std::pair<int, QString>> diveTypes;
};
class LanguageModel : public QAbstractListModel {

View file

@ -113,6 +113,11 @@ TestCase {
var x27 = PrefTechnicalDetails.zoomed_plot
PrefTechnicalDetails.zoomed_plot = true
compare(PrefTechnicalDetails.zoomed_plot, true)
var x28 = PrefTechnicalDetails.allowOcGasAsDiluent
PrefTechnicalDetails.allowOcGasAsDiluent = true
compare(PrefTechnicalDetails.allowOcGasAsDiluent, true)
}
Item {
@ -204,6 +209,7 @@ TestCase {
PrefTechnicalDetails.tankbar = ! PrefTechnicalDetails.tankbar
PrefTechnicalDetails.vpmb_conservatism = -127
PrefTechnicalDetails.zoomed_plot = ! PrefTechnicalDetails.zoomed_plot
PrefTechnicalDetails.allowOcGasAsDiluent = ! PrefTechnicalDetails.allowOcGasAsDiluent
compare(spyCatcher.spy1, true)
compare(spyCatcher.spy2, true)