2017-04-27 18:25:32 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2016-04-05 05:02:03 +00:00
|
|
|
#include "qt-models/diveplotdatamodel.h"
|
|
|
|
#include "core/dive.h"
|
|
|
|
#include "core/profile.h"
|
|
|
|
#include "core/divelist.h"
|
|
|
|
#include "core/color.h"
|
2014-01-14 18:43:58 +00:00
|
|
|
|
2015-06-22 13:42:02 +00:00
|
|
|
DivePlotDataModel::DivePlotDataModel(QObject *parent) :
|
|
|
|
QAbstractTableModel(parent),
|
|
|
|
diveId(0),
|
|
|
|
dcNr(0)
|
2014-01-14 18:43:58 +00:00
|
|
|
{
|
2014-02-09 17:47:56 +00:00
|
|
|
memset(&pInfo, 0, sizeof(pInfo));
|
2014-01-14 18:43:58 +00:00
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
int DivePlotDataModel::columnCount(const QModelIndex &parent) const
|
2014-01-14 18:43:58 +00:00
|
|
|
{
|
2016-03-08 05:27:03 +00:00
|
|
|
Q_UNUSED(parent);
|
2014-01-14 18:43:58 +00:00
|
|
|
return COLUMNS;
|
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
QVariant DivePlotDataModel::data(const QModelIndex &index, int role) const
|
2014-01-14 18:43:58 +00:00
|
|
|
{
|
2016-01-19 18:27:38 +00:00
|
|
|
if ((!index.isValid()) || (index.row() >= pInfo.nr) || pInfo.entry == 0)
|
2014-01-14 18:43:58 +00:00
|
|
|
return QVariant();
|
|
|
|
|
2014-02-04 19:34:16 +00:00
|
|
|
plot_data item = pInfo.entry[index.row()];
|
2014-01-16 04:50:56 +00:00
|
|
|
if (role == Qt::DisplayRole) {
|
|
|
|
switch (index.column()) {
|
2014-02-28 04:09:57 +00:00
|
|
|
case DEPTH:
|
|
|
|
return item.depth;
|
|
|
|
case TIME:
|
|
|
|
return item.sec;
|
|
|
|
case PRESSURE:
|
2017-07-20 21:39:02 +00:00
|
|
|
return item.pressure[0][0];
|
2014-02-28 04:09:57 +00:00
|
|
|
case TEMPERATURE:
|
|
|
|
return item.temperature;
|
|
|
|
case COLOR:
|
|
|
|
return item.velocity;
|
|
|
|
case USERENTERED:
|
|
|
|
return false;
|
|
|
|
case CYLINDERINDEX:
|
Profile support for multiple concurrent pressure sensors
This finally handles multiple cylinder pressures, both overlapping and
consecutive, and it seems to work on the nasty cases I've thrown at it.
Want to just track five different cylinders all at once, without any
pesky gas switch events? Sure, you can do that. It will show five
different gas pressures for your five cylinders, and they will go down
as you breathe down the cylinders.
I obviously don't have any real data for that case, but I do have a test
file with five actual cylinders that all have samples over the whole
course of the dive. The end result looks messy as hell, but what did
you expect?
HOWEVER.
The only way to do this sanely was
- actually make the "struct plot_info" have all the cylinder pressures
(so no "sensor index and pressure" - every cylinder has a pressure for
every plot info entry)
This obviously makes the plot_info much bigger. We used to have
MAX_CYLINDERS be a fairly generous 8, which seems sane. The planning
code made that 8 be 20. That seems questionable. But whatever.
The good news is that the plot-info should hopefully get freed, and
only be allocated one dive at a time, so the fact that it is big and
nasty shouldn't be a scaling issue, though.
- the "populate_pressure_information()" function had to be rewritten
quite a bit. The good news is that it's actually simpler now, although
I would not go so far as to really call it simple. It's still
complicated and suble, but now it explicitly just does one cylinder at
a time.
It *used* to have this insanely complicated "keep track of the pressure
ranges for every cylinder at once". I just couldn't stand that model
and keep my sanity, so it now just tracks one cylinder at a time, and
doesn't have an array of live data, instead the caller will just call
it for each cylinder.
- get rid of some of our hackier stuff, like the code that populates the
plot_info data code with the currently selected cylinder number, and
clears out any other pressures. That obviously does *not* work when you
may not have a single primary cylinder any more.
Now, the above sounds like all good things. Yeah, it mostly is.
BUT.
There's a few big downsides from the above:
- there's no sane way to do this as a series of small changes.
The change to make the plot_info take an array of cylinder pressures
rather than the sensor+pressure model really isn't amenable to "fix up
one use at a time". When you switch over to the new data structure
model, you have to switch over to the new way of populating the
pressure ranges. The two just go hand in hand.
- Some of our code *depended* on the "sensor+pressure" model. I fixed all
the ones I could sanely fix. There was one particular case that I just
couldn't sanely fix, and I didn't care enough about it to do something
insane.
So the only _known_ breakage is the "TankItem" profile widget. That's
the bar at the bottom of the profile that shows which cylinder is in
use right now. You'd think that would be trivial to fix up, and yes it
would be - I could just use the regular model of
firstcyl = explicit_first_cylinder(dive, dc)
.. then iterate over the gas change events to see the others ..
but the problem with the "TankItem" widget is that it does its own
model, and it has thrown away the dive and the dive computer
information. It just doesn't even know. It only knows what cylinders
there are, and the plot_info. And it just used to look at the sensor
number in the plot_info, and be done with that. That number no longer
exists.
- I have tested it, and I think the code is better, but hey, it's a
fairly large patch to some of the more complex code in our code base.
That "interpolate missing pressure fields" code really isn't pretty. It
may be prettier, but..
Anyway, without further ado, here's the patch. No sign-off yet, because I
do think people should look and comment. But I think the patch is fine,
and I'll fix anythign that anybody can find, *except* for that TankItem
thing that I will refuse to touch. That class is ugly. It needs to have
access to the actual dive.
Note how it actually does remove more lines than it adds, and that's
despite added comments etc. The code really is simpler, but there may be
cases in there that need more work.
Known missing pieces that don't currently take advantage of concurrent
cylinder pressure data:
- the momentary SAC rate coloring for dives will need more work
- dive merging (but we expect to generally normally not merge dive
computers, which is the main source of sensor data)
- actually taking advantage of different sensor data from different
dive computers
But most of all: Testing. Lots and lots of testing to find all the
corner cases.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-27 17:17:05 +00:00
|
|
|
return 0;
|
2014-02-28 04:09:57 +00:00
|
|
|
case SENSOR_PRESSURE:
|
2017-07-20 21:39:02 +00:00
|
|
|
return item.pressure[0][0];
|
2014-02-28 04:09:57 +00:00
|
|
|
case INTERPOLATED_PRESSURE:
|
2017-07-20 21:39:02 +00:00
|
|
|
return item.pressure[0][1];
|
2014-02-28 04:09:57 +00:00
|
|
|
case CEILING:
|
|
|
|
return item.ceiling;
|
|
|
|
case SAC:
|
|
|
|
return item.sac;
|
|
|
|
case PN2:
|
2014-09-15 12:55:20 +00:00
|
|
|
return item.pressures.n2;
|
2014-02-28 04:09:57 +00:00
|
|
|
case PHE:
|
2014-09-15 12:55:20 +00:00
|
|
|
return item.pressures.he;
|
2014-02-28 04:09:57 +00:00
|
|
|
case PO2:
|
2014-09-15 12:55:20 +00:00
|
|
|
return item.pressures.o2;
|
2015-01-05 07:20:26 +00:00
|
|
|
case O2SETPOINT:
|
|
|
|
return item.o2setpoint.mbar / 1000.0;
|
2015-01-20 18:13:53 +00:00
|
|
|
case CCRSENSOR1:
|
|
|
|
return item.o2sensor[0].mbar / 1000.0;
|
|
|
|
case CCRSENSOR2:
|
|
|
|
return item.o2sensor[1].mbar / 1000.0;
|
|
|
|
case CCRSENSOR3:
|
|
|
|
return item.o2sensor[2].mbar / 1000.0;
|
2014-02-28 04:09:57 +00:00
|
|
|
case HEARTBEAT:
|
|
|
|
return item.heartbeat;
|
2014-09-15 12:09:00 +00:00
|
|
|
case AMBPRESSURE:
|
|
|
|
return AMB_PERCENTAGE;
|
|
|
|
case GFLINE:
|
|
|
|
return item.gfline;
|
2014-12-30 22:27:39 +00:00
|
|
|
case INSTANT_MEANDEPTH:
|
|
|
|
return item.running_sum;
|
2014-01-14 18:43:58 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-21 17:31:56 +00:00
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
if (role == Qt::DisplayRole && index.column() >= TISSUE_1 && index.column() <= TISSUE_16) {
|
|
|
|
return item.ceilings[index.column() - TISSUE_1];
|
2014-01-21 17:31:56 +00:00
|
|
|
}
|
|
|
|
|
2014-09-15 12:09:00 +00:00
|
|
|
if (role == Qt::DisplayRole && index.column() >= PERCENTAGE_1 && index.column() <= PERCENTAGE_16) {
|
|
|
|
return item.percentages[index.column() - PERCENTAGE_1];
|
|
|
|
}
|
|
|
|
|
2014-01-16 04:50:56 +00:00
|
|
|
if (role == Qt::BackgroundRole) {
|
|
|
|
switch (index.column()) {
|
2014-02-28 04:09:57 +00:00
|
|
|
case COLOR:
|
|
|
|
return getColor((color_indice_t)(VELOCITY_COLORS_START_IDX + item.velocity));
|
2014-01-14 18:43:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
const plot_info &DivePlotDataModel::data() const
|
2014-01-21 15:27:08 +00:00
|
|
|
{
|
2014-02-04 19:34:16 +00:00
|
|
|
return pInfo;
|
2014-01-21 15:27:08 +00:00
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
int DivePlotDataModel::rowCount(const QModelIndex &parent) const
|
2014-01-14 18:43:58 +00:00
|
|
|
{
|
2016-03-08 05:27:03 +00:00
|
|
|
Q_UNUSED(parent);
|
2014-02-04 19:34:16 +00:00
|
|
|
return pInfo.nr;
|
2014-01-14 18:43:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QVariant DivePlotDataModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
|
|
{
|
|
|
|
if (orientation != Qt::Horizontal)
|
|
|
|
return QVariant();
|
|
|
|
|
|
|
|
if (role != Qt::DisplayRole)
|
|
|
|
return QVariant();
|
|
|
|
|
2014-01-16 04:50:56 +00:00
|
|
|
switch (section) {
|
2014-02-28 04:09:57 +00:00
|
|
|
case DEPTH:
|
|
|
|
return tr("Depth");
|
|
|
|
case TIME:
|
|
|
|
return tr("Time");
|
|
|
|
case PRESSURE:
|
|
|
|
return tr("Pressure");
|
|
|
|
case TEMPERATURE:
|
|
|
|
return tr("Temperature");
|
|
|
|
case COLOR:
|
|
|
|
return tr("Color");
|
|
|
|
case USERENTERED:
|
2014-07-11 17:39:05 +00:00
|
|
|
return tr("User entered");
|
2014-02-28 04:09:57 +00:00
|
|
|
case CYLINDERINDEX:
|
2014-07-11 17:39:05 +00:00
|
|
|
return tr("Cylinder index");
|
2014-02-28 04:09:57 +00:00
|
|
|
case SENSOR_PRESSURE:
|
2014-06-22 14:41:44 +00:00
|
|
|
return tr("Pressure S");
|
2014-02-28 04:09:57 +00:00
|
|
|
case INTERPOLATED_PRESSURE:
|
|
|
|
return tr("Pressure I");
|
|
|
|
case CEILING:
|
|
|
|
return tr("Ceiling");
|
|
|
|
case SAC:
|
|
|
|
return tr("SAC");
|
|
|
|
case PN2:
|
2014-06-22 14:41:44 +00:00
|
|
|
return tr("pN₂");
|
2014-02-28 04:09:57 +00:00
|
|
|
case PHE:
|
2014-06-22 14:41:44 +00:00
|
|
|
return tr("pHe");
|
2014-02-28 04:09:57 +00:00
|
|
|
case PO2:
|
2014-06-22 14:41:44 +00:00
|
|
|
return tr("pO₂");
|
2015-01-05 07:20:26 +00:00
|
|
|
case O2SETPOINT:
|
|
|
|
return tr("Setpoint");
|
2015-01-20 18:13:53 +00:00
|
|
|
case CCRSENSOR1:
|
2015-01-27 15:06:13 +00:00
|
|
|
return tr("Sensor 1");
|
2015-01-20 18:13:53 +00:00
|
|
|
case CCRSENSOR2:
|
2015-01-27 15:06:13 +00:00
|
|
|
return tr("Sensor 2");
|
2015-01-20 18:13:53 +00:00
|
|
|
case CCRSENSOR3:
|
2015-01-27 15:06:13 +00:00
|
|
|
return tr("Sensor 3");
|
2014-09-15 12:09:00 +00:00
|
|
|
case AMBPRESSURE:
|
|
|
|
return tr("Ambient pressure");
|
2014-12-30 22:16:25 +00:00
|
|
|
case HEARTBEAT:
|
2015-01-26 06:51:18 +00:00
|
|
|
return tr("Heart rate");
|
2014-12-30 22:17:23 +00:00
|
|
|
case GFLINE:
|
2015-01-05 13:53:02 +00:00
|
|
|
return tr("Gradient factor");
|
2014-12-30 22:27:39 +00:00
|
|
|
case INSTANT_MEANDEPTH:
|
2015-01-26 06:51:19 +00:00
|
|
|
return tr("Mean depth @ s");
|
2014-01-14 18:43:58 +00:00
|
|
|
}
|
2014-02-28 04:09:57 +00:00
|
|
|
if (role == Qt::DisplayRole && section >= TISSUE_1 && section <= TISSUE_16) {
|
2014-01-21 17:31:56 +00:00
|
|
|
return QString("Ceiling: %1").arg(section - TISSUE_1);
|
|
|
|
}
|
2014-09-15 12:09:00 +00:00
|
|
|
if (role == Qt::DisplayRole && section >= PERCENTAGE_1 && section <= PERCENTAGE_16) {
|
|
|
|
return QString("Tissue: %1").arg(section - PERCENTAGE_1);
|
|
|
|
}
|
2014-01-14 18:43:58 +00:00
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivePlotDataModel::clear()
|
|
|
|
{
|
2014-01-16 04:50:56 +00:00
|
|
|
if (rowCount() != 0) {
|
2014-01-14 18:43:58 +00:00
|
|
|
beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
|
2014-02-10 16:41:59 +00:00
|
|
|
pInfo.nr = 0;
|
2016-01-19 18:27:38 +00:00
|
|
|
free(pInfo.entry);
|
|
|
|
pInfo.entry = 0;
|
2014-02-17 22:15:40 +00:00
|
|
|
diveId = -1;
|
|
|
|
dcNr = -1;
|
2014-01-14 18:43:58 +00:00
|
|
|
endRemoveRows();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
void DivePlotDataModel::setDive(dive *d, const plot_info &info)
|
2014-01-14 18:43:58 +00:00
|
|
|
{
|
|
|
|
clear();
|
2014-02-11 00:27:05 +00:00
|
|
|
Q_ASSERT(d != NULL);
|
2014-01-21 16:05:29 +00:00
|
|
|
diveId = d->id;
|
2014-02-11 17:55:14 +00:00
|
|
|
dcNr = dc_number;
|
2016-01-19 18:27:38 +00:00
|
|
|
free(pInfo.entry);
|
2014-02-04 19:34:16 +00:00
|
|
|
pInfo = info;
|
2016-01-19 18:27:38 +00:00
|
|
|
pInfo.entry = (struct plot_data *)malloc(sizeof(struct plot_data) * pInfo.nr);
|
|
|
|
memcpy(pInfo.entry, info.entry, sizeof(plot_data) * pInfo.nr);
|
2014-02-28 04:09:57 +00:00
|
|
|
beginInsertRows(QModelIndex(), 0, pInfo.nr - 1);
|
2014-01-14 18:43:58 +00:00
|
|
|
endInsertRows();
|
|
|
|
}
|
2014-01-21 16:05:29 +00:00
|
|
|
|
2014-03-18 18:26:29 +00:00
|
|
|
unsigned int DivePlotDataModel::dcShown() const
|
2014-02-11 17:55:14 +00:00
|
|
|
{
|
|
|
|
return dcNr;
|
|
|
|
}
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
#define MAX_PPGAS_FUNC(GAS, GASFUNC) \
|
|
|
|
double DivePlotDataModel::GASFUNC() \
|
|
|
|
{ \
|
|
|
|
double ret = -1; \
|
|
|
|
for (int i = 0, count = rowCount(); i < count; i++) { \
|
2014-09-15 12:55:20 +00:00
|
|
|
if (pInfo.entry[i].pressures.GAS > ret) \
|
|
|
|
ret = pInfo.entry[i].pressures.GAS; \
|
2014-02-28 04:09:57 +00:00
|
|
|
} \
|
|
|
|
return ret; \
|
|
|
|
}
|
2014-01-27 17:14:42 +00:00
|
|
|
|
2015-01-20 18:13:53 +00:00
|
|
|
#define MAX_SENSOR_GAS_FUNC(GASFUNC) \
|
|
|
|
double DivePlotDataModel::GASFUNC() /* CCR: This function finds the largest measured po2 value */ \
|
|
|
|
{ /* by scanning the readings from the three individual o2 sensors. */ \
|
|
|
|
double ret = -1; /* This is used for scaling the Y-axis for partial pressures */ \
|
|
|
|
for (int s = 0; s < 3; s++) { /* when displaying the graphs for individual o2 sensors */ \
|
|
|
|
for (int i = 0, count = rowCount(); i < count; i++) { /* POTENTIAL PROBLEM: the '3' (no_sensors) is hard-coded here */\
|
|
|
|
if (pInfo.entry[i].o2sensor[s].mbar > ret) \
|
|
|
|
ret = pInfo.entry[i].o2sensor[s].mbar; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
return (ret / 1000.0); /* mbar -> bar conversion */ \
|
|
|
|
}
|
|
|
|
|
2014-09-15 12:55:20 +00:00
|
|
|
MAX_PPGAS_FUNC(he, pheMax);
|
|
|
|
MAX_PPGAS_FUNC(n2, pn2Max);
|
|
|
|
MAX_PPGAS_FUNC(o2, po2Max);
|
2015-01-20 18:13:53 +00:00
|
|
|
MAX_SENSOR_GAS_FUNC(CCRMax);
|
2014-01-27 17:14:42 +00:00
|
|
|
|
|
|
|
void DivePlotDataModel::emitDataChanged()
|
|
|
|
{
|
|
|
|
emit dataChanged(QModelIndex(), QModelIndex());
|
|
|
|
}
|
2014-02-04 19:34:16 +00:00
|
|
|
|
2016-02-06 04:45:18 +00:00
|
|
|
#ifndef SUBSURFACE_MOBILE
|
2014-02-04 19:34:16 +00:00
|
|
|
void DivePlotDataModel::calculateDecompression()
|
|
|
|
{
|
2014-07-03 21:34:24 +00:00
|
|
|
struct divecomputer *dc = select_dc(&displayed_dive);
|
|
|
|
init_decompression(&displayed_dive);
|
|
|
|
calculate_deco_information(&displayed_dive, dc, &pInfo, false);
|
2014-02-28 04:09:57 +00:00
|
|
|
dataChanged(index(0, CEILING), index(pInfo.nr - 1, TISSUE_16));
|
2014-02-04 19:34:16 +00:00
|
|
|
}
|
2016-02-06 04:45:18 +00:00
|
|
|
#endif
|