mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-17 21:26:16 +00:00
The partial-pressure-axis was the only DiveCartesianAxis child that had its own code to set the bounds. The bounds of all other axes were set in plotDive(). For consistency, do this here as well. Thus, the whole class can be removed. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
547 lines
21 KiB
C++
547 lines
21 KiB
C++
// SPDX-License-Identifier: GPL-2.0
|
|
#include "profilescene.h"
|
|
#include "diveeventitem.h"
|
|
#include "divecartesianaxis.h"
|
|
#include "divepercentageitem.h"
|
|
#include "divepixmapcache.h"
|
|
#include "diveprofileitem.h"
|
|
#include "divetextitem.h"
|
|
#include "tankitem.h"
|
|
#include "core/device.h"
|
|
#include "core/event.h"
|
|
#include "core/pref.h"
|
|
#include "core/profile.h"
|
|
#include "core/qthelper.h" // for decoMode()
|
|
#include "core/subsurface-string.h"
|
|
#include "core/settings/qPrefDisplay.h"
|
|
#include "qt-models/diveplotdatamodel.h"
|
|
#include "qt-models/diveplannermodel.h"
|
|
|
|
static const double diveComputerTextBorder = 1.0;
|
|
|
|
template<typename T, class... Args>
|
|
T *ProfileScene::createItem(const DiveCartesianAxis &vAxis, int vColumn, int z, Args&&... args)
|
|
{
|
|
T *res = new T(*dataModel, *timeAxis, DivePlotDataModel::TIME, vAxis, vColumn,
|
|
std::forward<Args>(args)...);
|
|
res->setZValue(static_cast<double>(z));
|
|
profileItems.push_back(res);
|
|
return res;
|
|
}
|
|
|
|
PartialPressureGasItem *ProfileScene::createPPGas(int column, color_index_t color, color_index_t colorAlert,
|
|
const double *thresholdSettingsMin, const double *thresholdSettingsMax)
|
|
{
|
|
PartialPressureGasItem *item = createItem<PartialPressureGasItem>(*gasYAxis, column, 99, dpr);
|
|
item->setThresholdSettingsKey(thresholdSettingsMin, thresholdSettingsMax);
|
|
item->setColors(getColor(color, isGrayscale), getColor(colorAlert, isGrayscale));
|
|
return item;
|
|
}
|
|
|
|
ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
|
d(nullptr),
|
|
dc(-1),
|
|
dpr(dpr),
|
|
printMode(printMode),
|
|
isGrayscale(isGrayscale),
|
|
maxtime(-1),
|
|
maxdepth(-1),
|
|
dataModel(new DivePlotDataModel(this)),
|
|
profileYAxis(new DepthAxis(DiveCartesianAxis::Position::Left, 3, 0, TIME_GRID, dpr, 1.0, printMode, isGrayscale, *this)),
|
|
gasYAxis(new DiveCartesianAxis(DiveCartesianAxis::Position::Right, 1, 2, TIME_GRID, dpr, 0.7, printMode, isGrayscale, *this)),
|
|
temperatureAxis(new TemperatureAxis(DiveCartesianAxis::Position::Right, 3, 0, TIME_GRID, dpr, 1.0, printMode, isGrayscale, *this)),
|
|
timeAxis(new TimeAxis(DiveCartesianAxis::Position::Bottom, 2, 2, TIME_GRID, dpr, 1.0, printMode, isGrayscale, *this)),
|
|
cylinderPressureAxis(new DiveCartesianAxis(DiveCartesianAxis::Position::Right, 4, 0, TIME_GRID, dpr, 1.0, printMode, isGrayscale, *this)),
|
|
heartBeatAxis(new DiveCartesianAxis(DiveCartesianAxis::Position::Left, 3, 0, HR_AXIS, dpr, 0.7, printMode, isGrayscale, *this)),
|
|
percentageAxis(new DiveCartesianAxis(DiveCartesianAxis::Position::Right, 2, 0, TIME_GRID, dpr, 0.7, printMode, isGrayscale, *this)),
|
|
diveProfileItem(createItem<DiveProfileItem>(*profileYAxis, DivePlotDataModel::DEPTH, 0, dpr)),
|
|
temperatureItem(createItem<DiveTemperatureItem>(*temperatureAxis, DivePlotDataModel::TEMPERATURE, 1, dpr)),
|
|
meanDepthItem(createItem<DiveMeanDepthItem>(*profileYAxis, DivePlotDataModel::INSTANT_MEANDEPTH, 1, dpr)),
|
|
gasPressureItem(createItem<DiveGasPressureItem>(*cylinderPressureAxis, DivePlotDataModel::TEMPERATURE, 1, dpr)),
|
|
diveComputerText(new DiveTextItem(dpr, 1.0, Qt::AlignRight | Qt::AlignTop, nullptr)),
|
|
reportedCeiling(createItem<DiveReportedCeiling>(*profileYAxis, DivePlotDataModel::CEILING, 1, dpr)),
|
|
pn2GasItem(createPPGas(DivePlotDataModel::PN2, PN2, PN2_ALERT, NULL, &prefs.pp_graphs.pn2_threshold)),
|
|
pheGasItem(createPPGas(DivePlotDataModel::PHE, PHE, PHE_ALERT, NULL, &prefs.pp_graphs.phe_threshold)),
|
|
po2GasItem(createPPGas(DivePlotDataModel::PO2, PO2, PO2_ALERT, &prefs.pp_graphs.po2_threshold_min, &prefs.pp_graphs.po2_threshold_max)),
|
|
o2SetpointGasItem(createPPGas(DivePlotDataModel::O2SETPOINT, O2SETPOINT, PO2_ALERT, &prefs.pp_graphs.po2_threshold_min, &prefs.pp_graphs.po2_threshold_max)),
|
|
ccrsensor1GasItem(createPPGas(DivePlotDataModel::CCRSENSOR1, CCRSENSOR1, PO2_ALERT, &prefs.pp_graphs.po2_threshold_min, &prefs.pp_graphs.po2_threshold_max)),
|
|
ccrsensor2GasItem(createPPGas(DivePlotDataModel::CCRSENSOR2, CCRSENSOR2, PO2_ALERT, &prefs.pp_graphs.po2_threshold_min, &prefs.pp_graphs.po2_threshold_max)),
|
|
ccrsensor3GasItem(createPPGas(DivePlotDataModel::CCRSENSOR3, CCRSENSOR3, PO2_ALERT, &prefs.pp_graphs.po2_threshold_min, &prefs.pp_graphs.po2_threshold_max)),
|
|
ocpo2GasItem(createPPGas(DivePlotDataModel::SCR_OC_PO2, SCR_OCPO2, PO2_ALERT, &prefs.pp_graphs.po2_threshold_min, &prefs.pp_graphs.po2_threshold_max)),
|
|
diveCeiling(createItem<DiveCalculatedCeiling>(*profileYAxis, DivePlotDataModel::CEILING, 1, dpr)),
|
|
decoModelParameters(new DiveTextItem(dpr, 1.0, Qt::AlignHCenter | Qt::AlignTop, nullptr)),
|
|
heartBeatItem(createItem<DiveHeartrateItem>(*heartBeatAxis, DivePlotDataModel::HEARTBEAT, 1, dpr)),
|
|
percentageItem(new DivePercentageItem(*timeAxis, *percentageAxis, dpr)),
|
|
tankItem(new TankItem(*timeAxis, dpr)),
|
|
pixmaps(getDivePixmaps(dpr))
|
|
{
|
|
init_plot_info(&plotInfo);
|
|
|
|
setSceneRect(0, 0, 100, 100);
|
|
setItemIndexMethod(QGraphicsScene::NoIndex);
|
|
|
|
// Initialize axes. Perhaps part of this should be moved down to the axes code?
|
|
profileYAxis->setOrientation(DiveCartesianAxis::TopToBottom);
|
|
|
|
gasYAxis->setOrientation(DiveCartesianAxis::BottomToTop);
|
|
|
|
#ifndef SUBSURFACE_MOBILE
|
|
heartBeatAxis->setOrientation(DiveCartesianAxis::BottomToTop);
|
|
|
|
percentageAxis->setOrientation(DiveCartesianAxis::BottomToTop);
|
|
#endif
|
|
|
|
temperatureAxis->setOrientation(DiveCartesianAxis::BottomToTop);
|
|
|
|
cylinderPressureAxis->setOrientation(DiveCartesianAxis::BottomToTop);
|
|
|
|
heartBeatAxis->setTextVisible(true);
|
|
heartBeatAxis->setLinesVisible(true);
|
|
percentageAxis->setTextVisible(true);
|
|
percentageAxis->setLinesVisible(true);
|
|
|
|
temperatureAxis->setTextVisible(false);
|
|
temperatureAxis->setLinesVisible(false);
|
|
cylinderPressureAxis->setTextVisible(false);
|
|
cylinderPressureAxis->setLinesVisible(false);
|
|
timeAxis->setLinesVisible(true);
|
|
profileYAxis->setLinesVisible(true);
|
|
gasYAxis->setZValue(timeAxis->zValue() + 1);
|
|
tankItem->setZValue(100);
|
|
|
|
// These axes are not locale-dependent. Set their scale factor once here.
|
|
timeAxis->setTransform(1.0/60.0);
|
|
heartBeatAxis->setTransform(1.0);
|
|
gasYAxis->setTransform(1.0); // Non-metric countries likewise use bar (disguised as "percentage") for partial pressure.
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
DiveCalculatedTissue *tissueItem = createItem<DiveCalculatedTissue>(*profileYAxis, DivePlotDataModel::TISSUE_1 + i, i + 1, dpr);
|
|
allTissues.append(tissueItem);
|
|
}
|
|
|
|
percentageItem->setZValue(1.0);
|
|
|
|
// Add items to scene
|
|
addItem(diveComputerText);
|
|
addItem(tankItem);
|
|
addItem(decoModelParameters);
|
|
addItem(profileYAxis);
|
|
addItem(gasYAxis);
|
|
addItem(temperatureAxis);
|
|
addItem(timeAxis);
|
|
addItem(cylinderPressureAxis);
|
|
addItem(percentageAxis);
|
|
addItem(heartBeatAxis);
|
|
addItem(percentageItem);
|
|
|
|
for (AbstractProfilePolygonItem *item: profileItems)
|
|
addItem(item);
|
|
}
|
|
|
|
ProfileScene::~ProfileScene()
|
|
{
|
|
free_plot_info_data(&plotInfo);
|
|
}
|
|
|
|
void ProfileScene::clear()
|
|
{
|
|
dataModel->clear();
|
|
|
|
for (AbstractProfilePolygonItem *item: profileItems)
|
|
item->clear();
|
|
|
|
// the events will have connected slots which can fire after
|
|
// the dive and its data have been deleted - so explictly delete
|
|
// the DiveEventItems
|
|
qDeleteAll(eventItems);
|
|
eventItems.clear();
|
|
}
|
|
|
|
static bool ppGraphsEnabled()
|
|
{
|
|
return prefs.pp_graphs.po2 || prefs.pp_graphs.pn2 || prefs.pp_graphs.phe;
|
|
}
|
|
|
|
// Update visibility of non-interactive chart features according to preferences
|
|
void ProfileScene::updateVisibility(bool diveHasHeartBeat)
|
|
{
|
|
#ifndef SUBSURFACE_MOBILE
|
|
pn2GasItem->setVisible(prefs.pp_graphs.pn2);
|
|
po2GasItem->setVisible(prefs.pp_graphs.po2);
|
|
pheGasItem->setVisible(prefs.pp_graphs.phe);
|
|
|
|
const struct divecomputer *currentdc = d ? get_dive_dc_const(d, dc) : nullptr;
|
|
bool setpointflag = currentdc && currentdc->divemode == CCR && prefs.pp_graphs.po2;
|
|
bool sensorflag = setpointflag && prefs.show_ccr_sensors;
|
|
o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint);
|
|
ccrsensor1GasItem->setVisible(sensorflag);
|
|
ccrsensor2GasItem->setVisible(currentdc && sensorflag && currentdc->no_o2sensors > 1);
|
|
ccrsensor3GasItem->setVisible(currentdc && sensorflag && currentdc->no_o2sensors > 2);
|
|
ocpo2GasItem->setVisible(currentdc && currentdc->divemode == PSCR && prefs.show_scr_ocpo2);
|
|
|
|
heartBeatItem->setVisible(prefs.hrgraph && diveHasHeartBeat);
|
|
#endif
|
|
diveCeiling->setVisible(prefs.calcceiling);
|
|
decoModelParameters->setVisible(prefs.decoinfo);
|
|
#ifndef SUBSURFACE_MOBILE
|
|
for (DiveCalculatedTissue *tissue: allTissues)
|
|
tissue->setVisible(prefs.calcalltissues && prefs.calcceiling);
|
|
percentageItem->setVisible(prefs.percentagegraph);
|
|
#endif
|
|
meanDepthItem->setVisible(prefs.show_average_depth);
|
|
reportedCeiling->setVisible(prefs.dcceiling);
|
|
tankItem->setVisible(prefs.tankbar);
|
|
}
|
|
|
|
void ProfileScene::resize(QSizeF size)
|
|
{
|
|
setSceneRect(QRectF(QPointF(), size));
|
|
}
|
|
|
|
// Helper templates to determine slope and intersect of a linear function.
|
|
// The function arguments are supposed to be integral types.
|
|
template<typename Func>
|
|
static auto intercept(Func f)
|
|
{
|
|
return f(0);
|
|
}
|
|
template<typename Func>
|
|
static auto slope(Func f)
|
|
{
|
|
return f(1) - f(0);
|
|
}
|
|
|
|
// Helper structure for laying out secondary plots.
|
|
struct VerticalAxisLayout {
|
|
DiveCartesianAxis *axis;
|
|
double height;
|
|
bool visible;
|
|
};
|
|
|
|
void ProfileScene::updateAxes(bool instant, bool diveHasHeartBeat)
|
|
{
|
|
int animSpeed = instant || printMode ? 0 : qPrefDisplay::animation_speed();
|
|
|
|
// Calculate left and right border needed for the axes.
|
|
// viz. the depth axis to the left and the partial pressure axis to the right.
|
|
// Thus, calculating the "border" of the graph is trivial.
|
|
double leftBorder = profileYAxis->width();
|
|
if (prefs.hrgraph)
|
|
leftBorder = std::max(leftBorder, heartBeatAxis->width());
|
|
double rightWidth = ppGraphsEnabled() ? gasYAxis->width() : 0.0;
|
|
double rightBorder = sceneRect().width() - rightWidth;
|
|
double width = rightBorder - leftBorder;
|
|
|
|
if (width <= 10.0 * dpr)
|
|
return clear();
|
|
|
|
// Place the fixed dive computer text at the bottom
|
|
double bottomBorder = sceneRect().height() - diveComputerText->height() - 2.0 * dpr * diveComputerTextBorder;
|
|
diveComputerText->setPos(0.0, bottomBorder + dpr * diveComputerTextBorder);
|
|
|
|
double topBorder = 0.0;
|
|
|
|
// show the deco model parameters at the top in the center
|
|
if (prefs.decoinfo) {
|
|
decoModelParameters->setPos(leftBorder + width / 2.0, topBorder);
|
|
topBorder += decoModelParameters->height();
|
|
}
|
|
|
|
bottomBorder -= timeAxis->height();
|
|
timeAxis->animateChangeLine(QRectF(leftBorder, topBorder, width, bottomBorder - topBorder), animSpeed);
|
|
|
|
if (prefs.tankbar) {
|
|
bottomBorder -= tankItem->height();
|
|
// Note: we set x to 0.0, because the tank item uses the timeAxis to set the x-coordinate.
|
|
tankItem->setPos(0.0, bottomBorder);
|
|
}
|
|
|
|
double height = bottomBorder - topBorder;
|
|
if (height <= 50.0 * dpr)
|
|
return clear();
|
|
|
|
// The rest is laid out dynamically. Give at least 50% to the actual profile.
|
|
// The max heights are given for DPR=1, i.e. a ca. 800x600 pixels profile.
|
|
const double minProfileFraction = 0.5;
|
|
VerticalAxisLayout secondaryAxes[] = {
|
|
// Note: axes are listed from bottom to top, since they are added that way.
|
|
{ heartBeatAxis, 75.0, prefs.hrgraph && diveHasHeartBeat },
|
|
{ percentageAxis, 50.0, prefs.percentagegraph },
|
|
{ gasYAxis, 75.0, ppGraphsEnabled() },
|
|
{ temperatureAxis, 50.0, true },
|
|
};
|
|
|
|
// A loop is probably easier to read than std::accumulate.
|
|
double totalSecondaryHeight = 0.0;
|
|
for (const VerticalAxisLayout &l: secondaryAxes) {
|
|
if (l.visible)
|
|
totalSecondaryHeight += l.height * dpr;
|
|
}
|
|
|
|
if (totalSecondaryHeight > height * minProfileFraction) {
|
|
// Use 50% for the profile and the rest for the remaining graphs, scaled by their maximum height.
|
|
double remainingSpace = height * minProfileFraction;
|
|
for (VerticalAxisLayout &l: secondaryAxes)
|
|
l.height *= remainingSpace / totalSecondaryHeight;
|
|
}
|
|
|
|
for (const VerticalAxisLayout &l: secondaryAxes) {
|
|
l.axis->setVisible(l.visible);
|
|
if (!l.visible)
|
|
continue;
|
|
bottomBorder -= l.height * dpr;
|
|
l.axis->animateChangeLine(QRectF(leftBorder, bottomBorder, width, l.height * dpr), animSpeed);
|
|
}
|
|
|
|
height = bottomBorder - topBorder;
|
|
profileYAxis->animateChangeLine(QRectF(leftBorder, topBorder, width, height), animSpeed);
|
|
|
|
// The cylinders are displayed in the 24-80% region of the profile
|
|
cylinderPressureAxis->animateChangeLine(QRectF(leftBorder, topBorder + 0.24 * height, width, 0.56 * height), animSpeed);
|
|
|
|
// Set scale factors depending on locale.
|
|
// The conversion calls, such as mm_to_feet(), will be optimized away.
|
|
profileYAxis->setTransform(prefs.units.length == units::METERS ? 0.001 : slope(mm_to_feet));
|
|
cylinderPressureAxis->setTransform(prefs.units.pressure == units::BAR ? 0.001 : slope(mbar_to_PSI));
|
|
// Temperature is special: this is not a linear transformation, but requires a shift of origin.
|
|
if (prefs.units.temperature == units::CELSIUS)
|
|
temperatureAxis->setTransform(slope(mkelvin_to_C), intercept(mkelvin_to_C));
|
|
else
|
|
temperatureAxis->setTransform(slope(mkelvin_to_F), intercept(mkelvin_to_F));
|
|
}
|
|
|
|
bool ProfileScene::isPointOutOfBoundaries(const QPointF &point) const
|
|
{
|
|
double xpos = timeAxis->valueAt(point);
|
|
double ypos = profileYAxis->valueAt(point);
|
|
return xpos > timeAxis->maximum() ||
|
|
xpos < timeAxis->minimum() ||
|
|
ypos > profileYAxis->maximum() ||
|
|
ypos < profileYAxis->minimum();
|
|
}
|
|
|
|
void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsModel *plannerModel, bool inPlanner, bool instant, bool calcMax)
|
|
{
|
|
d = dIn;
|
|
dc = dcIn;
|
|
if (!d) {
|
|
clear();
|
|
return;
|
|
}
|
|
|
|
if (!plannerModel) {
|
|
if (decoMode(false) == VPMB)
|
|
decoModelParameters->set(QString("VPM-B +%1").arg(prefs.vpmb_conservatism), getColor(PRESSURE_TEXT));
|
|
else
|
|
decoModelParameters->set(QString("GF %1/%2").arg(prefs.gflow).arg(prefs.gfhigh), getColor(PRESSURE_TEXT));
|
|
} else {
|
|
struct diveplan &diveplan = plannerModel->getDiveplan();
|
|
if (decoMode(inPlanner) == VPMB)
|
|
decoModelParameters->set(QString("VPM-B +%1").arg(diveplan.vpmb_conservatism), getColor(PRESSURE_TEXT));
|
|
else
|
|
decoModelParameters->set(QString("GF %1/%2").arg(diveplan.gflow).arg(diveplan.gfhigh), getColor(PRESSURE_TEXT));
|
|
}
|
|
|
|
const struct divecomputer *currentdc = get_dive_dc_const(d, dc);
|
|
if (!currentdc || !currentdc->samples)
|
|
return;
|
|
|
|
int animSpeed = instant || printMode ? 0 : qPrefDisplay::animation_speed();
|
|
|
|
bool setpointflag = (currentdc->divemode == CCR) && prefs.pp_graphs.po2;
|
|
bool sensorflag = setpointflag && prefs.show_ccr_sensors;
|
|
o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint);
|
|
ccrsensor1GasItem->setVisible(sensorflag);
|
|
ccrsensor2GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 1));
|
|
ccrsensor3GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 2));
|
|
ocpo2GasItem->setVisible((currentdc->divemode == PSCR) && prefs.show_scr_ocpo2);
|
|
|
|
// A non-null planner_ds signals to create_plot_info_new that the dive is currently planned.
|
|
struct deco_state *planner_ds = inPlanner && plannerModel ? &plannerModel->final_deco_state : nullptr;
|
|
|
|
/* This struct holds all the data that's about to be plotted.
|
|
* I'm not sure this is the best approach ( but since we are
|
|
* interpolating some points of the Dive, maybe it is... )
|
|
* The Calculation of the points should be done per graph,
|
|
* so I'll *not* calculate everything if something is not being
|
|
* shown.
|
|
* create_plot_info_new() automatically frees old plot data.
|
|
*/
|
|
create_plot_info_new(d, get_dive_dc_const(d, dc), &plotInfo, !calcMax, planner_ds);
|
|
|
|
bool hasHeartBeat = plotInfo.maxhr;
|
|
updateVisibility(hasHeartBeat);
|
|
updateAxes(instant, hasHeartBeat);
|
|
|
|
int newMaxtime = get_maxtime(&plotInfo);
|
|
if (calcMax || newMaxtime > maxtime)
|
|
maxtime = newMaxtime;
|
|
|
|
/* Only update the max. depth if it's bigger than the current ones
|
|
* when we are dragging the handler to plan / add dive.
|
|
* otherwhise, update normally.
|
|
*/
|
|
int newMaxDepth = get_maxdepth(&plotInfo);
|
|
if (!calcMax) {
|
|
if (maxdepth < newMaxDepth) {
|
|
maxdepth = newMaxDepth;
|
|
}
|
|
} else {
|
|
maxdepth = newMaxDepth;
|
|
}
|
|
|
|
dataModel->setDive(plotInfo);
|
|
|
|
// It seems that I'll have a lot of boilerplate setting the model / axis for
|
|
// each item, I'll mostly like to fix this in the future, but I'll keep at this for now.
|
|
profileYAxis->setBounds(0.0, maxdepth);
|
|
profileYAxis->updateTicks(animSpeed);
|
|
|
|
temperatureAxis->setBounds(plotInfo.mintemp,
|
|
plotInfo.maxtemp - plotInfo.mintemp > 2000 ? plotInfo.maxtemp : plotInfo.mintemp + 2000);
|
|
|
|
if (hasHeartBeat) {
|
|
heartBeatAxis->setBounds(plotInfo.minhr, plotInfo.maxhr);
|
|
heartBeatAxis->updateTicks(animSpeed);
|
|
}
|
|
|
|
percentageAxis->setBounds(0, 100);
|
|
percentageAxis->setVisible(false);
|
|
percentageAxis->updateTicks(animSpeed);
|
|
|
|
if (calcMax)
|
|
timeAxis->setBounds(0.0, maxtime);
|
|
|
|
timeAxis->updateTicks(animSpeed);
|
|
cylinderPressureAxis->setBounds(plotInfo.minpressure, plotInfo.maxpressure);
|
|
|
|
#ifdef SUBSURFACE_MOBILE
|
|
if (currentdc->divemode == CCR) {
|
|
tankItem->setVisible(false);
|
|
pn2GasItem->setVisible(false);
|
|
po2GasItem->setVisible(prefs.pp_graphs.po2);
|
|
pheGasItem->setVisible(false);
|
|
o2SetpointGasItem->setVisible(prefs.show_ccr_setpoint);
|
|
ccrsensor1GasItem->setVisible(prefs.show_ccr_sensors);
|
|
ccrsensor2GasItem->setVisible(prefs.show_ccr_sensors && (currentdc->no_o2sensors > 1));
|
|
ccrsensor3GasItem->setVisible(prefs.show_ccr_sensors && (currentdc->no_o2sensors > 1));
|
|
ocpo2GasItem->setVisible((currentdc->divemode == PSCR) && prefs.show_scr_ocpo2);
|
|
//when no gas graph, we can show temperature
|
|
if (!po2GasItem->isVisible() &&
|
|
!o2SetpointGasItem->isVisible() &&
|
|
!ccrsensor1GasItem->isVisible() &&
|
|
!ccrsensor2GasItem->isVisible() &&
|
|
!ccrsensor3GasItem->isVisible() &&
|
|
!ocpo2GasItem->isVisible())
|
|
temperatureItem->setVisible(true);
|
|
else
|
|
temperatureItem->setVisible(false);
|
|
} else {
|
|
tankItem->setVisible(prefs.tankbar);
|
|
pn2GasItem->setVisible(false);
|
|
po2GasItem->setVisible(false);
|
|
pheGasItem->setVisible(false);
|
|
o2SetpointGasItem->setVisible(false);
|
|
ccrsensor1GasItem->setVisible(false);
|
|
ccrsensor2GasItem->setVisible(false);
|
|
ccrsensor3GasItem->setVisible(false);
|
|
ocpo2GasItem->setVisible(false);
|
|
}
|
|
#endif
|
|
tankItem->setData(&plotInfo, d);
|
|
|
|
if (ppGraphsEnabled()) {
|
|
double max = prefs.pp_graphs.phe ? dataModel->pheMax() : -1;
|
|
if (prefs.pp_graphs.pn2)
|
|
max = std::max(dataModel->pn2Max(), max);
|
|
if (prefs.pp_graphs.po2)
|
|
max = std::max(dataModel->po2Max(), max);
|
|
|
|
gasYAxis->setBounds(0.0, max);
|
|
gasYAxis->updateTicks(animSpeed);
|
|
}
|
|
|
|
// Replot dive items
|
|
for (AbstractProfilePolygonItem *item: profileItems)
|
|
item->replot(d, inPlanner);
|
|
|
|
if (prefs.percentagegraph)
|
|
percentageItem->replot(d, currentdc, dataModel->data());
|
|
|
|
// The event items are a bit special since we don't know how many events are going to
|
|
// exist on a dive, so I cant create cache items for that. that's why they are here
|
|
// while all other items are up there on the constructor.
|
|
qDeleteAll(eventItems);
|
|
eventItems.clear();
|
|
struct event *event = currentdc->events;
|
|
struct gasmix lastgasmix = get_gasmix_at_time(d, get_dive_dc_const(d, dc), duration_t{1});
|
|
|
|
while (event) {
|
|
// if print mode is selected only draw headings, SP change, gas events or bookmark event
|
|
if (printMode) {
|
|
if (empty_string(event->name) ||
|
|
!(strcmp(event->name, "heading") == 0 ||
|
|
(same_string(event->name, "SP change") && event->time.seconds == 0) ||
|
|
event_is_gaschange(event) ||
|
|
event->type == SAMPLE_EVENT_BOOKMARK)) {
|
|
event = event->next;
|
|
continue;
|
|
}
|
|
}
|
|
if (DiveEventItem::isInteresting(d, currentdc, event, plotInfo)) {
|
|
auto item = new DiveEventItem(d, event, lastgasmix, plotInfo,
|
|
timeAxis, profileYAxis, animSpeed, *pixmaps);
|
|
item->setZValue(2);
|
|
addItem(item);
|
|
eventItems.push_back(item);
|
|
}
|
|
if (event_is_gaschange(event))
|
|
lastgasmix = get_gasmix_from_event(d, event);
|
|
event = event->next;
|
|
}
|
|
|
|
QString dcText = get_dc_nickname(currentdc);
|
|
if (dcText == "planned dive")
|
|
dcText = tr("Planned dive");
|
|
else if (dcText == "manually added dive")
|
|
dcText = tr("Manually added dive");
|
|
else if (dcText.isEmpty())
|
|
dcText = tr("Unknown dive computer");
|
|
#ifndef SUBSURFACE_MOBILE
|
|
int nr;
|
|
if ((nr = number_of_computers(d)) > 1)
|
|
dcText += tr(" (#%1 of %2)").arg(dc + 1).arg(nr);
|
|
#endif
|
|
diveComputerText->set(dcText, getColor(TIME_TEXT, isGrayscale));
|
|
}
|
|
|
|
void ProfileScene::draw(QPainter *painter, const QRect &pos,
|
|
const struct dive *d, int dc,
|
|
DivePlannerPointsModel *plannerModel, bool inPlanner)
|
|
{
|
|
QSize size = pos.size();
|
|
resize(QSizeF(size));
|
|
plotDive(d, dc, plannerModel, inPlanner, true, true);
|
|
|
|
QImage image(pos.size(), QImage::Format_ARGB32);
|
|
image.fill(getColor(::BACKGROUND, isGrayscale));
|
|
|
|
QPainter imgPainter(&image);
|
|
imgPainter.setRenderHint(QPainter::Antialiasing);
|
|
imgPainter.setRenderHint(QPainter::SmoothPixmapTransform);
|
|
render(&imgPainter, QRect(QPoint(), size), sceneRect(), Qt::IgnoreAspectRatio);
|
|
imgPainter.end();
|
|
|
|
if (isGrayscale) {
|
|
// convert QImage to grayscale before rendering
|
|
for (int i = 0; i < image.height(); i++) {
|
|
QRgb *pixel = reinterpret_cast<QRgb *>(image.scanLine(i));
|
|
QRgb *end = pixel + image.width();
|
|
for (; pixel != end; pixel++) {
|
|
int gray_val = qGray(*pixel);
|
|
*pixel = QColor(gray_val, gray_val, gray_val).rgb();
|
|
}
|
|
}
|
|
}
|
|
painter->drawImage(pos, image);
|
|
}
|