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:
Berthold Stoeger 2023-05-20 20:58:38 +02:00
parent 413b236867
commit e3767976a3
9 changed files with 129 additions and 316 deletions

View file

@ -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 \

View file

@ -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
} }
} }

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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()
{ {

View file

@ -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");