2023-05-20 14:27:20 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include "profileview.h"
|
|
|
|
#include "profilescene.h"
|
2023-06-16 18:24:22 +02:00
|
|
|
#include "tooltipitem.h"
|
2023-05-20 14:27:20 +02:00
|
|
|
#include "zvalues.h"
|
|
|
|
#include "core/dive.h"
|
2023-05-20 20:58:38 +02:00
|
|
|
#include "core/divelog.h"
|
2023-05-20 14:27:20 +02:00
|
|
|
#include "core/errorhelper.h"
|
|
|
|
#include "core/pref.h"
|
2023-05-20 15:22:02 +02:00
|
|
|
#include "core/settings/qPrefDisplay.h"
|
2023-05-20 15:37:11 +02:00
|
|
|
#include "core/settings/qPrefPartialPressureGas.h"
|
|
|
|
#include "core/settings/qPrefTechnicalDetails.h"
|
2023-05-20 14:27:20 +02:00
|
|
|
#include "qt-quick/chartitem.h"
|
|
|
|
|
2023-05-20 15:22:02 +02:00
|
|
|
#include <QAbstractAnimation>
|
2023-05-20 17:12:33 +02:00
|
|
|
#include <QCursor>
|
2023-05-20 14:27:20 +02:00
|
|
|
#include <QDebug>
|
|
|
|
#include <QElapsedTimer>
|
|
|
|
|
2023-05-20 15:22:02 +02:00
|
|
|
// Class for animations (if any). Might want to do our own.
|
|
|
|
class ProfileAnimation : public QAbstractAnimation {
|
|
|
|
ProfileView &view;
|
|
|
|
// For historical reasons, speed is actually the duration
|
|
|
|
// (i.e. the reciprocal of speed). Ouch, that hurts.
|
|
|
|
int speed;
|
|
|
|
|
|
|
|
int duration() const override
|
|
|
|
{
|
|
|
|
return speed;
|
|
|
|
}
|
|
|
|
void updateCurrentTime(int time) override
|
|
|
|
{
|
|
|
|
// Note: we explicitly pass 1.0 at the end, so that
|
|
|
|
// the callee can do a simple float comparison for "end".
|
|
|
|
view.anim(time == speed ? 1.0
|
|
|
|
: static_cast<double>(time) / speed);
|
|
|
|
}
|
|
|
|
public:
|
|
|
|
ProfileAnimation(ProfileView &view, int animSpeed) :
|
|
|
|
view(view),
|
|
|
|
speed(animSpeed)
|
|
|
|
{
|
|
|
|
start();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-05-20 14:27:20 +02:00
|
|
|
ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count),
|
|
|
|
d(nullptr),
|
|
|
|
dc(0),
|
2023-06-16 18:24:22 +02:00
|
|
|
dpr(1.0),
|
2023-05-20 20:58:38 +02:00
|
|
|
zoomLevel(1.00),
|
2023-05-20 14:27:20 +02:00
|
|
|
zoomedPosition(0.0),
|
2023-05-20 17:12:33 +02:00
|
|
|
panning(false),
|
2023-05-20 14:27:20 +02:00
|
|
|
empty(true),
|
2023-05-20 14:46:58 +02:00
|
|
|
shouldCalculateMax(true)
|
2023-05-20 14:27:20 +02:00
|
|
|
{
|
|
|
|
setBackgroundColor(Qt::black);
|
|
|
|
setFlag(ItemHasContents, true);
|
|
|
|
|
|
|
|
setAcceptHoverEvents(true);
|
|
|
|
setAcceptedMouseButtons(Qt::LeftButton);
|
2023-05-20 15:37:11 +02:00
|
|
|
|
|
|
|
auto tec = qPrefTechnicalDetails::instance();
|
|
|
|
connect(tec, &qPrefTechnicalDetails::calcalltissuesChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::calcceilingChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::gflowChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::gfhighChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::dcceilingChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::eadChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::calcceiling3mChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::modChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::calcndlttsChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::hrgraphChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::rulergraphChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::show_sacChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::zoomed_plotChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::decoinfoChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::show_pictures_in_profileChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::tankbarChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::percentagegraphChanged , this, &ProfileView::replot);
|
|
|
|
connect(tec, &qPrefTechnicalDetails::infoboxChanged , this, &ProfileView::replot);
|
|
|
|
|
|
|
|
auto pp_gas = qPrefPartialPressureGas::instance();
|
|
|
|
connect(pp_gas, &qPrefPartialPressureGas::pheChanged, this, &ProfileView::replot);
|
|
|
|
connect(pp_gas, &qPrefPartialPressureGas::pn2Changed, this, &ProfileView::replot);
|
|
|
|
connect(pp_gas, &qPrefPartialPressureGas::po2Changed, this, &ProfileView::replot);
|
2023-05-20 17:12:33 +02:00
|
|
|
|
|
|
|
setAcceptTouchEvents(true);
|
2023-06-16 18:24:22 +02:00
|
|
|
setAcceptHoverEvents(true);
|
2023-05-20 14:27:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ProfileView::ProfileView() : ProfileView(nullptr)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ProfileView::~ProfileView()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::resetPointers()
|
|
|
|
{
|
|
|
|
profileItem.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::plotAreaChanged(const QSizeF &s)
|
|
|
|
{
|
|
|
|
if (!empty)
|
|
|
|
plotDive(d, dc, RenderFlags::Instant);
|
|
|
|
}
|
|
|
|
|
2023-05-20 15:37:11 +02:00
|
|
|
void ProfileView::replot()
|
|
|
|
{
|
|
|
|
if (!empty)
|
|
|
|
plotDive(d, dc, RenderFlags::None);
|
|
|
|
}
|
|
|
|
|
2023-05-20 14:27:20 +02:00
|
|
|
void ProfileView::clear()
|
|
|
|
{
|
|
|
|
//clearPictures();
|
|
|
|
//disconnectPlannerConnections();
|
2023-05-20 14:46:58 +02:00
|
|
|
if (profileScene)
|
|
|
|
profileScene->clear();
|
2023-05-20 14:27:20 +02:00
|
|
|
//handles.clear();
|
|
|
|
//gases.clear();
|
2023-06-25 17:31:58 +02:00
|
|
|
if (tooltip)
|
|
|
|
tooltip->setVisible(false);
|
2023-05-20 14:27:20 +02:00
|
|
|
empty = true;
|
|
|
|
d = nullptr;
|
|
|
|
dc = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags)
|
|
|
|
{
|
|
|
|
d = dIn;
|
|
|
|
dc = dcIn;
|
|
|
|
if (!d) {
|
|
|
|
clear();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-20 14:46:58 +02:00
|
|
|
// We can't create the scene in the constructor, because we can't get the DPR property there. Oh joy!
|
|
|
|
if (!profileScene) {
|
2023-06-16 18:24:22 +02:00
|
|
|
dpr = std::clamp(property("dpr").toReal(), 0.5, 100.0);
|
2023-05-20 14:46:58 +02:00
|
|
|
profileScene = std::make_unique<ProfileScene>(dpr, false, false);
|
|
|
|
}
|
2023-05-20 14:27:20 +02:00
|
|
|
// If there was no previously displayed dive, turn off animations
|
|
|
|
if (empty)
|
|
|
|
flags |= RenderFlags::Instant;
|
|
|
|
empty = false;
|
|
|
|
|
|
|
|
// If Qt decided to destroy our canvas, recreate it
|
|
|
|
if (!profileItem)
|
|
|
|
profileItem = createChartItem<ChartGraphicsSceneItem>(ProfileZValue::Profile);
|
|
|
|
|
|
|
|
profileItem->setPos(QPointF(0.0, 0.0));
|
|
|
|
|
|
|
|
QElapsedTimer measureDuration; // let's measure how long this takes us (maybe we'll turn of TTL calculation later
|
|
|
|
measureDuration.start();
|
|
|
|
|
|
|
|
//DivePlannerPointsModel *model = currentState == EDIT || currentState == PLAN ? plannerModel : nullptr;
|
|
|
|
DivePlannerPointsModel *model = nullptr;
|
|
|
|
bool inPlanner = flags & RenderFlags::PlanMode;
|
|
|
|
|
2023-05-20 15:22:02 +02:00
|
|
|
int animSpeed = flags & RenderFlags::Instant ? 0 : qPrefDisplay::animation_speed();
|
|
|
|
|
2023-05-20 14:27:20 +02:00
|
|
|
profileScene->resize(size());
|
2023-05-20 15:22:02 +02:00
|
|
|
profileScene->plotDive(d, dc, animSpeed, model, inPlanner,
|
2023-05-20 14:27:20 +02:00
|
|
|
flags & RenderFlags::DontRecalculatePlotInfo,
|
2023-05-20 20:58:38 +02:00
|
|
|
shouldCalculateMax, zoomLevel, zoomedPosition);
|
2023-05-20 15:22:02 +02:00
|
|
|
background = inPlanner ? QColor("#D7E3EF") : getColor(::BACKGROUND, false);
|
|
|
|
profileItem->draw(size(), background, *profileScene);
|
2023-05-20 14:27:20 +02:00
|
|
|
|
|
|
|
//rulerItem->setVisible(prefs.rulergraph && currentState != PLAN && currentState != EDIT);
|
|
|
|
//rulerItem->setPlotInfo(d, profileScene->plotInfo);
|
|
|
|
|
|
|
|
//if ((currentState == EDIT || currentState == PLAN) && plannerModel) {
|
|
|
|
//repositionDiveHandlers();
|
|
|
|
//plannerModel->deleteTemporaryPlan();
|
|
|
|
//}
|
|
|
|
|
|
|
|
// On zoom / pan don't recreate the picture thumbnails, only change their position.
|
|
|
|
//if (flags & RenderFlags::DontRecalculatePlotInfo)
|
|
|
|
//updateThumbnails();
|
|
|
|
//else
|
|
|
|
//plotPicturesInternal(d, flags & RenderFlags::Instant);
|
|
|
|
|
|
|
|
//toolTipItem->refresh(d, mapToScene(mapFromGlobal(QCursor::pos())), currentState == PLAN);
|
|
|
|
|
|
|
|
update();
|
|
|
|
|
|
|
|
// OK, how long did this take us? Anything above the second is way too long,
|
|
|
|
// so if we are calculation TTS / NDL then let's force that off.
|
|
|
|
qint64 elapsedTime = measureDuration.elapsed();
|
|
|
|
if (verbose)
|
|
|
|
qDebug() << "Profile calculation for dive " << d->number << "took" << elapsedTime << "ms" << " -- calculated ceiling preference is" << prefs.calcceiling;
|
|
|
|
if (elapsedTime > 1000 && prefs.calcndltts) {
|
|
|
|
qPrefTechnicalDetails::set_calcndltts(false);
|
|
|
|
report_error("%s", qPrintable(tr("Show NDL / TTS was disabled because of excessive processing time")));
|
|
|
|
}
|
2023-05-20 15:22:02 +02:00
|
|
|
|
2023-06-16 18:24:22 +02:00
|
|
|
if (!tooltip)
|
|
|
|
tooltip = createChartItem<ToolTipItem>(dpr);
|
|
|
|
if (prefs.infobox) {
|
|
|
|
tooltip->setVisible(true);
|
|
|
|
tooltip->update(d, dpr, 0, profileScene->getPlotInfo(), flags & RenderFlags::PlanMode);
|
|
|
|
} else {
|
|
|
|
tooltip->setVisible(false);
|
|
|
|
}
|
|
|
|
|
2023-05-20 15:22:02 +02:00
|
|
|
// Reset animation.
|
|
|
|
if (animSpeed <= 0)
|
|
|
|
animation.reset();
|
|
|
|
else
|
|
|
|
animation = std::make_unique<ProfileAnimation>(*this, animSpeed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::anim(double fraction)
|
|
|
|
{
|
|
|
|
if (!profileScene || !profileItem)
|
|
|
|
return;
|
|
|
|
profileScene->anim(fraction);
|
|
|
|
profileItem->draw(size(), background, *profileScene);
|
|
|
|
update();
|
2023-05-20 14:27:20 +02:00
|
|
|
}
|
2023-05-20 17:12:33 +02:00
|
|
|
|
|
|
|
void ProfileView::resetZoom()
|
|
|
|
{
|
2023-05-20 20:58:38 +02:00
|
|
|
zoomLevel = 1.0;
|
2023-05-20 17:12:33 +02:00
|
|
|
zoomedPosition = 0.0;
|
|
|
|
}
|
|
|
|
|
2023-05-20 20:58:38 +02:00
|
|
|
void ProfileView::setZoom(double level)
|
2023-05-20 17:12:33 +02:00
|
|
|
{
|
2023-05-20 20:58:38 +02:00
|
|
|
level = std::clamp(level, 1.0, 20.0);
|
|
|
|
double old = std::exchange(zoomLevel, level);
|
|
|
|
if (level != old)
|
|
|
|
plotDive(d, dc, RenderFlags::DontRecalculatePlotInfo);
|
|
|
|
emit zoomLevelChanged();
|
2023-05-20 17:12:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::wheelEvent(QWheelEvent *event)
|
|
|
|
{
|
|
|
|
if (!d)
|
|
|
|
return;
|
|
|
|
if (panning)
|
|
|
|
return; // No change in zoom level while panning.
|
|
|
|
if (event->buttons() == Qt::LeftButton)
|
|
|
|
return;
|
2023-05-20 20:58:38 +02:00
|
|
|
if (event->angleDelta().y() > 0)
|
|
|
|
setZoom(zoomLevel * 1.15);
|
|
|
|
else if (event->angleDelta().y() < 0)
|
|
|
|
setZoom(zoomLevel / 1.15);
|
2023-05-20 17:12:33 +02:00
|
|
|
else if (event->angleDelta().x() && zoomLevel > 0) {
|
|
|
|
double oldPos = zoomedPosition;
|
2023-05-20 20:58:38 +02:00
|
|
|
zoomedPosition = profileScene->calcZoomPosition(zoomLevel,
|
2023-05-20 17:12:33 +02:00
|
|
|
oldPos,
|
|
|
|
oldPos - event->angleDelta().x());
|
|
|
|
if (oldPos != zoomedPosition)
|
|
|
|
plotDive(d, dc, RenderFlags::Instant | RenderFlags::DontRecalculatePlotInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::mousePressEvent(QMouseEvent *event)
|
|
|
|
{
|
2023-06-25 17:31:58 +02:00
|
|
|
// Handle dragging of items
|
|
|
|
ChartView::mousePressEvent(event);
|
|
|
|
if (event->isAccepted())
|
|
|
|
return;
|
|
|
|
|
2023-05-20 17:12:33 +02:00
|
|
|
panning = true;
|
2023-05-20 20:58:38 +02:00
|
|
|
QPointF pos = mapToScene(event->pos());
|
|
|
|
panStart(pos.x(), pos.y());
|
2023-05-20 17:12:33 +02:00
|
|
|
setCursor(Qt::ClosedHandCursor);
|
|
|
|
event->accept();
|
|
|
|
}
|
|
|
|
|
2023-06-25 17:31:58 +02:00
|
|
|
void ProfileView::mouseReleaseEvent(QMouseEvent *event)
|
2023-05-20 17:12:33 +02:00
|
|
|
{
|
2023-06-25 17:31:58 +02:00
|
|
|
ChartView::mouseReleaseEvent(event);
|
|
|
|
|
2023-05-20 17:12:33 +02:00
|
|
|
if (panning) {
|
|
|
|
panning = false;
|
|
|
|
unsetCursor();
|
|
|
|
}
|
|
|
|
//if (currentState == PLAN || currentState == EDIT) {
|
|
|
|
// shouldCalculateMax = true;
|
|
|
|
// replot();
|
|
|
|
//}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::mouseMoveEvent(QMouseEvent *event)
|
|
|
|
{
|
2023-06-25 17:31:58 +02:00
|
|
|
ChartView::mouseMoveEvent(event);
|
|
|
|
|
2023-05-20 17:12:33 +02:00
|
|
|
QPointF pos = mapToScene(event->pos());
|
2023-05-20 20:58:38 +02:00
|
|
|
if (panning)
|
|
|
|
pan(pos.x(), pos.y());
|
2023-05-20 17:12:33 +02:00
|
|
|
|
|
|
|
//toolTipItem->refresh(d, mapToScene(mapFromGlobal(QCursor::pos())), currentState == PLAN);
|
|
|
|
|
|
|
|
//if (currentState == PLAN || currentState == EDIT) {
|
|
|
|
//QRectF rect = profileScene->profileRegion;
|
|
|
|
//auto [miny, maxy] = profileScene->profileYAxis->screenMinMax();
|
|
|
|
//double x = std::clamp(pos.x(), rect.left(), rect.right());
|
|
|
|
//double y = std::clamp(pos.y(), miny, maxy);
|
|
|
|
//mouseFollowerHorizontal->setLine(rect.left(), y, rect.right(), y);
|
|
|
|
//mouseFollowerVertical->setLine(x, rect.top(), x, rect.bottom());
|
|
|
|
//}
|
|
|
|
}
|
2023-05-20 20:58:38 +02:00
|
|
|
|
|
|
|
int ProfileView::getDiveId() const
|
|
|
|
{
|
|
|
|
return d ? d->id : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::setDiveId(int id)
|
|
|
|
{
|
|
|
|
plotDive(divelog.dives.get_by_uniq_id(id), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ProfileView::numDC() const
|
|
|
|
{
|
|
|
|
return d ? d->number_of_computers() : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::pinchStart()
|
|
|
|
{
|
|
|
|
zoomLevelPinchStart = zoomLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::pinch(double factor)
|
|
|
|
{
|
|
|
|
setZoom(zoomLevelPinchStart * factor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::nextDC()
|
|
|
|
{
|
|
|
|
rotateDC(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::prevDC()
|
|
|
|
{
|
|
|
|
rotateDC(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::rotateDC(int dir)
|
|
|
|
{
|
|
|
|
int num = numDC();
|
|
|
|
if (num <= 1)
|
|
|
|
return;
|
|
|
|
dc = (dc + dir) % num;
|
|
|
|
if (dc < 0)
|
|
|
|
dc += num;
|
|
|
|
replot();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::panStart(double x, double y)
|
|
|
|
{
|
|
|
|
panningOriginalMousePosition = x;
|
|
|
|
panningOriginalProfilePosition = zoomedPosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::pan(double x, double y)
|
|
|
|
{
|
|
|
|
double oldPos = zoomedPosition;
|
|
|
|
zoomedPosition = profileScene->calcZoomPosition(zoomLevel,
|
|
|
|
panningOriginalProfilePosition,
|
|
|
|
panningOriginalMousePosition - x);
|
|
|
|
if (oldPos != zoomedPosition)
|
|
|
|
plotDive(d, dc, RenderFlags::Instant | RenderFlags::DontRecalculatePlotInfo); // TODO: animations don't work when scrolling
|
|
|
|
}
|
2023-06-16 18:24:22 +02:00
|
|
|
|
|
|
|
void ProfileView::hoverEnterEvent(QHoverEvent *)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfileView::hoverMoveEvent(QHoverEvent *event)
|
|
|
|
{
|
|
|
|
if (!profileScene)
|
|
|
|
return;
|
|
|
|
QPointF pos = event->pos();
|
|
|
|
int time = profileScene->timeAt(pos);
|
|
|
|
bool requires_update = false;
|
|
|
|
if (tooltip) {
|
|
|
|
tooltip->update(d, dpr, time, profileScene->getPlotInfo(), false); // TODO: plan mode
|
|
|
|
requires_update = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (requires_update)
|
|
|
|
update();
|
|
|
|
}
|