mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
profile: Unify desktop and mobile widgets
This breaks DPR handling, but it is a start. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
413b236867
commit
e3767976a3
9 changed files with 129 additions and 316 deletions
|
@ -171,7 +171,6 @@ SOURCES += subsurface-mobile-main.cpp \
|
||||||
qt-models/weightsysteminfomodel.cpp \
|
qt-models/weightsysteminfomodel.cpp \
|
||||||
qt-models/filterconstraintmodel.cpp \
|
qt-models/filterconstraintmodel.cpp \
|
||||||
qt-models/filterpresetmodel.cpp \
|
qt-models/filterpresetmodel.cpp \
|
||||||
profile-widget/qmlprofile.cpp \
|
|
||||||
profile-widget/divecartesianaxis.cpp \
|
profile-widget/divecartesianaxis.cpp \
|
||||||
profile-widget/diveeventitem.cpp \
|
profile-widget/diveeventitem.cpp \
|
||||||
profile-widget/divepercentageitem.cpp \
|
profile-widget/divepercentageitem.cpp \
|
||||||
|
@ -337,7 +336,6 @@ HEADERS += \
|
||||||
qt-models/weightsysteminfomodel.h \
|
qt-models/weightsysteminfomodel.h \
|
||||||
qt-models/filterconstraintmodel.h \
|
qt-models/filterconstraintmodel.h \
|
||||||
qt-models/filterpresetmodel.h \
|
qt-models/filterpresetmodel.h \
|
||||||
profile-widget/qmlprofile.h \
|
|
||||||
profile-widget/divepercentageitem.h \
|
profile-widget/divepercentageitem.h \
|
||||||
profile-widget/diveprofileitem.h \
|
profile-widget/diveprofileitem.h \
|
||||||
profile-widget/profilescene.h \
|
profile-widget/profilescene.h \
|
||||||
|
|
|
@ -231,13 +231,13 @@ Item {
|
||||||
Layout.columnSpan: 3
|
Layout.columnSpan: 3
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
QMLProfile {
|
ProfileView {
|
||||||
id: qmlProfile
|
id: qmlProfile
|
||||||
visible: !noDive
|
visible: !noDive
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
property real lastScale: 1.0 // final scale at the end of previous pinch
|
|
||||||
diveId: detailsView.myId
|
diveId: detailsView.myId
|
||||||
|
property real dpr: 0.8 // TODO: make this dynamic
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
|
@ -253,36 +253,20 @@ Item {
|
||||||
// before realizing that this is actually a pinch/zoom. So let's reset this
|
// before realizing that this is actually a pinch/zoom. So let's reset this
|
||||||
// just in case
|
// just in case
|
||||||
qmlProfile.opacity = 1.0
|
qmlProfile.opacity = 1.0
|
||||||
if (manager.verboseEnabled)
|
qmlProfile.pinchStart()
|
||||||
manager.appendTextToLog("pinch started w/ previousScale " + qmlProfile.lastScale)
|
|
||||||
}
|
}
|
||||||
onPinchUpdated: {
|
onPinchUpdated: {
|
||||||
if (pinch.scale * qmlProfile.lastScale < 1.0)
|
qmlProfile.pinch(pinch.scale)
|
||||||
qmlProfile.lastScale = 1.0 / pinch.scale // this way we never shrink and the changes stay smooth
|
|
||||||
// the underlying widget deals with the scaling, no need to send an update request
|
|
||||||
qmlProfile.scale = pinch.scale * qmlProfile.lastScale
|
|
||||||
if (manager.verboseEnabled)
|
|
||||||
manager.appendTextToLog("pinch updated to scale " + qmlProfile.scale);
|
|
||||||
}
|
|
||||||
onPinchFinished: {
|
|
||||||
// remember the final scale value so we can continue from there next time the user pinches
|
|
||||||
qmlProfile.lastScale = pinch.scale * qmlProfile.lastScale
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
// we want to pan the profile if we are zoomed in, but we want to immediately
|
// we want to pan the profile if we are zoomed in, but we want to immediately
|
||||||
// pass the mouse events through to the ListView if we are not. That way you
|
// pass the mouse events through to the ListView if we are not. That way you
|
||||||
// can swipe through the dive list, even if you happen to swipe the profile
|
// can swipe through the dive list, even if you happen to swipe the profile
|
||||||
property bool isZoomed: qmlProfile.scale - 1.0 > 0.02
|
property bool isZoomed: qmlProfile.zoomLevel > 1.02
|
||||||
|
|
||||||
// this indicates that we are actually dragging
|
// this indicates that we are actually dragging
|
||||||
property bool dragging: false
|
property bool dragging: false
|
||||||
// cursor/finger position as we start dragging
|
|
||||||
property real initialX
|
|
||||||
property real initialY
|
|
||||||
// the offset previously used to show the profile
|
|
||||||
property real oldXOffset
|
|
||||||
property real oldYOffset
|
|
||||||
|
|
||||||
// if the profile is not scaled in, don't start panning
|
// if the profile is not scaled in, don't start panning
|
||||||
// but if the profile is scaled in, then start almost immediately
|
// but if the profile is scaled in, then start almost immediately
|
||||||
|
@ -291,16 +275,6 @@ Item {
|
||||||
// pass events through to the parent and eventually into the ListView
|
// pass events through to the parent and eventually into the ListView
|
||||||
propagateComposedEvents: true
|
propagateComposedEvents: true
|
||||||
|
|
||||||
// for testing / debugging on a desktop
|
|
||||||
scrollGestureEnabled: true
|
|
||||||
onWheel: {
|
|
||||||
manager.appendTextToLog("wheel " + wheel.angleDelta)
|
|
||||||
if (wheel.angleDelta.y > 0)
|
|
||||||
qmlProfile.scale += 0.2
|
|
||||||
if (wheel.angleDelta.y < 0 && qmlProfile.scale > 1.1)
|
|
||||||
qmlProfile.scale -= 0.2
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
drag.target: qmlProfile
|
drag.target: qmlProfile
|
||||||
drag.axis: Drag.XAndYAxis
|
drag.axis: Drag.XAndYAxis
|
||||||
|
@ -311,10 +285,7 @@ Item {
|
||||||
}
|
}
|
||||||
onPressAndHold: {
|
onPressAndHold: {
|
||||||
dragging = true;
|
dragging = true;
|
||||||
oldXOffset = qmlProfile.xOffset
|
qmlProfile.panStart(mouse.x, mouse.y)
|
||||||
oldYOffset = qmlProfile.yOffset
|
|
||||||
initialX = mouse.x
|
|
||||||
initialY = mouse.y
|
|
||||||
if (manager.verboseEnabled)
|
if (manager.verboseEnabled)
|
||||||
manager.appendTextToLog("press and hold at mouse" + Math.round(10 * mouse.x) / 10 + " / " + Math.round(10 * mouse.y) / 10)
|
manager.appendTextToLog("press and hold at mouse" + Math.round(10 * mouse.x) / 10 + " / " + Math.round(10 * mouse.y) / 10)
|
||||||
// give visual feedback to the user that they now can drag
|
// give visual feedback to the user that they now can drag
|
||||||
|
@ -322,13 +293,9 @@ Item {
|
||||||
}
|
}
|
||||||
onPositionChanged: {
|
onPositionChanged: {
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
var x = (mouse.x - initialX) / qmlProfile.scale
|
|
||||||
var y = (mouse.y - initialY) / qmlProfile.scale
|
|
||||||
if (manager.verboseEnabled)
|
if (manager.verboseEnabled)
|
||||||
manager.appendTextToLog("drag mouse " + Math.round(10 * mouse.x) / 10 + " / " + Math.round(10 * mouse.y) / 10 + " delta " + Math.round(x) + " / " + Math.round(y))
|
manager.appendTextToLog("drag mouse " + Math.round(10 * mouse.x) / 10 + " / " + Math.round(10 * mouse.y) / 10 + " delta " + Math.round(x) + " / " + Math.round(y))
|
||||||
qmlProfile.xOffset = oldXOffset + x
|
qmlProfile.pan(mouse.x, mouse.y)
|
||||||
qmlProfile.yOffset = oldYOffset + y
|
|
||||||
qmlProfile.update()
|
|
||||||
} else {
|
} else {
|
||||||
mouse.accepted = false
|
mouse.accepted = false
|
||||||
}
|
}
|
||||||
|
@ -344,7 +311,6 @@ Item {
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// reset the position if not zoomed in
|
// reset the position if not zoomed in
|
||||||
if (!isZoomed) {
|
if (!isZoomed) {
|
||||||
qmlProfile.xOffset = qmlProfile.yOffset = oldXOffset = oldYOffset = 0
|
|
||||||
mouse.accepted = false
|
mouse.accepted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,8 +32,6 @@ set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||||
if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "MobileExecutable")
|
if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "MobileExecutable")
|
||||||
set(SUBSURFACE_PROFILE_LIB_SRCS
|
set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||||
${SUBSURFACE_PROFILE_LIB_SRCS}
|
${SUBSURFACE_PROFILE_LIB_SRCS}
|
||||||
qmlprofile.cpp
|
|
||||||
qmlprofile.h
|
|
||||||
)
|
)
|
||||||
else ()
|
else ()
|
||||||
set(SUBSURFACE_PROFILE_LIB_SRCS
|
set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "profilescene.h"
|
#include "profilescene.h"
|
||||||
#include "zvalues.h"
|
#include "zvalues.h"
|
||||||
#include "core/dive.h"
|
#include "core/dive.h"
|
||||||
|
#include "core/divelog.h"
|
||||||
#include "core/errorhelper.h"
|
#include "core/errorhelper.h"
|
||||||
#include "core/pref.h"
|
#include "core/pref.h"
|
||||||
#include "core/settings/qPrefDisplay.h"
|
#include "core/settings/qPrefDisplay.h"
|
||||||
|
@ -42,17 +43,10 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static double calcZoom(int zoomLevel)
|
|
||||||
{
|
|
||||||
// Base of exponential zoom function: one wheel-click will increase the zoom by 15%.
|
|
||||||
constexpr double zoomFactor = 1.15;
|
|
||||||
return zoomLevel == 0 ? 1.0 : pow(zoomFactor, zoomLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count),
|
ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count),
|
||||||
d(nullptr),
|
d(nullptr),
|
||||||
dc(0),
|
dc(0),
|
||||||
zoomLevel(0),
|
zoomLevel(1.00),
|
||||||
zoomedPosition(0.0),
|
zoomedPosition(0.0),
|
||||||
panning(false),
|
panning(false),
|
||||||
empty(true),
|
empty(true),
|
||||||
|
@ -141,7 +135,7 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags)
|
||||||
|
|
||||||
// We can't create the scene in the constructor, because we can't get the DPR property there. Oh joy!
|
// We can't create the scene in the constructor, because we can't get the DPR property there. Oh joy!
|
||||||
if (!profileScene) {
|
if (!profileScene) {
|
||||||
double dpr = std::clamp(property("dpr").toReal(), 1.0, 100.0);
|
double dpr = std::clamp(property("dpr").toReal(), 0.5, 100.0);
|
||||||
profileScene = std::make_unique<ProfileScene>(dpr, false, false);
|
profileScene = std::make_unique<ProfileScene>(dpr, false, false);
|
||||||
}
|
}
|
||||||
// If there was no previously displayed dive, turn off animations
|
// If there was no previously displayed dive, turn off animations
|
||||||
|
@ -162,14 +156,12 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags)
|
||||||
DivePlannerPointsModel *model = nullptr;
|
DivePlannerPointsModel *model = nullptr;
|
||||||
bool inPlanner = flags & RenderFlags::PlanMode;
|
bool inPlanner = flags & RenderFlags::PlanMode;
|
||||||
|
|
||||||
double zoom = calcZoom(zoomLevel);
|
|
||||||
|
|
||||||
int animSpeed = flags & RenderFlags::Instant ? 0 : qPrefDisplay::animation_speed();
|
int animSpeed = flags & RenderFlags::Instant ? 0 : qPrefDisplay::animation_speed();
|
||||||
|
|
||||||
profileScene->resize(size());
|
profileScene->resize(size());
|
||||||
profileScene->plotDive(d, dc, animSpeed, model, inPlanner,
|
profileScene->plotDive(d, dc, animSpeed, model, inPlanner,
|
||||||
flags & RenderFlags::DontRecalculatePlotInfo,
|
flags & RenderFlags::DontRecalculatePlotInfo,
|
||||||
shouldCalculateMax, zoom, zoomedPosition);
|
shouldCalculateMax, zoomLevel, zoomedPosition);
|
||||||
background = inPlanner ? QColor("#D7E3EF") : getColor(::BACKGROUND, false);
|
background = inPlanner ? QColor("#D7E3EF") : getColor(::BACKGROUND, false);
|
||||||
profileItem->draw(size(), background, *profileScene);
|
profileItem->draw(size(), background, *profileScene);
|
||||||
|
|
||||||
|
@ -220,14 +212,17 @@ void ProfileView::anim(double fraction)
|
||||||
|
|
||||||
void ProfileView::resetZoom()
|
void ProfileView::resetZoom()
|
||||||
{
|
{
|
||||||
zoomLevel = 0;
|
zoomLevel = 1.0;
|
||||||
zoomedPosition = 0.0;
|
zoomedPosition = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileView::setZoom(int level)
|
void ProfileView::setZoom(double level)
|
||||||
{
|
{
|
||||||
zoomLevel = level;
|
level = std::clamp(level, 1.0, 20.0);
|
||||||
|
double old = std::exchange(zoomLevel, level);
|
||||||
|
if (level != old)
|
||||||
plotDive(d, dc, RenderFlags::DontRecalculatePlotInfo);
|
plotDive(d, dc, RenderFlags::DontRecalculatePlotInfo);
|
||||||
|
emit zoomLevelChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileView::wheelEvent(QWheelEvent *event)
|
void ProfileView::wheelEvent(QWheelEvent *event)
|
||||||
|
@ -238,13 +233,13 @@ void ProfileView::wheelEvent(QWheelEvent *event)
|
||||||
return; // No change in zoom level while panning.
|
return; // No change in zoom level while panning.
|
||||||
if (event->buttons() == Qt::LeftButton)
|
if (event->buttons() == Qt::LeftButton)
|
||||||
return;
|
return;
|
||||||
if (event->angleDelta().y() > 0 && zoomLevel < 20)
|
if (event->angleDelta().y() > 0)
|
||||||
setZoom(++zoomLevel);
|
setZoom(zoomLevel * 1.15);
|
||||||
else if (event->angleDelta().y() < 0 && zoomLevel > 0)
|
else if (event->angleDelta().y() < 0)
|
||||||
setZoom(--zoomLevel);
|
setZoom(zoomLevel / 1.15);
|
||||||
else if (event->angleDelta().x() && zoomLevel > 0) {
|
else if (event->angleDelta().x() && zoomLevel > 0) {
|
||||||
double oldPos = zoomedPosition;
|
double oldPos = zoomedPosition;
|
||||||
zoomedPosition = profileScene->calcZoomPosition(calcZoom(zoomLevel),
|
zoomedPosition = profileScene->calcZoomPosition(zoomLevel,
|
||||||
oldPos,
|
oldPos,
|
||||||
oldPos - event->angleDelta().x());
|
oldPos - event->angleDelta().x());
|
||||||
if (oldPos != zoomedPosition)
|
if (oldPos != zoomedPosition)
|
||||||
|
@ -255,8 +250,8 @@ void ProfileView::wheelEvent(QWheelEvent *event)
|
||||||
void ProfileView::mousePressEvent(QMouseEvent *event)
|
void ProfileView::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
panning = true;
|
panning = true;
|
||||||
panningOriginalMousePosition = mapToScene(event->pos()).x();
|
QPointF pos = mapToScene(event->pos());
|
||||||
panningOriginalProfilePosition = zoomedPosition;
|
panStart(pos.x(), pos.y());
|
||||||
setCursor(Qt::ClosedHandCursor);
|
setCursor(Qt::ClosedHandCursor);
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
@ -276,14 +271,8 @@ void ProfileView::mouseReleaseEvent(QMouseEvent *)
|
||||||
void ProfileView::mouseMoveEvent(QMouseEvent *event)
|
void ProfileView::mouseMoveEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
QPointF pos = mapToScene(event->pos());
|
QPointF pos = mapToScene(event->pos());
|
||||||
if (panning) {
|
if (panning)
|
||||||
double oldPos = zoomedPosition;
|
pan(pos.x(), pos.y());
|
||||||
zoomedPosition = profileScene->calcZoomPosition(calcZoom(zoomLevel),
|
|
||||||
panningOriginalProfilePosition,
|
|
||||||
panningOriginalMousePosition - pos.x());
|
|
||||||
if (oldPos != zoomedPosition)
|
|
||||||
plotDive(d, dc, RenderFlags::Instant | RenderFlags::DontRecalculatePlotInfo); // TODO: animations don't work when scrolling
|
|
||||||
}
|
|
||||||
|
|
||||||
//toolTipItem->refresh(d, mapToScene(mapFromGlobal(QCursor::pos())), currentState == PLAN);
|
//toolTipItem->refresh(d, mapToScene(mapFromGlobal(QCursor::pos())), currentState == PLAN);
|
||||||
|
|
||||||
|
@ -296,3 +285,65 @@ void ProfileView::mouseMoveEvent(QMouseEvent *event)
|
||||||
//mouseFollowerVertical->setLine(x, rect.top(), x, rect.bottom());
|
//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
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,11 @@ class ProfileScene;
|
||||||
|
|
||||||
class ProfileView : public ChartView {
|
class ProfileView : public ChartView {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
// Communication with the mobile interface is via properties. I hate it.
|
||||||
|
Q_PROPERTY(int diveId READ getDiveId WRITE setDiveId)
|
||||||
|
Q_PROPERTY(int numDC READ numDC NOTIFY numDCChanged)
|
||||||
|
Q_PROPERTY(double zoomLevel MEMBER zoomLevel NOTIFY zoomLevelChanged)
|
||||||
public:
|
public:
|
||||||
ProfileView();
|
ProfileView();
|
||||||
ProfileView(QQuickItem *parent);
|
ProfileView(QQuickItem *parent);
|
||||||
|
@ -28,10 +33,21 @@ public:
|
||||||
void clear();
|
void clear();
|
||||||
void resetZoom();
|
void resetZoom();
|
||||||
void anim(double fraction);
|
void anim(double fraction);
|
||||||
|
|
||||||
|
// For mobile
|
||||||
|
Q_INVOKABLE void pinchStart();
|
||||||
|
Q_INVOKABLE void pinch(double factor);
|
||||||
|
Q_INVOKABLE void nextDC();
|
||||||
|
Q_INVOKABLE void prevDC();
|
||||||
|
Q_INVOKABLE void panStart(double x, double y);
|
||||||
|
Q_INVOKABLE void pan(double x, double y);
|
||||||
|
signals:
|
||||||
|
void numDCChanged();
|
||||||
|
void zoomLevelChanged();
|
||||||
private:
|
private:
|
||||||
const struct dive *d;
|
const struct dive *d;
|
||||||
int dc;
|
int dc;
|
||||||
int zoomLevel;
|
double zoomLevel, zoomLevelPinchStart;
|
||||||
double zoomedPosition; // Position when zoomed: 0.0 = beginning, 1.0 = end.
|
double zoomedPosition; // Position when zoomed: 0.0 = beginning, 1.0 = end.
|
||||||
bool panning; // Currently panning.
|
bool panning; // Currently panning.
|
||||||
double panningOriginalMousePosition;
|
double panningOriginalMousePosition;
|
||||||
|
@ -46,12 +62,18 @@ private:
|
||||||
void plotAreaChanged(const QSizeF &size) override;
|
void plotAreaChanged(const QSizeF &size) override;
|
||||||
void resetPointers() override;
|
void resetPointers() override;
|
||||||
void replot();
|
void replot();
|
||||||
void setZoom(int level);
|
void setZoom(double level);
|
||||||
|
|
||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
void mousePressEvent(QMouseEvent *event) override;
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
void mouseMoveEvent(QMouseEvent *event) override;
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
|
||||||
|
// For mobile
|
||||||
|
int getDiveId() const;
|
||||||
|
void setDiveId(int id);
|
||||||
|
int numDC() const;
|
||||||
|
void rotateDC(int dir);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,163 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-2.
|
|
||||||
#include "qmlprofile.h"
|
|
||||||
#include "profilescene.h"
|
|
||||||
#include "mobile-widgets/qmlmanager.h"
|
|
||||||
#include "core/divelist.h"
|
|
||||||
#include "core/errorhelper.h"
|
|
||||||
#include "core/subsurface-float.h"
|
|
||||||
#include "core/metrics.h"
|
|
||||||
#include "core/subsurface-string.h"
|
|
||||||
#include <QTransform>
|
|
||||||
#include <QScreen>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
|
|
||||||
QMLProfile::QMLProfile(QQuickItem *parent) :
|
|
||||||
QQuickPaintedItem(parent),
|
|
||||||
m_diveId(0),
|
|
||||||
m_dc(0),
|
|
||||||
m_devicePixelRatio(1.0),
|
|
||||||
m_margin(0),
|
|
||||||
m_xOffset(0.0),
|
|
||||||
m_yOffset(0.0)
|
|
||||||
{
|
|
||||||
createProfileView();
|
|
||||||
setAntialiasing(true);
|
|
||||||
setFlags(QQuickItem::ItemClipsChildrenToShape | QQuickItem::ItemHasContents );
|
|
||||||
connect(QMLManager::instance(), &QMLManager::sendScreenChanged, this, &QMLProfile::screenChanged);
|
|
||||||
connect(this, &QMLProfile::scaleChanged, this, &QMLProfile::triggerUpdate);
|
|
||||||
connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &QMLProfile::divesChanged);
|
|
||||||
setDevicePixelRatio(QMLManager::instance()->lastDevicePixelRatio());
|
|
||||||
}
|
|
||||||
|
|
||||||
QMLProfile::~QMLProfile()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::createProfileView()
|
|
||||||
{
|
|
||||||
m_profileWidget.reset(new ProfileScene(m_devicePixelRatio * 0.8, false, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need this so we can connect update() to the scaleChanged() signal - which the connect above cannot do
|
|
||||||
// directly as it chokes on the default parameter for update().
|
|
||||||
// If the scale changes we may need to change our offsets to ensure that we still only show a subset of
|
|
||||||
// the profile and not empty space around it, which the paint() method below will take care of, which will
|
|
||||||
// eventually get called after we call update()
|
|
||||||
void QMLProfile::triggerUpdate()
|
|
||||||
{
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::paint(QPainter *painter)
|
|
||||||
{
|
|
||||||
QElapsedTimer timer;
|
|
||||||
if (verbose)
|
|
||||||
timer.start();
|
|
||||||
|
|
||||||
// let's look at the intended size of the content and scale our scene accordingly
|
|
||||||
// for some odd reason the painter transformation is set up to scale by the dpr - which results
|
|
||||||
// in applying that dpr scaling twice. So we hard-code it here to be the identity matrix
|
|
||||||
QRect painterRect = painter->viewport();
|
|
||||||
painter->resetTransform();
|
|
||||||
if (m_diveId < 0)
|
|
||||||
return;
|
|
||||||
struct dive *d = divelog.dives.get_by_uniq_id(m_diveId);
|
|
||||||
if (!d)
|
|
||||||
return;
|
|
||||||
m_profileWidget->draw(painter, painterRect, d, m_dc, nullptr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::setMargin(int margin)
|
|
||||||
{
|
|
||||||
m_margin = margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
int QMLProfile::diveId() const
|
|
||||||
{
|
|
||||||
return m_diveId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::setDiveId(int diveId)
|
|
||||||
{
|
|
||||||
m_diveId = diveId;
|
|
||||||
emit numDCChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal QMLProfile::devicePixelRatio() const
|
|
||||||
{
|
|
||||||
return m_devicePixelRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::setDevicePixelRatio(qreal dpr)
|
|
||||||
{
|
|
||||||
if (dpr != m_devicePixelRatio) {
|
|
||||||
m_devicePixelRatio = dpr;
|
|
||||||
// Recreate the view to redraw the text items with the new scale.
|
|
||||||
createProfileView();
|
|
||||||
emit devicePixelRatioChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't update the profile here, have the user update x and y and then manually trigger an update
|
|
||||||
void QMLProfile::setXOffset(qreal value)
|
|
||||||
{
|
|
||||||
if (nearly_equal(value, m_xOffset))
|
|
||||||
return;
|
|
||||||
m_xOffset = value;
|
|
||||||
emit xOffsetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't update the profile here, have the user update x and y and then manually trigger an update
|
|
||||||
void QMLProfile::setYOffset(qreal value)
|
|
||||||
{
|
|
||||||
if (nearly_equal(value, m_yOffset))
|
|
||||||
return;
|
|
||||||
m_yOffset = value;
|
|
||||||
emit yOffsetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::screenChanged(QScreen *screen)
|
|
||||||
{
|
|
||||||
setDevicePixelRatio(screen->devicePixelRatio());
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::divesChanged(const QVector<dive *> &dives, DiveField)
|
|
||||||
{
|
|
||||||
for (struct dive *d: dives) {
|
|
||||||
if (d->id == m_diveId) {
|
|
||||||
report_info("dive #%d changed, trigger profile update", d->number);
|
|
||||||
triggerUpdate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::nextDC()
|
|
||||||
{
|
|
||||||
rotateDC(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::prevDC()
|
|
||||||
{
|
|
||||||
rotateDC(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLProfile::rotateDC(int dir)
|
|
||||||
{
|
|
||||||
struct dive *d = divelog.dives.get_by_uniq_id(m_diveId);
|
|
||||||
if (!d)
|
|
||||||
return;
|
|
||||||
int numDC = d->number_of_computers();
|
|
||||||
if (numDC == 1)
|
|
||||||
return;
|
|
||||||
m_dc = (m_dc + dir) % numDC;
|
|
||||||
if (m_dc < 0)
|
|
||||||
m_dc += numDC;
|
|
||||||
triggerUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
int QMLProfile::numDC() const
|
|
||||||
{
|
|
||||||
struct dive *d = divelog.dives.get_by_uniq_id(m_diveId);
|
|
||||||
return d ? d->number_of_computers() : 0;
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0
|
|
||||||
#ifndef QMLPROFILE_H
|
|
||||||
#define QMLPROFILE_H
|
|
||||||
|
|
||||||
#include "core/subsurface-qt/divelistnotifier.h"
|
|
||||||
#include <QQuickPaintedItem>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class ProfileScene;
|
|
||||||
|
|
||||||
class QMLProfile : public QQuickPaintedItem
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(int diveId MEMBER m_diveId WRITE setDiveId)
|
|
||||||
Q_PROPERTY(int numDC READ numDC NOTIFY numDCChanged)
|
|
||||||
Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged)
|
|
||||||
Q_PROPERTY(qreal xOffset MEMBER m_xOffset WRITE setXOffset NOTIFY xOffsetChanged)
|
|
||||||
Q_PROPERTY(qreal yOffset MEMBER m_yOffset WRITE setYOffset NOTIFY yOffsetChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit QMLProfile(QQuickItem *parent = 0);
|
|
||||||
~QMLProfile();
|
|
||||||
|
|
||||||
void paint(QPainter *painter);
|
|
||||||
|
|
||||||
int diveId() const;
|
|
||||||
void setDiveId(int diveId);
|
|
||||||
qreal devicePixelRatio() const;
|
|
||||||
void setDevicePixelRatio(qreal dpr);
|
|
||||||
void setXOffset(qreal value);
|
|
||||||
void setYOffset(qreal value);
|
|
||||||
Q_INVOKABLE void nextDC();
|
|
||||||
Q_INVOKABLE void prevDC();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void setMargin(int margin);
|
|
||||||
void screenChanged(QScreen *screen);
|
|
||||||
void triggerUpdate();
|
|
||||||
|
|
||||||
private:
|
|
||||||
int m_diveId;
|
|
||||||
int m_dc;
|
|
||||||
qreal m_devicePixelRatio;
|
|
||||||
int m_margin;
|
|
||||||
qreal m_xOffset, m_yOffset;
|
|
||||||
std::unique_ptr<ProfileScene> m_profileWidget;
|
|
||||||
void createProfileView();
|
|
||||||
void rotateDC(int dir);
|
|
||||||
int numDC() const;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void divesChanged(const QVector<dive *> &dives, DiveField);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void rightAlignedChanged();
|
|
||||||
void devicePixelRatioChanged();
|
|
||||||
void xOffsetChanged();
|
|
||||||
void yOffsetChanged();
|
|
||||||
void numDCChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // QMLPROFILE_H
|
|
|
@ -13,10 +13,6 @@ ChartView::ChartView(QQuickItem *parent, size_t maxZ) : QQuickItem(parent),
|
||||||
setFlag(ItemHasContents, true);
|
setFlag(ItemHasContents, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChartView::~ChartView()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define a hideable dummy QSG node that is used as a parent node to make
|
// Define a hideable dummy QSG node that is used as a parent node to make
|
||||||
// all objects of a z-level visible / invisible.
|
// all objects of a z-level visible / invisible.
|
||||||
using ZNode = HideableQSGNode<QSGNode>;
|
using ZNode = HideableQSGNode<QSGNode>;
|
||||||
|
@ -24,22 +20,22 @@ using ZNode = HideableQSGNode<QSGNode>;
|
||||||
class RootNode : public QSGNode
|
class RootNode : public QSGNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RootNode(ChartView &view, QColor backgroundColor, size_t maxZ);
|
RootNode(ChartView *view, QColor backgroundColor, size_t maxZ);
|
||||||
~RootNode();
|
~RootNode();
|
||||||
ChartView &view;
|
ChartView *view;
|
||||||
std::unique_ptr<QSGRectangleNode> backgroundNode; // solid background
|
std::unique_ptr<QSGRectangleNode> backgroundNode; // solid background
|
||||||
// We entertain one node per Z-level.
|
// We entertain one node per Z-level.
|
||||||
std::vector<std::unique_ptr<ZNode>> zNodes;
|
std::vector<std::unique_ptr<ZNode>> zNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
RootNode::RootNode(ChartView &view, QColor backgroundColor, size_t maxZ) : view(view)
|
RootNode::RootNode(ChartView *view, QColor backgroundColor, size_t maxZ) : view(view)
|
||||||
{
|
{
|
||||||
zNodes.resize(maxZ);
|
zNodes.resize(maxZ);
|
||||||
|
|
||||||
// Add a background rectangle with a solid color. This could
|
// Add a background rectangle with a solid color. This could
|
||||||
// also be done on the widget level, but would have to be done
|
// also be done on the widget level, but would have to be done
|
||||||
// separately for desktop and mobile, so do it here.
|
// separately for desktop and mobile, so do it here.
|
||||||
backgroundNode.reset(view.w()->createRectangleNode());
|
backgroundNode.reset(view->w()->createRectangleNode());
|
||||||
appendChildNode(backgroundNode.get());
|
appendChildNode(backgroundNode.get());
|
||||||
|
|
||||||
for (auto &zNode: zNodes) {
|
for (auto &zNode: zNodes) {
|
||||||
|
@ -50,7 +46,16 @@ RootNode::RootNode(ChartView &view, QColor backgroundColor, size_t maxZ) : view(
|
||||||
|
|
||||||
RootNode::~RootNode()
|
RootNode::~RootNode()
|
||||||
{
|
{
|
||||||
view.emergencyShutdown();
|
if (view)
|
||||||
|
view->emergencyShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChartView::~ChartView()
|
||||||
|
{
|
||||||
|
// Sometimes the rootNode is destructed before the view,
|
||||||
|
// sometimes the other way around. QtQuick is a mess!
|
||||||
|
if (rootNode)
|
||||||
|
rootNode->view = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChartView::freeDeletedChartItems()
|
void ChartView::freeDeletedChartItems()
|
||||||
|
@ -69,7 +74,7 @@ QSGNode *ChartView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNod
|
||||||
// This is just a copy of what is found in Qt's documentation.
|
// This is just a copy of what is found in Qt's documentation.
|
||||||
RootNode *n = static_cast<RootNode *>(oldNode);
|
RootNode *n = static_cast<RootNode *>(oldNode);
|
||||||
if (!n)
|
if (!n)
|
||||||
n = rootNode = new RootNode(*this, backgroundColor, maxZ);
|
n = rootNode = new RootNode(this, backgroundColor, maxZ);
|
||||||
|
|
||||||
// Delete all chart items that are marked for deletion.
|
// Delete all chart items that are marked for deletion.
|
||||||
freeDeletedChartItems();
|
freeDeletedChartItems();
|
||||||
|
@ -93,7 +98,7 @@ QSGNode *ChartView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNod
|
||||||
// permission to do so! If the widget is reused, we try to delete the
|
// permission to do so! If the widget is reused, we try to delete the
|
||||||
// stale items, whose nodes have already been deleted by QtQuick, leading
|
// stale items, whose nodes have already been deleted by QtQuick, leading
|
||||||
// to a double-free(). Instead of searching for the cause of this behavior,
|
// to a double-free(). Instead of searching for the cause of this behavior,
|
||||||
// let's just hook into the rootNodes destructor and delete the objects
|
// let's just hook into the rootNode's destructor and delete the objects
|
||||||
// in a controlled manner, so that QtQuick has no more access to them.
|
// in a controlled manner, so that QtQuick has no more access to them.
|
||||||
void ChartView::emergencyShutdown()
|
void ChartView::emergencyShutdown()
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
#include "qt-models/divesummarymodel.h"
|
#include "qt-models/divesummarymodel.h"
|
||||||
#include "qt-models/messagehandlermodel.h"
|
#include "qt-models/messagehandlermodel.h"
|
||||||
#include "qt-models/mobilelistmodel.h"
|
#include "qt-models/mobilelistmodel.h"
|
||||||
#include "profile-widget/qmlprofile.h"
|
|
||||||
#include "core/downloadfromdcthread.h"
|
#include "core/downloadfromdcthread.h"
|
||||||
#include "core/subsurfacestartup.h" // for testqml
|
#include "core/subsurfacestartup.h" // for testqml
|
||||||
#include "core/metrics.h"
|
#include "core/metrics.h"
|
||||||
|
@ -220,7 +219,6 @@ static void register_qml_types(QQmlEngine *engine)
|
||||||
#ifdef SUBSURFACE_MOBILE
|
#ifdef SUBSURFACE_MOBILE
|
||||||
register_qml_type<QMLManager>("QMLManager");
|
register_qml_type<QMLManager>("QMLManager");
|
||||||
register_qml_type<StatsManager>("StatsManager");
|
register_qml_type<StatsManager>("StatsManager");
|
||||||
register_qml_type<QMLProfile>("QMLProfile");
|
|
||||||
register_qml_type<DiveImportedModel>("DCImportModel");
|
register_qml_type<DiveImportedModel>("DCImportModel");
|
||||||
register_qml_type<DiveSummaryModel>("DiveSummaryModel");
|
register_qml_type<DiveSummaryModel>("DiveSummaryModel");
|
||||||
register_qml_type<ChartListModel>("ChartListModel");
|
register_qml_type<ChartListModel>("ChartListModel");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue