mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
To do so, generalize the animation routine. This seems to expose a QtQuick bug: we get spurious hover-events when the tooltip item is updated in the animation. We have to check for that to prevent en endless loop (until the user moves the mouse out of the profile window). Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
423 lines
12 KiB
C++
423 lines
12 KiB
C++
// SPDX-License-Identifier: GPL-2.0
|
|
#include "profileview.h"
|
|
#include "profilescene.h"
|
|
#include "tooltipitem.h"
|
|
#include "zvalues.h"
|
|
#include "core/dive.h"
|
|
#include "core/divelog.h"
|
|
#include "core/errorhelper.h"
|
|
#include "core/pref.h"
|
|
#include "core/settings/qPrefDisplay.h"
|
|
#include "core/settings/qPrefPartialPressureGas.h"
|
|
#include "core/settings/qPrefTechnicalDetails.h"
|
|
#include "qt-quick/chartitem.h"
|
|
|
|
#include <QAbstractAnimation>
|
|
#include <QCursor>
|
|
#include <QDebug>
|
|
#include <QElapsedTimer>
|
|
|
|
// Class templates for animations (if any). Might want to do our own.
|
|
// Calls the function object passed in the constructor with a time argument,
|
|
// where 0.0 = start at 1.0 = end..
|
|
// On the last invocation, a 1.0 literal is passed, so floating-point
|
|
// comparison is OK.
|
|
class ProfileAnimation : public QAbstractAnimation {
|
|
int duration() const override
|
|
{
|
|
return speed;
|
|
}
|
|
protected:
|
|
// For historical reasons, speed is actually the duration
|
|
// (i.e. the reciprocal of speed). Ouch, that hurts.
|
|
int speed;
|
|
public:
|
|
ProfileAnimation(int animSpeed) : speed(animSpeed)
|
|
{
|
|
}
|
|
};
|
|
|
|
template <typename FUNC>
|
|
class ProfileAnimationTemplate : public ProfileAnimation {
|
|
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".
|
|
func(time == speed ? 1.0
|
|
: static_cast<double>(time) / speed);
|
|
}
|
|
FUNC func;
|
|
public:
|
|
ProfileAnimationTemplate(FUNC func, int animSpeed) :
|
|
ProfileAnimation(animSpeed),
|
|
func(func)
|
|
{
|
|
start();
|
|
}
|
|
};
|
|
|
|
// Helper function to make creation of animations somewhat more palatable
|
|
template <typename FUNC>
|
|
std::unique_ptr<ProfileAnimationTemplate<FUNC>> make_anim(FUNC func, int animSpeed)
|
|
{
|
|
return std::make_unique<ProfileAnimationTemplate<FUNC>>(func, animSpeed);
|
|
}
|
|
|
|
ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count),
|
|
d(nullptr),
|
|
dc(0),
|
|
dpr(1.0),
|
|
zoomLevel(1.00),
|
|
zoomedPosition(0.0),
|
|
panning(false),
|
|
empty(true),
|
|
shouldCalculateMax(true)
|
|
{
|
|
setBackgroundColor(Qt::black);
|
|
setFlag(ItemHasContents, true);
|
|
|
|
setAcceptHoverEvents(true);
|
|
setAcceptedMouseButtons(Qt::LeftButton);
|
|
|
|
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);
|
|
|
|
setAcceptTouchEvents(true);
|
|
setAcceptHoverEvents(true);
|
|
}
|
|
|
|
ProfileView::ProfileView() : ProfileView(nullptr)
|
|
{
|
|
}
|
|
|
|
ProfileView::~ProfileView()
|
|
{
|
|
}
|
|
|
|
void ProfileView::resetPointers()
|
|
{
|
|
profileItem.reset();
|
|
}
|
|
|
|
void ProfileView::plotAreaChanged(const QSizeF &s)
|
|
{
|
|
if (!empty)
|
|
plotDive(d, dc, RenderFlags::Instant);
|
|
}
|
|
|
|
void ProfileView::replot()
|
|
{
|
|
if (!empty)
|
|
plotDive(d, dc, RenderFlags::None);
|
|
}
|
|
|
|
void ProfileView::clear()
|
|
{
|
|
//clearPictures();
|
|
//disconnectPlannerConnections();
|
|
if (profileScene)
|
|
profileScene->clear();
|
|
//handles.clear();
|
|
//gases.clear();
|
|
if (tooltip)
|
|
tooltip->setVisible(false);
|
|
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;
|
|
}
|
|
|
|
// We can't create the scene in the constructor, because we can't get the DPR property there. Oh joy!
|
|
if (!profileScene) {
|
|
dpr = std::clamp(property("dpr").toReal(), 0.5, 100.0);
|
|
profileScene = std::make_unique<ProfileScene>(dpr, false, false);
|
|
}
|
|
// 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;
|
|
|
|
int animSpeed = flags & RenderFlags::Instant ? 0 : qPrefDisplay::animation_speed();
|
|
|
|
profileScene->resize(size());
|
|
profileScene->plotDive(d, dc, animSpeed, model, inPlanner,
|
|
flags & RenderFlags::DontRecalculatePlotInfo,
|
|
shouldCalculateMax, zoomLevel, zoomedPosition);
|
|
background = inPlanner ? QColor("#D7E3EF") : getColor(::BACKGROUND, false);
|
|
profileItem->draw(size(), background, *profileScene);
|
|
|
|
//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);
|
|
|
|
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")));
|
|
}
|
|
|
|
if (!tooltip)
|
|
tooltip = createChartItem<ToolTipItem>(dpr);
|
|
if (prefs.infobox) {
|
|
QPoint pos = mapFromGlobal(QCursor::pos()).toPoint();
|
|
tooltip->setVisible(true);
|
|
updateTooltip(pos, flags & RenderFlags::PlanMode, animSpeed);
|
|
} else {
|
|
tooltip->setVisible(false);
|
|
}
|
|
|
|
// Reset animation.
|
|
if (animSpeed <= 0)
|
|
animation.reset();
|
|
else
|
|
animation = make_anim([this](double progress) { anim(progress); }, animSpeed);
|
|
}
|
|
|
|
void ProfileView::anim(double fraction)
|
|
{
|
|
if (!profileScene || !profileItem)
|
|
return;
|
|
profileScene->anim(fraction);
|
|
profileItem->draw(size(), background, *profileScene);
|
|
update();
|
|
}
|
|
|
|
void ProfileView::resetZoom()
|
|
{
|
|
zoomLevel = 1.0;
|
|
zoomedPosition = 0.0;
|
|
}
|
|
|
|
void ProfileView::setZoom(double level)
|
|
{
|
|
level = std::clamp(level, 1.0, 20.0);
|
|
double old = std::exchange(zoomLevel, level);
|
|
if (level != old)
|
|
plotDive(d, dc, RenderFlags::DontRecalculatePlotInfo);
|
|
emit zoomLevelChanged();
|
|
}
|
|
|
|
void ProfileView::wheelEvent(QWheelEvent *event)
|
|
{
|
|
if (!d)
|
|
return;
|
|
if (panning)
|
|
return; // No change in zoom level while panning.
|
|
if (event->buttons() == Qt::LeftButton)
|
|
return;
|
|
if (event->angleDelta().y() > 0)
|
|
setZoom(zoomLevel * 1.15);
|
|
else if (event->angleDelta().y() < 0)
|
|
setZoom(zoomLevel / 1.15);
|
|
else if (event->angleDelta().x() && zoomLevel > 0) {
|
|
double oldPos = zoomedPosition;
|
|
zoomedPosition = profileScene->calcZoomPosition(zoomLevel,
|
|
oldPos,
|
|
oldPos - event->angleDelta().x());
|
|
if (oldPos != zoomedPosition)
|
|
plotDive(d, dc, RenderFlags::Instant | RenderFlags::DontRecalculatePlotInfo);
|
|
}
|
|
}
|
|
|
|
void ProfileView::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
// Handle dragging of items
|
|
ChartView::mousePressEvent(event);
|
|
if (event->isAccepted())
|
|
return;
|
|
|
|
panning = true;
|
|
QPointF pos = mapToScene(event->pos());
|
|
panStart(pos.x(), pos.y());
|
|
setCursor(Qt::ClosedHandCursor);
|
|
event->accept();
|
|
}
|
|
|
|
void ProfileView::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
ChartView::mouseReleaseEvent(event);
|
|
|
|
if (panning) {
|
|
panning = false;
|
|
unsetCursor();
|
|
}
|
|
//if (currentState == PLAN || currentState == EDIT) {
|
|
// shouldCalculateMax = true;
|
|
// replot();
|
|
//}
|
|
}
|
|
|
|
void ProfileView::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
ChartView::mouseMoveEvent(event);
|
|
|
|
QPointF pos = mapToScene(event->pos());
|
|
if (panning)
|
|
pan(pos.x(), pos.y());
|
|
|
|
//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());
|
|
//}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
void ProfileView::hoverEnterEvent(QHoverEvent *)
|
|
{
|
|
}
|
|
|
|
void ProfileView::hoverMoveEvent(QHoverEvent *event)
|
|
{
|
|
if (!profileScene)
|
|
return;
|
|
|
|
// This is incredibly stupid: For some weird reason (a bug?), when
|
|
// resizing the ToolTipItem we get spurious hoverMoveEvents, which
|
|
// restarts the animation, giving an infinite loop.
|
|
// Prevent this by comparing to the old mouse position.
|
|
if (std::exchange(previousHoveMovePosition, event->pos()) == previousHoveMovePosition)
|
|
return;
|
|
|
|
if (tooltip && prefs.infobox) {
|
|
updateTooltip(event->pos(), false, qPrefDisplay::animation_speed()); // TODO: plan mode
|
|
update();
|
|
}
|
|
}
|
|
|
|
void ProfileView::updateTooltip(QPointF pos, bool plannerMode, int animSpeed)
|
|
{
|
|
int time = profileScene->timeAt(pos);
|
|
auto events = profileScene->eventsAt(pos);
|
|
tooltip->update(d, dpr, time, profileScene->getPlotInfo(), events, plannerMode, animSpeed);
|
|
|
|
// Reset animation.
|
|
if (animSpeed <= 0)
|
|
tooltip_animation.reset();
|
|
else
|
|
tooltip_animation = make_anim([this](double progress)
|
|
{ if (tooltip) tooltip->anim(progress); update(); }, animSpeed);
|
|
}
|