2017-04-27 18:25:32 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2015-05-28 19:23:49 +00:00
|
|
|
#include "diveplannermodel.h"
|
2020-05-01 11:43:52 +00:00
|
|
|
#include "core/divelist.h"
|
2018-05-11 15:25:41 +00:00
|
|
|
#include "core/subsurface-string.h"
|
2016-04-05 05:02:03 +00:00
|
|
|
#include "qt-models/cylindermodel.h"
|
|
|
|
#include "core/planner.h"
|
|
|
|
#include "qt-models/models.h"
|
2016-11-08 20:17:35 +00:00
|
|
|
#include "core/device.h"
|
2017-11-22 19:42:33 +00:00
|
|
|
#include "core/qthelper.h"
|
2020-10-25 12:28:55 +00:00
|
|
|
#include "core/sample.h"
|
2018-08-15 09:52:05 +00:00
|
|
|
#include "core/settings/qPrefDivePlanner.h"
|
2020-01-23 15:27:41 +00:00
|
|
|
#include "core/settings/qPrefUnit.h"
|
2020-04-13 16:07:17 +00:00
|
|
|
#if !defined(SUBSURFACE_TESTING)
|
2019-11-13 14:08:40 +00:00
|
|
|
#include "commands/command.h"
|
2020-04-13 16:07:17 +00:00
|
|
|
#endif // !SUBSURFACE_TESTING
|
2018-06-17 06:48:54 +00:00
|
|
|
#include "core/gettextfromc.h"
|
2019-07-15 21:36:14 +00:00
|
|
|
#include "core/deco.h"
|
2017-01-23 17:11:52 +00:00
|
|
|
#include <QApplication>
|
2017-04-19 13:40:59 +00:00
|
|
|
#include <QTextDocument>
|
2017-08-29 09:41:30 +00:00
|
|
|
#include <QtConcurrent>
|
2015-05-28 19:23:49 +00:00
|
|
|
|
2017-11-27 21:07:09 +00:00
|
|
|
#define VARIATIONS_IN_BACKGROUND 1
|
|
|
|
|
2017-10-03 08:06:15 +00:00
|
|
|
#define UNIT_FACTOR ((prefs.units.length == units::METERS) ? 1000.0 / 60.0 : feet_to_mm(1.0) / 60.0)
|
|
|
|
|
2020-02-03 17:59:12 +00:00
|
|
|
CylindersModel *DivePlannerPointsModel::cylindersModel()
|
2020-02-03 17:52:17 +00:00
|
|
|
{
|
|
|
|
return &cylinders;
|
|
|
|
}
|
|
|
|
|
2020-02-23 11:17:48 +00:00
|
|
|
/* TODO: Port this to CleanerTableModel to remove a bit of boilerplate. */
|
2015-05-28 19:23:49 +00:00
|
|
|
void DivePlannerPointsModel::removeSelectedPoints(const QVector<int> &rows)
|
|
|
|
{
|
|
|
|
if (!rows.count())
|
|
|
|
return;
|
|
|
|
int firstRow = rowCount() - rows.count();
|
|
|
|
QVector<int> v2 = rows;
|
|
|
|
std::sort(v2.begin(), v2.end());
|
|
|
|
|
|
|
|
beginRemoveRows(QModelIndex(), firstRow, rowCount() - 1);
|
|
|
|
for (int i = v2.count() - 1; i >= 0; i--) {
|
|
|
|
divepoints.remove(v2[i]);
|
|
|
|
}
|
|
|
|
endRemoveRows();
|
2020-02-03 17:59:12 +00:00
|
|
|
cylinders.updateTrashIcon();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::createSimpleDive()
|
|
|
|
{
|
2020-04-13 13:35:27 +00:00
|
|
|
// clean out the dive and give it an id and the correct dc model
|
|
|
|
clear_dive(&displayed_dive);
|
|
|
|
displayed_dive.id = dive_getUniqID();
|
|
|
|
displayed_dive.when = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset() + 3600;
|
|
|
|
displayed_dive.dc.model = strdup("planned dive"); // don't translate! this is stored in the XML file
|
|
|
|
|
|
|
|
clear();
|
|
|
|
setupCylinders();
|
|
|
|
setupStartTime();
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
// initialize the start time in the plan
|
2021-01-20 08:41:21 +00:00
|
|
|
diveplan.when = dateTimeToTimestamp(startTime);
|
|
|
|
displayed_dive.when = diveplan.when;
|
2015-05-28 19:23:49 +00:00
|
|
|
|
2016-07-06 12:40:28 +00:00
|
|
|
// Use gas from the first cylinder
|
|
|
|
int cylinderid = 0;
|
2015-05-28 19:23:49 +00:00
|
|
|
|
|
|
|
// If we're in drop_stone_mode, don't add a first point.
|
2021-01-20 08:41:21 +00:00
|
|
|
// It will be added implicitly.
|
2015-05-28 19:23:49 +00:00
|
|
|
if (!prefs.drop_stone_mode)
|
2018-06-17 11:41:06 +00:00
|
|
|
addStop(M_OR_FT(15, 45), 1 * 60, cylinderid, 0, true, UNDEF_COMP_TYPE);
|
2015-05-28 19:23:49 +00:00
|
|
|
|
2018-06-17 11:41:06 +00:00
|
|
|
addStop(M_OR_FT(15, 45), 20 * 60, 0, 0, true, UNDEF_COMP_TYPE);
|
2015-05-28 19:23:49 +00:00
|
|
|
if (!isPlanner()) {
|
2018-06-17 11:41:06 +00:00
|
|
|
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);
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
2016-07-06 12:40:35 +00:00
|
|
|
updateMaxDepth();
|
2016-10-26 02:10:34 +00:00
|
|
|
GasSelectionModel::instance()->repopulate();
|
2018-05-08 15:26:48 +00:00
|
|
|
DiveTypeSelectionModel::instance()->repopulate();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setupStartTime()
|
|
|
|
{
|
|
|
|
// if the latest dive is in the future, then start an hour after it ends
|
|
|
|
// otherwise start an hour from now
|
|
|
|
startTime = QDateTime::currentDateTimeUtc().addSecs(3600 + gettimezoneoffset());
|
|
|
|
if (dive_table.nr) {
|
|
|
|
struct dive *d = get_dive(dive_table.nr - 1);
|
2017-09-30 16:00:34 +00:00
|
|
|
time_t ends = dive_endtime(d);
|
2020-05-22 16:53:25 +00:00
|
|
|
time_t diff = ends - dateTimeToTimestamp(startTime);
|
2015-05-28 19:23:49 +00:00
|
|
|
if (diff > 0) {
|
|
|
|
startTime = startTime.addSecs(diff + 3600);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::loadFromDive(dive *d)
|
|
|
|
{
|
|
|
|
int depthsum = 0;
|
|
|
|
int samplecount = 0;
|
2017-04-20 06:43:48 +00:00
|
|
|
o2pressure_t last_sp;
|
2015-05-28 19:23:49 +00:00
|
|
|
bool oldRec = recalc;
|
2016-11-08 20:17:35 +00:00
|
|
|
struct divecomputer *dc = &(d->dc);
|
2018-08-16 22:58:30 +00:00
|
|
|
const struct event *evd = NULL;
|
2018-06-17 11:41:06 +00:00
|
|
|
enum divemode_t current_divemode = UNDEF_COMP_TYPE;
|
2015-05-28 19:23:49 +00:00
|
|
|
recalc = false;
|
2020-02-23 11:44:09 +00:00
|
|
|
cylinders.updateDive(&displayed_dive);
|
Improve profile display in planner
This patch allows the planner to save the last manually-entered
dive planner point of a dive plan. When the plan has been saved
and re-opened for edit, the time of the last-entered dive planner
point is used to ensure that dive planning continues from the same
point in the profile as was when the original dive plan was saved.
Mechanism:
1) In dive.h, create a new dc attribute dc->last_manual_time
with data type of duration_t.
2) In diveplanner.c, ensure that the last manually-entered
dive planner point is saved in dc->last_manual_time.
3) In save-xml.c, create a new XML attribute for the <divecomputer>
element, named last-manual-time. For dive plans, the element would
now look like:
<divecomputer model='planned dive' last-manual-time='31:17 min'>
4) In parse-xml.c, insert code that recognises the last-manual-time
XML attribute, reads the time value and assigns this time to
dc->last_manual_time.
5) In diveplannermodel.cpp, method DiveplannerPointModel::loadfromdive,
insert code that sets the appropriate boolean value to dp->entered
by comparing newtime (i.e. time of dp) with dc->last_manual_time.
6) Diveplannermodel.cpp also accepts profile data from normal dives in
the dive log, whether hand-entered or loaded from dive computer. It
looks like the reduction of dive points for dives with >100 points
continues to work ok.
The result is that when a dive plan is saved with manually entered
points up to e.g. 10 minutes into the dive, it can be re-opened for edit
in the dive planner and the planner re-creates the plan with manually
entered points up to 10 minutes. The rest of the points are "soft"
points, shaped by the deco calculations of the planner.
Improvements: Improve code for profile display in dive planner
This responds to #1052.
Change load-git.c and save-git.c so that the last-manual-time is
also saved in the git-format dive log.
Several stylistic changes in text for consistent C source code.
Improvement of dive planner profile display:
Do some simplification of my alterations to diveplannermodel.cpp
Two small style changes in planner.c and diveplannermodel.cpp
as requested ny @neolit123
Signed-off-by: Willem Ferguson <willemferguson@zoology.up.ac.za>
2018-01-15 12:51:47 +00:00
|
|
|
duration_t lasttime = { 0 };
|
2017-01-23 15:37:04 +00:00
|
|
|
duration_t lastrecordedtime = {};
|
2015-05-28 19:23:49 +00:00
|
|
|
duration_t newtime = {};
|
|
|
|
free_dps(&diveplan);
|
2017-03-05 20:07:11 +00:00
|
|
|
if (mode != PLAN)
|
|
|
|
clear();
|
2015-05-28 19:23:49 +00:00
|
|
|
diveplan.when = d->when;
|
|
|
|
// is this a "new" dive where we marked manually entered samples?
|
|
|
|
// if yes then the first sample should be marked
|
|
|
|
// if it is we only add the manually entered samples as waypoints to the diveplan
|
|
|
|
// otherwise we have to add all of them
|
2015-10-08 15:59:37 +00:00
|
|
|
|
|
|
|
bool hasMarkedSamples = false;
|
|
|
|
|
2016-11-08 20:17:35 +00:00
|
|
|
if (dc->samples)
|
|
|
|
hasMarkedSamples = dc->sample[0].manually_entered;
|
|
|
|
else
|
2018-05-05 17:26:48 +00:00
|
|
|
fake_dc(dc);
|
2015-10-08 15:59:37 +00:00
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
// if this dive has more than 100 samples (so it is probably a logged dive),
|
|
|
|
// average samples so we end up with a total of 100 samples.
|
2016-11-08 20:17:35 +00:00
|
|
|
int plansamples = dc->samples <= 100 ? dc->samples : 100;
|
2015-05-28 19:23:49 +00:00
|
|
|
int j = 0;
|
2017-03-05 01:14:22 +00:00
|
|
|
int cylinderid = 0;
|
2020-08-17 20:04:53 +00:00
|
|
|
|
2017-04-20 06:43:48 +00:00
|
|
|
last_sp.mbar = 0;
|
2015-05-28 19:23:49 +00:00
|
|
|
for (int i = 0; i < plansamples - 1; i++) {
|
2018-02-04 22:17:58 +00:00
|
|
|
if (dc->last_manual_time.seconds && dc->last_manual_time.seconds > 120 && lasttime.seconds >= dc->last_manual_time.seconds)
|
Improve profile display in planner
This patch allows the planner to save the last manually-entered
dive planner point of a dive plan. When the plan has been saved
and re-opened for edit, the time of the last-entered dive planner
point is used to ensure that dive planning continues from the same
point in the profile as was when the original dive plan was saved.
Mechanism:
1) In dive.h, create a new dc attribute dc->last_manual_time
with data type of duration_t.
2) In diveplanner.c, ensure that the last manually-entered
dive planner point is saved in dc->last_manual_time.
3) In save-xml.c, create a new XML attribute for the <divecomputer>
element, named last-manual-time. For dive plans, the element would
now look like:
<divecomputer model='planned dive' last-manual-time='31:17 min'>
4) In parse-xml.c, insert code that recognises the last-manual-time
XML attribute, reads the time value and assigns this time to
dc->last_manual_time.
5) In diveplannermodel.cpp, method DiveplannerPointModel::loadfromdive,
insert code that sets the appropriate boolean value to dp->entered
by comparing newtime (i.e. time of dp) with dc->last_manual_time.
6) Diveplannermodel.cpp also accepts profile data from normal dives in
the dive log, whether hand-entered or loaded from dive computer. It
looks like the reduction of dive points for dives with >100 points
continues to work ok.
The result is that when a dive plan is saved with manually entered
points up to e.g. 10 minutes into the dive, it can be re-opened for edit
in the dive planner and the planner re-creates the plan with manually
entered points up to 10 minutes. The rest of the points are "soft"
points, shaped by the deco calculations of the planner.
Improvements: Improve code for profile display in dive planner
This responds to #1052.
Change load-git.c and save-git.c so that the last-manual-time is
also saved in the git-format dive log.
Several stylistic changes in text for consistent C source code.
Improvement of dive planner profile display:
Do some simplification of my alterations to diveplannermodel.cpp
Two small style changes in planner.c and diveplannermodel.cpp
as requested ny @neolit123
Signed-off-by: Willem Ferguson <willemferguson@zoology.up.ac.za>
2018-01-15 12:51:47 +00:00
|
|
|
break;
|
2016-11-08 20:17:35 +00:00
|
|
|
while (j * plansamples <= i * dc->samples) {
|
|
|
|
const sample &s = dc->sample[j];
|
2018-06-17 21:21:53 +00:00
|
|
|
const sample &prev = dc->sample[j-1];
|
2015-05-28 19:23:49 +00:00
|
|
|
if (s.time.seconds != 0 && (!hasMarkedSamples || s.manually_entered)) {
|
|
|
|
depthsum += s.depth.mm;
|
2018-06-17 21:21:53 +00:00
|
|
|
last_sp = prev.setpoint;
|
2015-05-28 19:23:49 +00:00
|
|
|
++samplecount;
|
|
|
|
newtime = s.time;
|
|
|
|
}
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
if (samplecount) {
|
2017-02-26 04:51:13 +00:00
|
|
|
cylinderid = get_cylinderid_at_time(d, dc, lasttime);
|
2020-08-17 20:04:53 +00:00
|
|
|
duration_t nexttime = newtime;
|
|
|
|
++nexttime.seconds;
|
|
|
|
if (newtime.seconds - lastrecordedtime.seconds > 10 || cylinderid == get_cylinderid_at_time(d, dc, nexttime)) {
|
|
|
|
if (newtime.seconds == lastrecordedtime.seconds)
|
|
|
|
newtime.seconds += 10;
|
2018-06-17 21:21:53 +00:00
|
|
|
current_divemode = get_current_divemode(dc, newtime.seconds - 1, &evd, ¤t_divemode);
|
2018-06-17 11:41:06 +00:00
|
|
|
addStop(depthsum / samplecount, newtime.seconds, cylinderid, last_sp.mbar, true, current_divemode);
|
2017-01-23 15:37:04 +00:00
|
|
|
lastrecordedtime = newtime;
|
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
lasttime = newtime;
|
|
|
|
depthsum = 0;
|
|
|
|
samplecount = 0;
|
|
|
|
}
|
|
|
|
}
|
2017-02-26 04:51:13 +00:00
|
|
|
// make sure we get the last point right so the duration is correct
|
2018-06-17 11:41:06 +00:00
|
|
|
current_divemode = get_current_divemode(dc, d->dc.duration.seconds, &evd, ¤t_divemode);
|
Improve profile display in planner
This patch allows the planner to save the last manually-entered
dive planner point of a dive plan. When the plan has been saved
and re-opened for edit, the time of the last-entered dive planner
point is used to ensure that dive planning continues from the same
point in the profile as was when the original dive plan was saved.
Mechanism:
1) In dive.h, create a new dc attribute dc->last_manual_time
with data type of duration_t.
2) In diveplanner.c, ensure that the last manually-entered
dive planner point is saved in dc->last_manual_time.
3) In save-xml.c, create a new XML attribute for the <divecomputer>
element, named last-manual-time. For dive plans, the element would
now look like:
<divecomputer model='planned dive' last-manual-time='31:17 min'>
4) In parse-xml.c, insert code that recognises the last-manual-time
XML attribute, reads the time value and assigns this time to
dc->last_manual_time.
5) In diveplannermodel.cpp, method DiveplannerPointModel::loadfromdive,
insert code that sets the appropriate boolean value to dp->entered
by comparing newtime (i.e. time of dp) with dc->last_manual_time.
6) Diveplannermodel.cpp also accepts profile data from normal dives in
the dive log, whether hand-entered or loaded from dive computer. It
looks like the reduction of dive points for dives with >100 points
continues to work ok.
The result is that when a dive plan is saved with manually entered
points up to e.g. 10 minutes into the dive, it can be re-opened for edit
in the dive planner and the planner re-creates the plan with manually
entered points up to 10 minutes. The rest of the points are "soft"
points, shaped by the deco calculations of the planner.
Improvements: Improve code for profile display in dive planner
This responds to #1052.
Change load-git.c and save-git.c so that the last-manual-time is
also saved in the git-format dive log.
Several stylistic changes in text for consistent C source code.
Improvement of dive planner profile display:
Do some simplification of my alterations to diveplannermodel.cpp
Two small style changes in planner.c and diveplannermodel.cpp
as requested ny @neolit123
Signed-off-by: Willem Ferguson <willemferguson@zoology.up.ac.za>
2018-01-15 12:51:47 +00:00
|
|
|
if (!hasMarkedSamples && !dc->last_manual_time.seconds)
|
2018-06-17 11:41:06 +00:00
|
|
|
addStop(0, d->dc.duration.seconds,cylinderid, last_sp.mbar, true, current_divemode);
|
2015-05-28 19:23:49 +00:00
|
|
|
recalc = oldRec;
|
2018-07-13 16:46:54 +00:00
|
|
|
DiveTypeSelectionModel::instance()->repopulate();
|
2020-04-12 11:39:01 +00:00
|
|
|
preserved_until = d->duration;
|
2015-05-28 19:23:49 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy the tanks from the current dive, or the default cylinder
|
|
|
|
// or an unknown cylinder
|
|
|
|
// setup the cylinder widget accordingly
|
|
|
|
void DivePlannerPointsModel::setupCylinders()
|
|
|
|
{
|
2019-08-04 16:44:57 +00:00
|
|
|
clear_cylinder_table(&displayed_dive.cylinders);
|
2015-05-28 19:23:49 +00:00
|
|
|
if (mode == PLAN && current_dive) {
|
|
|
|
// take the displayed cylinders from the selected dive as starting point
|
2019-08-04 16:44:57 +00:00
|
|
|
copy_used_cylinders(current_dive, &displayed_dive, !prefs.display_unused_tanks);
|
2015-05-28 19:23:49 +00:00
|
|
|
reset_cylinders(&displayed_dive, true);
|
2015-10-20 07:56:01 +00:00
|
|
|
|
2019-08-04 16:44:57 +00:00
|
|
|
if (displayed_dive.cylinders.nr > 0) {
|
2020-02-23 11:44:09 +00:00
|
|
|
cylinders.updateDive(&displayed_dive);
|
2019-08-04 16:44:57 +00:00
|
|
|
return; // We have at least one cylinder
|
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
2018-01-07 10:12:48 +00:00
|
|
|
if (!empty_string(prefs.default_cylinder)) {
|
2020-01-07 03:00:20 +00:00
|
|
|
cylinder_t cyl = empty_cylinder;
|
2019-08-04 16:44:57 +00:00
|
|
|
fill_default_cylinder(&displayed_dive, &cyl);
|
|
|
|
cyl.start = cyl.type.workingpressure;
|
2020-04-28 12:50:40 +00:00
|
|
|
add_cylinder(&displayed_dive.cylinders, 0, cyl);
|
2019-08-04 16:44:57 +00:00
|
|
|
} else {
|
2020-01-07 03:00:20 +00:00
|
|
|
cylinder_t cyl = empty_cylinder;
|
2015-05-28 19:23:49 +00:00
|
|
|
// roughly an AL80
|
2019-08-04 16:44:57 +00:00
|
|
|
cyl.type.description = copy_qstring(tr("unknown"));
|
|
|
|
cyl.type.size.mliter = 11100;
|
|
|
|
cyl.type.workingpressure.mbar = 207000;
|
2020-04-28 12:50:40 +00:00
|
|
|
add_cylinder(&displayed_dive.cylinders, 0, cyl);
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
reset_cylinders(&displayed_dive, false);
|
2020-02-23 11:44:09 +00:00
|
|
|
cylinders.updateDive(&displayed_dive);
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2017-03-06 12:27:39 +00:00
|
|
|
// Update the dive's maximum depth. Returns true if max. depth changed
|
2016-07-06 12:40:35 +00:00
|
|
|
bool DivePlannerPointsModel::updateMaxDepth()
|
|
|
|
{
|
|
|
|
int prevMaxDepth = displayed_dive.maxdepth.mm;
|
|
|
|
displayed_dive.maxdepth.mm = 0;
|
|
|
|
for (int i = 0; i < rowCount(); i++) {
|
|
|
|
divedatapoint p = at(i);
|
2017-03-10 12:37:54 +00:00
|
|
|
if (p.depth.mm > displayed_dive.maxdepth.mm)
|
|
|
|
displayed_dive.maxdepth.mm = p.depth.mm;
|
2016-07-06 12:40:35 +00:00
|
|
|
}
|
2018-02-17 20:21:16 +00:00
|
|
|
return displayed_dive.maxdepth.mm != prevMaxDepth;
|
2016-07-06 12:40:35 +00:00
|
|
|
}
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
void DivePlannerPointsModel::removeDeco()
|
|
|
|
{
|
|
|
|
bool oldrec = setRecalc(false);
|
|
|
|
QVector<int> computedPoints;
|
|
|
|
for (int i = 0; i < rowCount(); i++)
|
|
|
|
if (!at(i).entered)
|
|
|
|
computedPoints.push_back(i);
|
|
|
|
removeSelectedPoints(computedPoints);
|
|
|
|
setRecalc(oldrec);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::addCylinder_clicked()
|
|
|
|
{
|
2020-02-03 17:52:17 +00:00
|
|
|
cylinders.add();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setPlanMode(Mode m)
|
|
|
|
{
|
|
|
|
mode = m;
|
|
|
|
// the planner may reset our GF settings that are used to show deco
|
|
|
|
// reset them to what's in the preferences
|
2016-09-24 08:02:08 +00:00
|
|
|
if (m != PLAN) {
|
2017-09-19 12:38:38 +00:00
|
|
|
set_gf(prefs.gflow, prefs.gfhigh);
|
2016-09-24 08:02:08 +00:00
|
|
|
set_vpmb_conservatism(prefs.vpmb_conservatism);
|
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 10:28:19 +00:00
|
|
|
bool DivePlannerPointsModel::isPlanner() const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
return mode == PLAN;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* When the planner adds deco stops to the model, adding those should not trigger a new deco calculation.
|
|
|
|
* We thus start the planner only when recalc is true. */
|
|
|
|
|
|
|
|
bool DivePlannerPointsModel::setRecalc(bool rec)
|
|
|
|
{
|
|
|
|
bool old = recalc;
|
|
|
|
recalc = rec;
|
|
|
|
return old;
|
|
|
|
}
|
|
|
|
|
2021-01-23 10:28:19 +00:00
|
|
|
bool DivePlannerPointsModel::recalcQ() const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
return recalc;
|
|
|
|
}
|
|
|
|
|
2018-05-21 15:53:42 +00:00
|
|
|
int DivePlannerPointsModel::columnCount(const QModelIndex&) const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
return COLUMNS; // to disable CCSETPOINT subtract one
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const
|
|
|
|
{
|
|
|
|
divedatapoint p = divepoints.at(index.row());
|
|
|
|
if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
|
|
|
switch (index.column()) {
|
|
|
|
case CCSETPOINT:
|
|
|
|
return (double)p.setpoint / 1000;
|
|
|
|
case DEPTH:
|
2017-03-11 16:41:41 +00:00
|
|
|
return (int) lrint(get_depth_units(p.depth.mm, NULL, NULL));
|
2015-05-28 19:23:49 +00:00
|
|
|
case RUNTIME:
|
|
|
|
return p.time / 60;
|
|
|
|
case DURATION:
|
|
|
|
if (index.row())
|
|
|
|
return (p.time - divepoints.at(index.row() - 1).time) / 60;
|
|
|
|
else
|
|
|
|
return p.time / 60;
|
2018-03-28 20:50:28 +00:00
|
|
|
case DIVEMODE:
|
2018-06-17 15:55:47 +00:00
|
|
|
return gettextFromC::tr(divemode_text_ui[p.divemode]);
|
2015-05-28 19:23:49 +00:00
|
|
|
case GAS:
|
2017-10-08 03:14:57 +00:00
|
|
|
/* Check if we have the same gasmix two or more times
|
|
|
|
* If yes return more verbose string */
|
2019-08-04 20:13:49 +00:00
|
|
|
int same_gas = same_gasmix_cylinder(get_cylinder(&displayed_dive, p.cylinderid), p.cylinderid, &displayed_dive, true);
|
2017-10-08 03:14:57 +00:00
|
|
|
if (same_gas == -1)
|
2019-08-04 20:13:49 +00:00
|
|
|
return get_gas_string(get_cylinder(&displayed_dive, p.cylinderid)->gasmix);
|
2017-10-08 03:14:57 +00:00
|
|
|
else
|
2019-08-04 20:13:49 +00:00
|
|
|
return get_gas_string(get_cylinder(&displayed_dive, p.cylinderid)->gasmix) +
|
2017-10-08 03:14:57 +00:00
|
|
|
QString(" (%1 %2 ").arg(tr("cyl.")).arg(p.cylinderid + 1) +
|
2019-08-04 20:13:49 +00:00
|
|
|
get_cylinder(&displayed_dive, p.cylinderid)->type.description + ")";
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
} else if (role == Qt::DecorationRole) {
|
|
|
|
switch (index.column()) {
|
|
|
|
case REMOVE:
|
|
|
|
if (rowCount() > 1)
|
|
|
|
return p.entered ? trashIcon() : QVariant();
|
2015-09-17 22:27:49 +00:00
|
|
|
else
|
|
|
|
return trashForbiddenIcon();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
} else if (role == Qt::SizeHintRole) {
|
|
|
|
switch (index.column()) {
|
|
|
|
case REMOVE:
|
|
|
|
if (rowCount() > 1)
|
|
|
|
return p.entered ? trashIcon().size() : QVariant();
|
2015-09-17 22:27:49 +00:00
|
|
|
else
|
|
|
|
return trashForbiddenIcon().size();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
} else if (role == Qt::FontRole) {
|
|
|
|
if (divepoints.at(index.row()).entered) {
|
|
|
|
return defaultModelFont();
|
|
|
|
} else {
|
|
|
|
QFont font = defaultModelFont();
|
|
|
|
font.setBold(true);
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivePlannerPointsModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
|
|
{
|
|
|
|
int i, shift;
|
|
|
|
if (role == Qt::EditRole) {
|
|
|
|
divedatapoint &p = divepoints[index.row()];
|
|
|
|
switch (index.column()) {
|
|
|
|
case DEPTH:
|
2016-07-06 12:40:35 +00:00
|
|
|
if (value.toInt() >= 0) {
|
2017-03-10 12:37:54 +00:00
|
|
|
p.depth = units_to_depth(value.toInt());
|
2016-07-06 12:40:35 +00:00
|
|
|
if (updateMaxDepth())
|
2020-02-03 17:59:12 +00:00
|
|
|
cylinders.updateBestMixes();
|
2016-07-06 12:40:35 +00:00
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
break;
|
|
|
|
case RUNTIME:
|
2020-08-22 11:03:51 +00:00
|
|
|
{
|
|
|
|
int secs = value.toInt() * 60;
|
2015-05-28 19:23:49 +00:00
|
|
|
i = index.row();
|
2020-08-22 11:03:51 +00:00
|
|
|
int duration = secs;
|
2015-05-28 19:23:49 +00:00
|
|
|
if (i)
|
2020-08-22 11:03:51 +00:00
|
|
|
duration -= divepoints[i-1].time;
|
|
|
|
// Make sure segments have a minimal duration
|
|
|
|
if (duration <= 0)
|
|
|
|
secs += 10 - duration;
|
|
|
|
p.time = secs;
|
|
|
|
while (++i < divepoints.size())
|
|
|
|
if (divepoints[i].time < divepoints[i - 1].time + 10)
|
|
|
|
divepoints[i].time = divepoints[i - 1].time + 10;
|
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
break;
|
2020-08-22 11:03:51 +00:00
|
|
|
case DURATION:
|
|
|
|
{
|
|
|
|
int secs = value.toInt() * 60;
|
|
|
|
if (!secs)
|
|
|
|
secs = 10;
|
|
|
|
i = index.row();
|
|
|
|
if (i)
|
|
|
|
shift = divepoints[i].time - divepoints[i - 1].time - secs;
|
|
|
|
else
|
|
|
|
shift = divepoints[i].time - secs;
|
|
|
|
while (i < divepoints.size())
|
|
|
|
divepoints[i++].time -= shift;
|
|
|
|
}
|
|
|
|
break;
|
2015-05-28 19:23:49 +00:00
|
|
|
case CCSETPOINT: {
|
|
|
|
int po2 = 0;
|
|
|
|
QByteArray gasv = value.toByteArray();
|
|
|
|
if (validate_po2(gasv.data(), &po2))
|
|
|
|
p.setpoint = po2;
|
|
|
|
} break;
|
|
|
|
case GAS:
|
2019-08-04 16:44:57 +00:00
|
|
|
if (value.toInt() >= 0)
|
2016-07-06 12:40:28 +00:00
|
|
|
p.cylinderid = value.toInt();
|
2017-11-18 22:59:22 +00:00
|
|
|
/* Did we change the start (dp 0) cylinder to another cylinderid than 0? */
|
|
|
|
if (value.toInt() != 0 && index.row() == 0)
|
2020-02-03 17:59:12 +00:00
|
|
|
cylinders.moveAtFirst(value.toInt());
|
|
|
|
cylinders.updateTrashIcon();
|
2015-05-28 19:23:49 +00:00
|
|
|
break;
|
2018-03-28 20:50:28 +00:00
|
|
|
case DIVEMODE:
|
2018-06-17 19:30:51 +00:00
|
|
|
if (value.toInt() < FREEDIVE) {
|
2018-05-08 14:24:51 +00:00
|
|
|
p.divemode = (enum divemode_t) value.toInt();
|
2018-06-17 19:30:51 +00:00
|
|
|
p.setpoint = p.divemode == CCR ? prefs.defaultsetpoint : 0;
|
|
|
|
}
|
2018-03-28 20:50:28 +00:00
|
|
|
if (index.row() == 0)
|
2018-05-08 14:24:51 +00:00
|
|
|
displayed_dive.dc.divemode = (enum divemode_t) value.toInt();
|
2018-03-28 20:50:28 +00:00
|
|
|
break;
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
editStop(index.row(), p);
|
|
|
|
}
|
|
|
|
return QAbstractItemModel::setData(index, value, role);
|
|
|
|
}
|
|
|
|
|
2017-10-11 20:03:03 +00:00
|
|
|
void DivePlannerPointsModel::gasChange(const QModelIndex &index, int newcylinderid)
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
2016-07-06 12:40:28 +00:00
|
|
|
int i = index.row(), oldcylinderid = divepoints[i].cylinderid;
|
|
|
|
while (i < rowCount() && oldcylinderid == divepoints[i].cylinderid)
|
|
|
|
divepoints[i++].cylinderid = newcylinderid;
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2017-10-11 19:27:06 +00:00
|
|
|
void DivePlannerPointsModel::cylinderRenumber(int mapping[])
|
|
|
|
{
|
2017-11-29 09:16:00 +00:00
|
|
|
for (int i = 0; i < rowCount(); i++) {
|
|
|
|
if (mapping[divepoints[i].cylinderid] >= 0)
|
|
|
|
divepoints[i].cylinderid = mapping[divepoints[i].cylinderid];
|
|
|
|
}
|
2017-10-11 19:27:06 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
QVariant DivePlannerPointsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
|
|
{
|
|
|
|
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
|
|
|
|
switch (section) {
|
|
|
|
case DEPTH:
|
|
|
|
return tr("Final depth");
|
|
|
|
case RUNTIME:
|
|
|
|
return tr("Run time");
|
|
|
|
case DURATION:
|
|
|
|
return tr("Duration");
|
|
|
|
case GAS:
|
|
|
|
return tr("Used gas");
|
|
|
|
case CCSETPOINT:
|
2015-09-18 14:28:01 +00:00
|
|
|
return tr("CC setpoint");
|
2018-03-28 20:50:28 +00:00
|
|
|
case DIVEMODE:
|
|
|
|
return tr("Dive mode");
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
} else if (role == Qt::FontRole) {
|
|
|
|
return defaultModelFont();
|
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags DivePlannerPointsModel::flags(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
if (index.column() != REMOVE)
|
|
|
|
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
|
|
|
|
else
|
|
|
|
return QAbstractItemModel::flags(index);
|
|
|
|
}
|
|
|
|
|
2018-05-21 15:53:42 +00:00
|
|
|
int DivePlannerPointsModel::rowCount(const QModelIndex&) const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
return divepoints.count();
|
|
|
|
}
|
|
|
|
|
|
|
|
DivePlannerPointsModel::DivePlannerPointsModel(QObject *parent) : QAbstractTableModel(parent),
|
2020-03-01 15:12:13 +00:00
|
|
|
cylinders(true),
|
2015-05-28 19:23:49 +00:00
|
|
|
mode(NOTHING),
|
2017-12-19 19:55:17 +00:00
|
|
|
recalc(false)
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
memset(&diveplan, 0, sizeof(diveplan));
|
2016-06-22 20:46:22 +00:00
|
|
|
startTime.setTimeSpec(Qt::UTC);
|
2020-04-13 12:46:15 +00:00
|
|
|
// use a Qt-connection to send the variations text across thread boundary (in case we
|
|
|
|
// are calculating the variations in a background thread).
|
|
|
|
connect(this, &DivePlannerPointsModel::variationsComputed, this, &DivePlannerPointsModel::computeVariationsDone);
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DivePlannerPointsModel *DivePlannerPointsModel::instance()
|
|
|
|
{
|
2017-12-20 19:00:26 +00:00
|
|
|
static DivePlannerPointsModel self;
|
|
|
|
return &self;
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::emitDataChanged()
|
|
|
|
{
|
|
|
|
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setBottomSac(double sac)
|
|
|
|
{
|
2020-01-25 12:02:59 +00:00
|
|
|
// mobile delivers the same value as desktop when using
|
|
|
|
// units:METERS
|
|
|
|
// however when using units:CUFT mobile deliver 0-300 which
|
|
|
|
// are really 0.00 - 3.00 so start be correcting that
|
2020-01-23 15:27:41 +00:00
|
|
|
#ifdef SUBSURFACE_MOBILE
|
|
|
|
if (qPrefUnits::volume() == units::CUFT)
|
|
|
|
sac /= 100; // cuft without decimals (0 - 300)
|
|
|
|
#endif
|
2015-05-28 19:23:49 +00:00
|
|
|
diveplan.bottomsac = units_to_sac(sac);
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_bottomsac(diveplan.bottomsac);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setDecoSac(double sac)
|
|
|
|
{
|
2020-01-25 12:02:59 +00:00
|
|
|
// mobile delivers the same value as desktop when using
|
|
|
|
// units:METERS
|
|
|
|
// however when using units:CUFT mobile deliver 0-300 which
|
|
|
|
// are really 0.00 - 3.00 so start be correcting that
|
2020-01-23 15:27:41 +00:00
|
|
|
#ifdef SUBSURFACE_MOBILE
|
|
|
|
if (qPrefUnits::volume() == units::CUFT)
|
|
|
|
sac /= 100; // cuft without decimals (0 - 300)
|
|
|
|
#endif
|
2015-05-28 19:23:49 +00:00
|
|
|
diveplan.decosac = units_to_sac(sac);
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_decosac(diveplan.decosac);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2017-02-11 19:24:18 +00:00
|
|
|
void DivePlannerPointsModel::setSacFactor(double factor)
|
|
|
|
{
|
2020-01-25 12:02:59 +00:00
|
|
|
// sacfactor is normal x.y (one decimal), however mobile
|
|
|
|
// delivers 0 - 100 so adjust that to 0.0 - 10.0, to have
|
|
|
|
// the same value as desktop
|
2020-01-23 15:27:41 +00:00
|
|
|
#ifdef SUBSURFACE_MOBILE
|
|
|
|
factor /= 10.0;
|
|
|
|
#endif
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_sacfactor((int) round(factor * 100));
|
2017-02-11 19:24:18 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setProblemSolvingTime(int minutes)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_problemsolvingtime(minutes);
|
2017-02-11 19:24:18 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
void DivePlannerPointsModel::setGFHigh(const int gfhigh)
|
|
|
|
{
|
2017-12-19 19:55:17 +00:00
|
|
|
if (diveplan.gfhigh != gfhigh) {
|
|
|
|
diveplan.gfhigh = gfhigh;
|
2015-05-28 19:23:49 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-19 18:15:53 +00:00
|
|
|
void DivePlannerPointsModel::setGFLow(const int gflow)
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
2017-12-19 19:55:17 +00:00
|
|
|
if (diveplan.gflow != gflow) {
|
|
|
|
diveplan.gflow = gflow;
|
|
|
|
emitDataChanged();
|
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setRebreatherMode(int mode)
|
|
|
|
{
|
|
|
|
int i;
|
2018-05-08 14:24:51 +00:00
|
|
|
displayed_dive.dc.divemode = (divemode_t) mode;
|
2018-03-28 20:50:28 +00:00
|
|
|
for (i=0; i < rowCount(); i++) {
|
2015-05-28 19:23:49 +00:00
|
|
|
divepoints[i].setpoint = mode == CCR ? prefs.defaultsetpoint : 0;
|
2018-05-08 14:24:51 +00:00
|
|
|
divepoints[i].divemode = (enum divemode_t) mode;
|
2018-03-28 20:50:28 +00:00
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
|
|
|
|
2016-09-24 08:02:07 +00:00
|
|
|
void DivePlannerPointsModel::setVpmbConservatism(int level)
|
2015-08-15 13:16:51 +00:00
|
|
|
{
|
2016-09-24 08:02:08 +00:00
|
|
|
if (diveplan.vpmb_conservatism != level) {
|
|
|
|
diveplan.vpmb_conservatism = level;
|
|
|
|
emitDataChanged();
|
|
|
|
}
|
2015-08-15 13:16:51 +00:00
|
|
|
}
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
void DivePlannerPointsModel::setSurfacePressure(int pressure)
|
|
|
|
{
|
|
|
|
diveplan.surface_pressure = pressure;
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setSalinity(int salinity)
|
|
|
|
{
|
|
|
|
diveplan.salinity = salinity;
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 10:28:19 +00:00
|
|
|
int DivePlannerPointsModel::getSurfacePressure() const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
return diveplan.surface_pressure;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setLastStop6m(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_last_stop(value);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 09:48:31 +00:00
|
|
|
void DivePlannerPointsModel::setAscrate75Display(int rate)
|
2017-10-03 08:06:15 +00:00
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_ascrate75(lrint(rate * UNIT_FACTOR));
|
2017-10-03 08:06:15 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
2021-01-23 10:28:19 +00:00
|
|
|
int DivePlannerPointsModel::ascrate75Display() const
|
2020-01-19 17:19:14 +00:00
|
|
|
{
|
|
|
|
return lrint((float)prefs.ascrate75 / UNIT_FACTOR);
|
|
|
|
}
|
2017-10-03 08:06:15 +00:00
|
|
|
|
2020-01-20 09:48:31 +00:00
|
|
|
void DivePlannerPointsModel::setAscrate50Display(int rate)
|
2017-10-03 08:06:15 +00:00
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_ascrate50(lrint(rate * UNIT_FACTOR));
|
2017-10-03 08:06:15 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
2021-01-23 10:28:19 +00:00
|
|
|
int DivePlannerPointsModel::ascrate50Display() const
|
2020-01-19 17:19:14 +00:00
|
|
|
{
|
|
|
|
return lrint((float)prefs.ascrate50 / UNIT_FACTOR);
|
|
|
|
}
|
2017-10-03 08:06:15 +00:00
|
|
|
|
2020-01-20 09:48:31 +00:00
|
|
|
void DivePlannerPointsModel::setAscratestopsDisplay(int rate)
|
2017-10-03 08:06:15 +00:00
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_ascratestops(lrint(rate * UNIT_FACTOR));
|
2017-10-03 08:06:15 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
2021-01-23 10:28:19 +00:00
|
|
|
int DivePlannerPointsModel::ascratestopsDisplay() const
|
2020-01-19 17:19:14 +00:00
|
|
|
{
|
|
|
|
return lrint((float)prefs.ascratestops / UNIT_FACTOR);
|
|
|
|
}
|
2017-10-03 08:06:15 +00:00
|
|
|
|
2020-01-20 09:48:31 +00:00
|
|
|
void DivePlannerPointsModel::setAscratelast6mDisplay(int rate)
|
2017-10-03 08:06:15 +00:00
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_ascratelast6m(lrint(rate * UNIT_FACTOR));
|
2017-10-03 08:06:15 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
2021-01-23 10:28:19 +00:00
|
|
|
int DivePlannerPointsModel::ascratelast6mDisplay() const
|
2020-01-19 17:19:14 +00:00
|
|
|
{
|
|
|
|
return lrint((float)prefs.ascratelast6m / UNIT_FACTOR);
|
|
|
|
}
|
2017-10-03 08:06:15 +00:00
|
|
|
|
2020-01-20 09:48:31 +00:00
|
|
|
void DivePlannerPointsModel::setDescrateDisplay(int rate)
|
2017-10-03 08:06:15 +00:00
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_descrate(lrint(rate * UNIT_FACTOR));
|
2017-10-03 08:06:15 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
2021-01-23 10:28:19 +00:00
|
|
|
int DivePlannerPointsModel::descrateDisplay() const
|
2020-01-19 17:19:14 +00:00
|
|
|
{
|
|
|
|
return lrint((float)prefs.descrate / UNIT_FACTOR);
|
|
|
|
}
|
2017-10-03 08:06:15 +00:00
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
void DivePlannerPointsModel::setVerbatim(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_verbatim_plan(value);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setDisplayRuntime(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_display_runtime(value);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setDisplayDuration(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_display_duration(value);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setDisplayTransitions(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_display_transitions(value);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2017-09-18 14:10:47 +00:00
|
|
|
void DivePlannerPointsModel::setDisplayVariations(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_display_variations(value);
|
2017-09-18 14:10:47 +00:00
|
|
|
emitDataChanged();
|
|
|
|
}
|
|
|
|
|
2015-07-03 21:07:58 +00:00
|
|
|
void DivePlannerPointsModel::setDecoMode(int mode)
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_planner_deco_mode(deco_mode(mode));
|
2017-01-07 02:11:19 +00:00
|
|
|
emit recreationChanged(mode == int(prefs.planner_deco_mode));
|
2017-12-19 20:08:23 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setSafetyStop(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_safetystop(value);
|
2017-12-19 20:08:23 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setReserveGas(int reserve)
|
|
|
|
{
|
2016-03-22 22:44:59 +00:00
|
|
|
if (prefs.units.pressure == units::BAR)
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_reserve_gas(reserve * 1000);
|
2016-03-22 22:44:59 +00:00
|
|
|
else
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_reserve_gas(psi_to_mbar(reserve));
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setDropStoneMode(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_drop_stone_mode(value);
|
2015-05-28 19:23:49 +00:00
|
|
|
if (prefs.drop_stone_mode) {
|
|
|
|
/* Remove the first entry if we enable drop_stone_mode */
|
|
|
|
if (rowCount() >= 2) {
|
|
|
|
beginRemoveRows(QModelIndex(), 0, 0);
|
|
|
|
divepoints.remove(0);
|
|
|
|
endRemoveRows();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Add a first entry if we disable drop_stone_mode */
|
|
|
|
beginInsertRows(QModelIndex(), 0, 0);
|
|
|
|
/* Copy the first current point */
|
|
|
|
divedatapoint p = divepoints.at(0);
|
2017-03-10 12:37:54 +00:00
|
|
|
p.time = p.depth.mm / prefs.descrate;
|
2015-05-28 19:23:49 +00:00
|
|
|
divepoints.push_front(p);
|
|
|
|
endInsertRows();
|
|
|
|
}
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2015-06-22 11:48:42 +00:00
|
|
|
void DivePlannerPointsModel::setSwitchAtReqStop(bool value)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_switch_at_req_stop(value);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-06-22 11:48:42 +00:00
|
|
|
}
|
|
|
|
|
2015-06-19 10:25:03 +00:00
|
|
|
void DivePlannerPointsModel::setMinSwitchDuration(int duration)
|
|
|
|
{
|
2018-08-15 09:52:05 +00:00
|
|
|
qPrefDivePlanner::set_min_switch_duration(duration * 60);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-06-19 10:25:03 +00:00
|
|
|
}
|
|
|
|
|
2019-03-25 21:40:59 +00:00
|
|
|
void DivePlannerPointsModel::setSurfaceSegment(int duration)
|
|
|
|
{
|
|
|
|
qPrefDivePlanner::set_surface_segment(duration * 60);
|
|
|
|
emitDataChanged();
|
|
|
|
}
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
void DivePlannerPointsModel::setStartDate(const QDate &date)
|
|
|
|
{
|
|
|
|
startTime.setDate(date);
|
2020-05-22 16:53:25 +00:00
|
|
|
diveplan.when = dateTimeToTimestamp(startTime);
|
2015-05-28 19:23:49 +00:00
|
|
|
displayed_dive.when = diveplan.when;
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::setStartTime(const QTime &t)
|
|
|
|
{
|
|
|
|
startTime.setTime(t);
|
2021-01-19 20:20:01 +00:00
|
|
|
diveplan.when = dateTimeToTimestamp(startTime);
|
2015-05-28 19:23:49 +00:00
|
|
|
displayed_dive.when = diveplan.when;
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool divePointsLessThan(const divedatapoint &p1, const divedatapoint &p2)
|
|
|
|
{
|
2017-12-29 18:29:23 +00:00
|
|
|
return p1.time < p2.time;
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 10:28:19 +00:00
|
|
|
int DivePlannerPointsModel::lastEnteredPoint() const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
for (int i = divepoints.count() - 1; i >= 0; i--)
|
|
|
|
if (divepoints.at(i).entered)
|
|
|
|
return i;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-01-23 10:41:42 +00:00
|
|
|
void DivePlannerPointsModel::addDefaultStop()
|
|
|
|
{
|
|
|
|
addStop(0, 0, -1, 0, true, UNDEF_COMP_TYPE);
|
|
|
|
}
|
|
|
|
|
2017-02-04 13:12:43 +00:00
|
|
|
// cylinderid_in == -1 means same gas as before.
|
2018-06-17 11:41:06 +00:00
|
|
|
// divemode == UNDEF_COMP_TYPE means determine from previous point.
|
|
|
|
int DivePlannerPointsModel::addStop(int milimeters, int seconds, int cylinderid_in, int ccpoint, bool entered, enum divemode_t divemode)
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
2016-07-09 19:37:05 +00:00
|
|
|
int cylinderid = 0;
|
2015-05-28 19:23:49 +00:00
|
|
|
bool usePrevious = false;
|
2017-02-04 13:12:43 +00:00
|
|
|
if (cylinderid_in >= 0)
|
2016-07-06 12:40:28 +00:00
|
|
|
cylinderid = cylinderid_in;
|
2015-05-28 19:23:49 +00:00
|
|
|
else
|
|
|
|
usePrevious = true;
|
|
|
|
if (recalcQ())
|
|
|
|
removeDeco();
|
|
|
|
|
|
|
|
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());
|
2017-03-10 12:37:54 +00:00
|
|
|
milimeters = t.depth.mm;
|
2015-05-28 19:23:49 +00:00
|
|
|
seconds = t.time + 600; // 10 minutes.
|
2016-07-06 12:40:28 +00:00
|
|
|
cylinderid = t.cylinderid;
|
2015-05-28 19:23:49 +00:00
|
|
|
ccpoint = t.setpoint;
|
|
|
|
} else if (seconds == 0 && milimeters == 0 && row == 0) {
|
|
|
|
milimeters = M_OR_FT(5, 15); // 5m / 15ft
|
|
|
|
seconds = 600; // 10 min
|
2016-07-06 12:40:28 +00:00
|
|
|
// Default to the first cylinder
|
|
|
|
cylinderid = 0;
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// check if there's already a new stop before this one:
|
|
|
|
for (int i = 0; i < row; i++) {
|
|
|
|
const divedatapoint &dp = divepoints.at(i);
|
|
|
|
if (dp.time == seconds) {
|
|
|
|
row = i;
|
|
|
|
beginRemoveRows(QModelIndex(), row, row);
|
|
|
|
divepoints.remove(row);
|
|
|
|
endRemoveRows();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (dp.time > seconds) {
|
|
|
|
row = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Previous, actually means next as we are typically subdiving a segment and the gas for
|
|
|
|
// the segment is determined by the waypoint at the end.
|
|
|
|
if (usePrevious) {
|
|
|
|
if (row < divepoints.count()) {
|
2016-07-06 12:40:28 +00:00
|
|
|
cylinderid = divepoints.at(row).cylinderid;
|
2018-06-17 11:41:06 +00:00
|
|
|
if (divemode == UNDEF_COMP_TYPE)
|
|
|
|
divemode = divepoints.at(row).divemode;
|
2018-06-17 21:20:02 +00:00
|
|
|
ccpoint = divepoints.at(row).setpoint;
|
2015-05-28 19:23:49 +00:00
|
|
|
} else if (row > 0) {
|
2016-07-06 12:40:28 +00:00
|
|
|
cylinderid = divepoints.at(row - 1).cylinderid;
|
2018-06-17 11:41:06 +00:00
|
|
|
if (divemode == UNDEF_COMP_TYPE)
|
|
|
|
divemode = divepoints.at(row - 1).divemode;
|
2018-06-17 21:20:02 +00:00
|
|
|
ccpoint = divepoints.at(row -1).setpoint;
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-17 11:41:06 +00:00
|
|
|
if (divemode == UNDEF_COMP_TYPE)
|
|
|
|
divemode = displayed_dive.dc.divemode;
|
2015-05-28 19:23:49 +00:00
|
|
|
|
|
|
|
// add the new stop
|
|
|
|
beginInsertRows(QModelIndex(), row, row);
|
|
|
|
divedatapoint point;
|
2017-03-10 12:37:54 +00:00
|
|
|
point.depth.mm = milimeters;
|
2015-05-28 19:23:49 +00:00
|
|
|
point.time = seconds;
|
2016-07-06 12:40:28 +00:00
|
|
|
point.cylinderid = cylinderid;
|
2015-05-28 19:23:49 +00:00
|
|
|
point.setpoint = ccpoint;
|
|
|
|
point.entered = entered;
|
2018-03-28 20:50:28 +00:00
|
|
|
point.divemode = divemode;
|
2015-05-28 19:23:49 +00:00
|
|
|
point.next = NULL;
|
2021-01-23 12:51:39 +00:00
|
|
|
divepoints.insert(divepoints.begin() + row, point);
|
2015-05-28 19:23:49 +00:00
|
|
|
endInsertRows();
|
|
|
|
return row;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::editStop(int row, divedatapoint newData)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* When moving divepoints rigorously, we might end up with index
|
|
|
|
* out of range, thus returning the last one instead.
|
|
|
|
*/
|
2017-10-18 07:02:28 +00:00
|
|
|
int old_first_cylid = divepoints[0].cylinderid;
|
2015-05-28 19:23:49 +00:00
|
|
|
if (row >= divepoints.count())
|
|
|
|
return;
|
|
|
|
divepoints[row] = newData;
|
|
|
|
std::sort(divepoints.begin(), divepoints.end(), divePointsLessThan);
|
2016-07-06 12:40:38 +00:00
|
|
|
if (updateMaxDepth())
|
2020-02-03 17:59:12 +00:00
|
|
|
cylinders.updateBestMixes();
|
2017-10-18 07:02:28 +00:00
|
|
|
if (divepoints[0].cylinderid != old_first_cylid)
|
2020-02-03 17:59:12 +00:00
|
|
|
cylinders.moveAtFirst(divepoints[0].cylinderid);
|
2017-02-15 22:15:08 +00:00
|
|
|
emitDataChanged();
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 10:28:19 +00:00
|
|
|
int DivePlannerPointsModel::size() const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
return divepoints.size();
|
|
|
|
}
|
|
|
|
|
2021-01-23 10:28:19 +00:00
|
|
|
divedatapoint DivePlannerPointsModel::at(int row) const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* When moving divepoints rigorously, we might end up with index
|
|
|
|
* out of range, thus returning the last one instead.
|
|
|
|
*/
|
|
|
|
if (row >= divepoints.count())
|
|
|
|
return divepoints.at(divepoints.count() - 1);
|
|
|
|
return divepoints.at(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::remove(const QModelIndex &index)
|
|
|
|
{
|
|
|
|
if (index.column() != REMOVE || rowCount() == 1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
divedatapoint dp = at(index.row());
|
|
|
|
if (!dp.entered)
|
|
|
|
return;
|
|
|
|
|
2017-10-18 07:02:28 +00:00
|
|
|
int old_first_cylid = divepoints[0].cylinderid;
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
/* TODO: this seems so wrong.
|
|
|
|
* We can't do this here if we plan to use QML on mobile
|
|
|
|
* as mobile has no ControlModifier.
|
|
|
|
* The correct thing to do is to create a new method
|
|
|
|
* remove method that will pass the first and last index of the
|
|
|
|
* removed rows, and remove those in a go.
|
|
|
|
*/
|
2017-01-23 17:11:52 +00:00
|
|
|
int i;
|
|
|
|
int rows = rowCount();
|
|
|
|
if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
|
2020-04-12 11:39:01 +00:00
|
|
|
preserved_until.seconds = divepoints.at(index.row()).time;
|
2017-01-23 17:11:52 +00:00
|
|
|
beginRemoveRows(QModelIndex(), index.row(), rows - 1);
|
|
|
|
for (i = rows - 1; i >= index.row(); i--)
|
|
|
|
divepoints.remove(i);
|
|
|
|
} else {
|
2020-04-12 11:39:01 +00:00
|
|
|
if (index.row() == rows -1)
|
|
|
|
preserved_until.seconds = divepoints.at(rows - 1).time;
|
2015-05-28 19:23:49 +00:00
|
|
|
beginRemoveRows(QModelIndex(), index.row(), index.row());
|
|
|
|
divepoints.remove(index.row());
|
2017-01-23 17:11:52 +00:00
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
endRemoveRows();
|
2020-02-03 17:59:12 +00:00
|
|
|
cylinders.updateTrashIcon();
|
2017-10-18 07:02:28 +00:00
|
|
|
if (divepoints[0].cylinderid != old_first_cylid)
|
2020-02-03 17:59:12 +00:00
|
|
|
cylinders.moveAtFirst(divepoints[0].cylinderid);
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct diveplan &DivePlannerPointsModel::getDiveplan()
|
|
|
|
{
|
|
|
|
return diveplan;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::cancelPlan()
|
|
|
|
{
|
|
|
|
/* TODO:
|
|
|
|
* This check shouldn't be here - this is the interface responsability.
|
|
|
|
* as soon as the interface thinks that it could cancel the plan, this should be
|
|
|
|
* called.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
if (mode == PLAN && rowCount()) {
|
|
|
|
if (QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT(tr("Discard the plan?"),
|
|
|
|
tr("You are about to discard your plan.")),
|
|
|
|
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Discard) != QMessageBox::Discard) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
setPlanMode(NOTHING);
|
|
|
|
free_dps(&diveplan);
|
|
|
|
|
|
|
|
emit planCanceled();
|
|
|
|
}
|
|
|
|
|
|
|
|
DivePlannerPointsModel::Mode DivePlannerPointsModel::currentMode() const
|
|
|
|
{
|
|
|
|
return mode;
|
|
|
|
}
|
|
|
|
|
2021-01-23 10:28:19 +00:00
|
|
|
bool DivePlannerPointsModel::tankInUse(int cylinderid) const
|
2015-05-28 19:23:49 +00:00
|
|
|
{
|
|
|
|
for (int j = 0; j < rowCount(); j++) {
|
2021-01-23 10:28:19 +00:00
|
|
|
const divedatapoint &p = divepoints[j];
|
2015-05-28 19:23:49 +00:00
|
|
|
if (p.time == 0) // special entries that hold the available gases
|
|
|
|
continue;
|
|
|
|
if (!p.entered) // removing deco gases is ok
|
|
|
|
continue;
|
2016-07-06 12:40:28 +00:00
|
|
|
if (p.cylinderid == cylinderid) // tank is in use
|
2015-05-28 19:23:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::clear()
|
|
|
|
{
|
|
|
|
bool oldRecalc = setRecalc(false);
|
|
|
|
|
2020-02-23 11:44:09 +00:00
|
|
|
cylinders.updateDive(&displayed_dive);
|
2021-01-23 10:46:04 +00:00
|
|
|
beginResetModel();
|
|
|
|
divepoints.clear();
|
|
|
|
endResetModel();
|
2020-02-03 17:52:17 +00:00
|
|
|
cylinders.clear();
|
2020-04-12 11:39:01 +00:00
|
|
|
preserved_until.seconds = 0;
|
2015-05-28 19:23:49 +00:00
|
|
|
setRecalc(oldRecalc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::createTemporaryPlan()
|
|
|
|
{
|
|
|
|
// Get the user-input and calculate the dive info
|
|
|
|
free_dps(&diveplan);
|
|
|
|
int lastIndex = -1;
|
|
|
|
for (int i = 0; i < rowCount(); i++) {
|
|
|
|
divedatapoint p = at(i);
|
|
|
|
int deltaT = lastIndex != -1 ? p.time - at(lastIndex).time : p.time;
|
|
|
|
lastIndex = i;
|
2017-02-26 04:07:43 +00:00
|
|
|
if (i == 0 && mode == PLAN && prefs.drop_stone_mode) {
|
2016-07-06 12:40:28 +00:00
|
|
|
/* Okay, we add a first segment where we go down to depth */
|
2018-03-28 20:50:28 +00:00
|
|
|
plan_add_segment(&diveplan, p.depth.mm / prefs.descrate, p.depth.mm, p.cylinderid, p.setpoint, true, p.divemode);
|
2017-03-10 12:37:54 +00:00
|
|
|
deltaT -= p.depth.mm / prefs.descrate;
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
if (p.entered)
|
2018-03-28 20:50:28 +00:00
|
|
|
plan_add_segment(&diveplan, deltaT, p.depth.mm, p.cylinderid, p.setpoint, true, p.divemode);
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// what does the cache do???
|
2017-05-25 22:45:53 +00:00
|
|
|
struct deco_state *cache = NULL;
|
2015-05-28 19:23:49 +00:00
|
|
|
struct divedatapoint *dp = NULL;
|
2019-08-04 16:44:57 +00:00
|
|
|
for (int i = 0; i < displayed_dive.cylinders.nr; i++) {
|
2019-08-04 20:13:49 +00:00
|
|
|
cylinder_t *cyl = get_cylinder(&displayed_dive, i);
|
2017-01-23 16:35:27 +00:00
|
|
|
if (cyl->depth.mm && cyl->cylinder_use != NOT_USED) {
|
2016-07-06 12:40:28 +00:00
|
|
|
dp = create_dp(0, cyl->depth.mm, i, 0);
|
2015-05-28 19:23:49 +00:00
|
|
|
if (diveplan.dp) {
|
|
|
|
dp->next = diveplan.dp;
|
|
|
|
diveplan.dp = dp;
|
|
|
|
} else {
|
|
|
|
dp->next = NULL;
|
|
|
|
diveplan.dp = dp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#if DEBUG_PLAN
|
|
|
|
dump_plan(&diveplan);
|
|
|
|
#endif
|
|
|
|
if (recalcQ() && !diveplan_empty(&diveplan)) {
|
2017-08-27 20:49:41 +00:00
|
|
|
struct decostop stoptable[60];
|
2017-11-22 19:42:33 +00:00
|
|
|
struct deco_state plan_deco_state;
|
2017-11-27 16:36:21 +00:00
|
|
|
struct diveplan *plan_copy;
|
|
|
|
|
2018-08-31 11:46:34 +00:00
|
|
|
memset(&plan_deco_state, 0, sizeof(struct deco_state));
|
2017-11-22 19:42:33 +00:00
|
|
|
plan(&plan_deco_state, &diveplan, &displayed_dive, DECOTIMESTEP, stoptable, &cache, isPlanner(), false);
|
2017-11-27 16:36:21 +00:00
|
|
|
plan_copy = (struct diveplan *)malloc(sizeof(struct diveplan));
|
|
|
|
lock_planner();
|
|
|
|
cloneDiveplan(&diveplan, plan_copy);
|
|
|
|
unlock_planner();
|
2017-11-27 21:07:09 +00:00
|
|
|
#ifdef VARIATIONS_IN_BACKGROUND
|
2019-10-17 20:56:40 +00:00
|
|
|
// Since we're calling computeVariations asynchronously and plan_deco_state is allocated
|
|
|
|
// on the stack, it must be copied and freed by the worker-thread.
|
|
|
|
struct deco_state *plan_deco_state_copy = new deco_state(plan_deco_state);
|
|
|
|
QtConcurrent::run(this, &DivePlannerPointsModel::computeVariationsFreeDeco, plan_copy, plan_deco_state_copy);
|
2017-11-27 21:07:09 +00:00
|
|
|
#else
|
|
|
|
computeVariations(plan_copy, &plan_deco_state);
|
|
|
|
#endif
|
2017-11-24 13:17:01 +00:00
|
|
|
final_deco_state = plan_deco_state;
|
2020-04-13 13:09:35 +00:00
|
|
|
emit calculatedPlanNotes(QString(displayed_dive.notes));
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
// throw away the cache
|
|
|
|
free(cache);
|
|
|
|
#if DEBUG_PLAN
|
|
|
|
save_dive(stderr, &displayed_dive);
|
|
|
|
dump_plan(&diveplan);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::deleteTemporaryPlan()
|
|
|
|
{
|
|
|
|
free_dps(&diveplan);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::savePlan()
|
|
|
|
{
|
|
|
|
createPlan(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::saveDuplicatePlan()
|
|
|
|
{
|
|
|
|
createPlan(true);
|
|
|
|
}
|
|
|
|
|
2017-11-27 16:36:21 +00:00
|
|
|
struct divedatapoint * DivePlannerPointsModel::cloneDiveplan(struct diveplan *plan_src, struct diveplan *plan_copy)
|
2017-08-28 21:59:58 +00:00
|
|
|
{
|
|
|
|
divedatapoint *src, *last_segment;
|
|
|
|
divedatapoint **dp;
|
|
|
|
|
2017-11-27 16:36:21 +00:00
|
|
|
src = plan_src->dp;
|
|
|
|
*plan_copy = *plan_src;
|
2017-08-28 21:59:58 +00:00
|
|
|
dp = &plan_copy->dp;
|
|
|
|
while (src && (!src->time || src->entered)) {
|
|
|
|
*dp = (struct divedatapoint *)malloc(sizeof(struct divedatapoint));
|
|
|
|
**dp = *src;
|
|
|
|
dp = &(*dp)->next;
|
|
|
|
src = src->next;
|
|
|
|
}
|
|
|
|
(*dp) = NULL;
|
|
|
|
|
|
|
|
last_segment = plan_copy->dp;
|
2017-11-22 19:42:33 +00:00
|
|
|
while (last_segment && last_segment->next && last_segment->next->next)
|
2017-08-28 21:59:58 +00:00
|
|
|
last_segment = last_segment->next;
|
|
|
|
return last_segment;
|
|
|
|
}
|
|
|
|
|
2017-08-29 10:26:00 +00:00
|
|
|
int DivePlannerPointsModel::analyzeVariations(struct decostop *min, struct decostop *mid, struct decostop *max, const char *unit)
|
2017-08-28 21:59:58 +00:00
|
|
|
{
|
2017-12-02 07:45:41 +00:00
|
|
|
int minsum = 0;
|
|
|
|
int midsum = 0;
|
|
|
|
int maxsum = 0;
|
2017-08-28 21:59:58 +00:00
|
|
|
int leftsum = 0;
|
|
|
|
int rightsum = 0;
|
|
|
|
|
2017-12-02 07:45:41 +00:00
|
|
|
while (min->depth) {
|
|
|
|
minsum += min->time;
|
2017-08-28 21:59:58 +00:00
|
|
|
++min;
|
2017-12-02 07:45:41 +00:00
|
|
|
}
|
|
|
|
while (mid->depth) {
|
|
|
|
midsum += mid->time;
|
2017-08-28 21:59:58 +00:00
|
|
|
++mid;
|
2017-12-02 07:45:41 +00:00
|
|
|
}
|
|
|
|
while (max->depth) {
|
|
|
|
maxsum += max->time;
|
2017-08-28 21:59:58 +00:00
|
|
|
++max;
|
|
|
|
}
|
2017-12-02 07:45:41 +00:00
|
|
|
|
|
|
|
leftsum = midsum - minsum;
|
|
|
|
rightsum = maxsum - midsum;
|
|
|
|
|
|
|
|
#ifdef DEBUG_STOPVAR
|
2017-08-29 10:26:00 +00:00
|
|
|
printf("Total + %d:%02d/%s +- %d s/%s\n\n", FRACTION((leftsum + rightsum) / 2, 60), unit,
|
|
|
|
(rightsum - leftsum) / 2, unit);
|
2017-12-08 13:51:47 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(unit)
|
2017-08-29 10:26:00 +00:00
|
|
|
#endif
|
|
|
|
return (leftsum + rightsum) / 2;
|
2017-08-28 21:59:58 +00:00
|
|
|
}
|
|
|
|
|
2019-10-17 20:56:40 +00:00
|
|
|
void DivePlannerPointsModel::computeVariationsFreeDeco(struct diveplan *original_plan, struct deco_state *previous_ds)
|
|
|
|
{
|
|
|
|
computeVariations(original_plan, previous_ds);
|
|
|
|
delete previous_ds;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlannerPointsModel::computeVariations(struct diveplan *original_plan, const struct deco_state *previous_ds)
|
2017-08-28 21:59:58 +00:00
|
|
|
{
|
2019-10-27 01:11:39 +00:00
|
|
|
// nothing to do unless there's an original plan
|
|
|
|
if (!original_plan)
|
|
|
|
return;
|
2017-11-27 16:36:21 +00:00
|
|
|
|
2017-08-28 21:59:58 +00:00
|
|
|
struct dive *dive = alloc_dive();
|
|
|
|
copy_dive(&displayed_dive, dive);
|
|
|
|
struct decostop original[60], deeper[60], shallower[60], shorter[60], longer[60];
|
|
|
|
struct deco_state *cache = NULL, *save = NULL;
|
|
|
|
struct diveplan plan_copy;
|
|
|
|
struct divedatapoint *last_segment;
|
2019-10-17 20:56:40 +00:00
|
|
|
struct deco_state ds = *previous_ds;
|
2017-11-27 16:36:21 +00:00
|
|
|
|
2021-02-12 17:19:24 +00:00
|
|
|
if (isPlanner() && prefs.display_variations && decoMode(true) != RECREATIONAL) {
|
2017-08-29 09:41:30 +00:00
|
|
|
int my_instance = ++instanceCounter;
|
2017-11-27 16:36:21 +00:00
|
|
|
cache_deco_state(&ds, &save);
|
2017-12-26 21:14:40 +00:00
|
|
|
|
2017-12-02 10:29:35 +00:00
|
|
|
duration_t delta_time = { .seconds = 60 };
|
|
|
|
QString time_units = tr("min");
|
|
|
|
depth_t delta_depth;
|
|
|
|
QString depth_units;
|
|
|
|
|
|
|
|
if (prefs.units.length == units::METERS) {
|
|
|
|
delta_depth.mm = 1000; // 1m
|
|
|
|
depth_units = tr("m");
|
|
|
|
} else {
|
|
|
|
delta_depth.mm = feet_to_mm(1.0); // 1ft
|
|
|
|
depth_units = tr("ft");
|
|
|
|
}
|
2017-11-27 16:36:21 +00:00
|
|
|
|
|
|
|
last_segment = cloneDiveplan(original_plan, &plan_copy);
|
2017-12-02 10:29:35 +00:00
|
|
|
if (!last_segment)
|
2017-11-27 16:36:21 +00:00
|
|
|
goto finish;
|
2017-08-29 09:41:30 +00:00
|
|
|
if (my_instance != instanceCounter)
|
2017-11-27 16:36:21 +00:00
|
|
|
goto finish;
|
|
|
|
plan(&ds, &plan_copy, dive, 1, original, &cache, true, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
free_dps(&plan_copy);
|
2017-11-27 16:36:21 +00:00
|
|
|
restore_deco_state(save, &ds, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
|
2017-11-27 16:36:21 +00:00
|
|
|
last_segment = cloneDiveplan(original_plan, &plan_copy);
|
2017-12-02 10:29:35 +00:00
|
|
|
last_segment->depth.mm += delta_depth.mm;
|
|
|
|
last_segment->next->depth.mm += delta_depth.mm;
|
2017-08-29 09:41:30 +00:00
|
|
|
if (my_instance != instanceCounter)
|
2017-11-27 16:36:21 +00:00
|
|
|
goto finish;
|
|
|
|
plan(&ds, &plan_copy, dive, 1, deeper, &cache, true, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
free_dps(&plan_copy);
|
2017-11-27 16:36:21 +00:00
|
|
|
restore_deco_state(save, &ds, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
|
2017-11-27 16:36:21 +00:00
|
|
|
last_segment = cloneDiveplan(original_plan, &plan_copy);
|
2017-12-02 10:29:35 +00:00
|
|
|
last_segment->depth.mm -= delta_depth.mm;
|
|
|
|
last_segment->next->depth.mm -= delta_depth.mm;
|
2017-08-29 09:41:30 +00:00
|
|
|
if (my_instance != instanceCounter)
|
2017-11-27 16:36:21 +00:00
|
|
|
goto finish;
|
|
|
|
plan(&ds, &plan_copy, dive, 1, shallower, &cache, true, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
free_dps(&plan_copy);
|
2017-11-27 16:36:21 +00:00
|
|
|
restore_deco_state(save, &ds, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
|
2017-11-27 16:36:21 +00:00
|
|
|
last_segment = cloneDiveplan(original_plan, &plan_copy);
|
2017-12-02 10:29:35 +00:00
|
|
|
last_segment->next->time += delta_time.seconds;
|
2017-08-29 09:41:30 +00:00
|
|
|
if (my_instance != instanceCounter)
|
2017-11-27 16:36:21 +00:00
|
|
|
goto finish;
|
|
|
|
plan(&ds, &plan_copy, dive, 1, longer, &cache, true, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
free_dps(&plan_copy);
|
2017-11-27 16:36:21 +00:00
|
|
|
restore_deco_state(save, &ds, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
|
2017-11-27 16:36:21 +00:00
|
|
|
last_segment = cloneDiveplan(original_plan, &plan_copy);
|
2017-12-02 10:29:35 +00:00
|
|
|
last_segment->next->time -= delta_time.seconds;
|
2017-08-29 09:41:30 +00:00
|
|
|
if (my_instance != instanceCounter)
|
2017-11-27 16:36:21 +00:00
|
|
|
goto finish;
|
|
|
|
plan(&ds, &plan_copy, dive, 1, shorter, &cache, true, false);
|
2017-09-18 14:10:47 +00:00
|
|
|
free_dps(&plan_copy);
|
2017-11-27 16:36:21 +00:00
|
|
|
restore_deco_state(save, &ds, false);
|
2017-08-29 10:26:00 +00:00
|
|
|
|
2017-09-18 14:10:47 +00:00
|
|
|
char buf[200];
|
2018-02-25 12:51:41 +00:00
|
|
|
sprintf(buf, ", %s: + %d:%02d /%s + %d:%02d /min", qPrintable(tr("Stop times")),
|
|
|
|
FRACTION(analyzeVariations(shallower, original, deeper, qPrintable(depth_units)), 60), qPrintable(depth_units),
|
|
|
|
FRACTION(analyzeVariations(shorter, original, longer, qPrintable(time_units)), 60));
|
2017-11-28 20:14:05 +00:00
|
|
|
|
2020-04-13 12:46:15 +00:00
|
|
|
// By using a signal, we can transport the variations to the main thread.
|
2017-11-27 16:36:21 +00:00
|
|
|
emit variationsComputed(QString(buf));
|
2017-12-02 07:45:41 +00:00
|
|
|
#ifdef DEBUG_STOPVAR
|
2017-11-28 19:23:47 +00:00
|
|
|
printf("\n\n");
|
|
|
|
#endif
|
2017-09-18 14:10:47 +00:00
|
|
|
}
|
2017-11-28 19:23:47 +00:00
|
|
|
finish:
|
|
|
|
free_dps(original_plan);
|
|
|
|
free(original_plan);
|
2017-12-26 21:14:40 +00:00
|
|
|
free(save);
|
|
|
|
free(cache);
|
|
|
|
free(dive);
|
2017-11-28 19:23:47 +00:00
|
|
|
// setRecalc(oldRecalc);
|
2017-08-28 21:59:58 +00:00
|
|
|
}
|
|
|
|
|
2020-04-13 12:46:15 +00:00
|
|
|
void DivePlannerPointsModel::computeVariationsDone(QString variations)
|
|
|
|
{
|
|
|
|
QString notes = QString(displayed_dive.notes);
|
|
|
|
free(displayed_dive.notes);
|
|
|
|
displayed_dive.notes = copy_qstring(notes.replace("VARIATIONS", variations));
|
2020-04-13 13:09:35 +00:00
|
|
|
emit calculatedPlanNotes(QString(displayed_dive.notes));
|
2020-04-13 12:46:15 +00:00
|
|
|
}
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
void DivePlannerPointsModel::createPlan(bool replanCopy)
|
|
|
|
{
|
|
|
|
// Ok, so, here the diveplan creates a dive
|
2017-05-25 22:45:53 +00:00
|
|
|
struct deco_state *cache = NULL;
|
2015-05-28 19:23:49 +00:00
|
|
|
bool oldRecalc = setRecalc(false);
|
|
|
|
removeDeco();
|
|
|
|
createTemporaryPlan();
|
|
|
|
setRecalc(oldRecalc);
|
|
|
|
|
|
|
|
//TODO: C-based function here?
|
2017-08-27 20:49:41 +00:00
|
|
|
struct decostop stoptable[60];
|
2017-11-22 19:42:33 +00:00
|
|
|
plan(&ds_after_previous_dives, &diveplan, &displayed_dive, DECOTIMESTEP, stoptable, &cache, isPlanner(), true);
|
2017-11-27 16:36:21 +00:00
|
|
|
struct diveplan *plan_copy;
|
|
|
|
plan_copy = (struct diveplan *)malloc(sizeof(struct diveplan));
|
|
|
|
lock_planner();
|
|
|
|
cloneDiveplan(&diveplan, plan_copy);
|
|
|
|
unlock_planner();
|
|
|
|
computeVariations(plan_copy, &ds_after_previous_dives);
|
|
|
|
|
2015-06-22 03:24:07 +00:00
|
|
|
free(cache);
|
Undo: make adding of planned dive undo-able
Planned dives were still added by directly calling core code.
This could confuse the undo-machinery, leading to crashes.
Instead, use the proper undo-command. The problem is that as
opposed to the other AddDive-commands, planned dives may
belong to a trip. Thus, the interface to the AddDive command
was changed to respect the divetrip field. Make sure that
the other callers reset that field (actually, it should never
be set). Add a comment describing the perhaps surprising
interface (the passed-in dive, usually displayed dive, is
reset).
Moreover, a dive cloned in the planner is not assigned a
new number. Thus, add an argument to the AddDive-command,
which expresses whether a new number should be generated
for the to-be-added dive.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-09-08 17:58:11 +00:00
|
|
|
|
|
|
|
// Fixup planner notes.
|
|
|
|
if (current_dive && displayed_dive.id == current_dive->id) {
|
2017-03-29 19:25:30 +00:00
|
|
|
// Try to identify old planner output and remove only this part
|
2017-04-19 13:40:59 +00:00
|
|
|
// Treat user provided text as plain text.
|
|
|
|
QTextDocument notesDocument;
|
|
|
|
notesDocument.setHtml(current_dive->notes);
|
|
|
|
QString oldnotes(notesDocument.toPlainText());
|
2019-09-01 15:52:25 +00:00
|
|
|
QString disclaimer = get_planner_disclaimer();
|
|
|
|
int disclaimerMid = disclaimer.indexOf("%s");
|
|
|
|
QString disclaimerBegin, disclaimerEnd;
|
|
|
|
if (disclaimerMid >= 0) {
|
|
|
|
disclaimerBegin = disclaimer.left(disclaimerMid);
|
|
|
|
disclaimerEnd = disclaimer.mid(disclaimerMid + 2);
|
|
|
|
} else {
|
|
|
|
disclaimerBegin = disclaimer;
|
|
|
|
}
|
|
|
|
int disclaimerPositionStart = oldnotes.indexOf(disclaimerBegin);
|
|
|
|
if (disclaimerPositionStart >= 0) {
|
|
|
|
if (oldnotes.indexOf(disclaimerEnd, disclaimerPositionStart) >= 0) {
|
|
|
|
// We found a disclaimer according to the current locale.
|
|
|
|
// Remove the disclaimer and anything after the disclaimer, because
|
|
|
|
// that's supposedly the old planner notes.
|
|
|
|
oldnotes = oldnotes.left(disclaimerPositionStart);
|
|
|
|
}
|
|
|
|
}
|
2017-04-19 13:40:59 +00:00
|
|
|
// Deal with line breaks
|
2017-04-23 11:48:47 +00:00
|
|
|
oldnotes.replace("\n", "<br>");
|
2017-03-29 19:25:30 +00:00
|
|
|
oldnotes.append(displayed_dive.notes);
|
2018-02-28 22:37:09 +00:00
|
|
|
displayed_dive.notes = copy_qstring(oldnotes);
|
2017-03-29 19:25:30 +00:00
|
|
|
// If we save as new create a copy of the dive here
|
Undo: make adding of planned dive undo-able
Planned dives were still added by directly calling core code.
This could confuse the undo-machinery, leading to crashes.
Instead, use the proper undo-command. The problem is that as
opposed to the other AddDive-commands, planned dives may
belong to a trip. Thus, the interface to the AddDive command
was changed to respect the divetrip field. Make sure that
the other callers reset that field (actually, it should never
be set). Add a comment describing the perhaps surprising
interface (the passed-in dive, usually displayed dive, is
reset).
Moreover, a dive cloned in the planner is not assigned a
new number. Thus, add an argument to the AddDive-command,
which expresses whether a new number should be generated
for the to-be-added dive.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-09-08 17:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
setPlanMode(NOTHING);
|
|
|
|
planCreated(); // This signal will exit the profile from planner state. This must be *before* adding the dive,
|
|
|
|
// so that the Undo-commands update the display accordingly (see condition in updateDiveInfo().
|
|
|
|
|
|
|
|
// Now, add or modify the dive.
|
|
|
|
if (!current_dive || displayed_dive.id != current_dive->id) {
|
|
|
|
// we were planning a new dive, not re-planning an existing one
|
|
|
|
displayed_dive.divetrip = nullptr; // Should not be necessary, just in case!
|
2020-04-13 16:07:17 +00:00
|
|
|
#if !defined(SUBSURFACE_TESTING)
|
2019-03-28 16:23:35 +00:00
|
|
|
Command::addDive(&displayed_dive, autogroup, true);
|
2020-04-13 16:07:17 +00:00
|
|
|
#endif // !SUBSURFACE_TESTING
|
2020-04-12 11:39:01 +00:00
|
|
|
} else {
|
|
|
|
copy_events_until(current_dive, &displayed_dive, preserved_until.seconds);
|
|
|
|
if (replanCopy) {
|
|
|
|
// we were planning an old dive and save as a new dive
|
|
|
|
displayed_dive.id = dive_getUniqID(); // Things will break horribly if we create dives with the same id.
|
2020-04-13 16:07:17 +00:00
|
|
|
#if !defined(SUBSURFACE_TESTING)
|
2020-04-12 11:39:01 +00:00
|
|
|
Command::addDive(&displayed_dive, false, false);
|
2020-04-13 16:07:17 +00:00
|
|
|
#endif // !SUBSURFACE_TESTING
|
2020-04-12 11:39:01 +00:00
|
|
|
} else {
|
|
|
|
// we were planning an old dive and rewrite the plan
|
2020-04-13 16:07:17 +00:00
|
|
|
#if !defined(SUBSURFACE_TESTING)
|
2020-04-12 11:39:01 +00:00
|
|
|
Command::replanDive(&displayed_dive);
|
2020-04-13 16:07:17 +00:00
|
|
|
#endif // !SUBSURFACE_TESTING
|
2020-04-12 11:39:01 +00:00
|
|
|
}
|
2015-05-28 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove and clean the diveplan, so we don't delete
|
|
|
|
// the dive by mistake.
|
|
|
|
free_dps(&diveplan);
|
|
|
|
}
|