diff --git a/Subsurface-mobile.pro b/Subsurface-mobile.pro index 04fadc80d..bfda0fa80 100644 --- a/Subsurface-mobile.pro +++ b/Subsurface-mobile.pro @@ -182,6 +182,7 @@ SOURCES += subsurface-mobile-main.cpp \ profile-widget/tooltipitem.cpp \ profile-widget/divelineitem.cpp \ profile-widget/divetextitem.cpp \ + profile-widget/handleitem.cpp \ profile-widget/profileview.cpp \ profile-widget/ruleritem.cpp @@ -346,6 +347,7 @@ HEADERS += \ profile-widget/divelineitem.h \ profile-widget/divepixmapcache.h \ profile-widget/divetextitem.h \ + profile-widget/handleitem.h \ profile-widget/profileview.h \ profile-widget/ruleritem.h \ profile-widget/profiletranslations.h diff --git a/core/gas.h b/core/gas.h index ee583877b..0a1a92b6a 100644 --- a/core/gas.h +++ b/core/gas.h @@ -15,6 +15,15 @@ struct gasmix { fraction_t o2; fraction_t he; std::string name() const; + // TODO: use spaceship operator once we move to C++20 + bool operator==(const gasmix &g2) const + { + return o2.permille == g2.o2.permille && he.permille == g2.he.permille; + } + bool operator!=(const gasmix &g2) const + { + return !(*this == g2); + } }; static const struct gasmix gasmix_invalid = { { .permille = -1 }, { .permille = -1 } }; static const struct gasmix gasmix_air = { 0_percent, 0_percent }; diff --git a/desktop-widgets/profilewidget.cpp b/desktop-widgets/profilewidget.cpp index f7cb687db..90dc32a31 100644 --- a/desktop-widgets/profilewidget.cpp +++ b/desktop-widgets/profilewidget.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -201,15 +202,6 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), placingCommand(false) ui.profTissues->setChecked(qPrefTechnicalDetails::percentagegraph()); ui.profScaled->setChecked(qPrefTechnicalDetails::zoomed_plot()); ui.profInfobox->setChecked(qPrefTechnicalDetails::infobox()); - - //connect(&diveListNotifier, &DiveListNotifier::settingsChanged, view.get(), &ProfileWidget2::settingsChanged); - //connect(&diveListNotifier, &DiveListNotifier::cylinderAdded, this, &ProfileWidget::cylindersChanged); - //connect(&diveListNotifier, &DiveListNotifier::cylinderRemoved, this, &ProfileWidget::cylindersChanged); - //connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &ProfileWidget::cylindersChanged); - //connect(view.get(), &ProfileWidget2::stopAdded, this, &ProfileWidget::stopAdded); - //connect(view.get(), &ProfileWidget2::stopRemoved, this, &ProfileWidget::stopRemoved); - //connect(view.get(), &ProfileWidget2::stopMoved, this, &ProfileWidget::stopMoved); - //connect(view.get(), &ProfileWidget2::stopEdited, this, &ProfileWidget::stopEdited); } ProfileWidget::~ProfileWidget() @@ -229,6 +221,16 @@ ProfileView *ProfileWidget::getView() viewWidget->engine()->setObjectOwnership(view, QQmlEngine::CppOwnership); view->setParent(this); view->setVisible(isVisible()); // Synchronize visibility of widget and QtQuick-view. + + if (!view->initialized) { + view->setPlannerModel(*DivePlannerPointsModel::instance()); + + //connect(&diveListNotifier, &DiveListNotifier::settingsChanged, view.get(), &ProfileWidget2::settingsChanged); + connect(view, &ProfileView::stopAdded, this, &ProfileWidget::stopAdded); + connect(view, &ProfileView::stopRemoved, this, &ProfileWidget::stopRemoved); + connect(view, &ProfileView::stopMoved, this, &ProfileWidget::stopMoved); + view->initialized = true; + } } return view; } @@ -303,7 +305,7 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn) auto view = getView(); setEnabledToolbar(d != nullptr); if (editedDive) { - view->plotDive(editedDive.get(), dc); + view->plotDive(editedDive.get(), dc, ProfileView::RenderFlags::EditMode); setDive(editedDive.get(), dc); } else if (d) { //view->setProfileState(d, dc); @@ -404,9 +406,14 @@ void ProfileWidget::editDive() { editedDive = std::make_unique(); copy_dive(d, editedDive.get()); // Work on a copy of the dive + + // We don't want the DivePlannerPointsModel send signals while reloading, + // because that would reload the just deleted dive. And we will be reloading + // the dive anyway. Control-flow here is truly horrible. + QSignalBlocker blocker(DivePlannerPointsModel::instance()); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::EDIT); DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc); - //view->setEditState(editedDive.get(), dc); + //view->setEditState(editedDive.get(), editedDc); } void ProfileWidget::exitEditMode() diff --git a/profile-widget/CMakeLists.txt b/profile-widget/CMakeLists.txt index c8f5846eb..35afa36b3 100644 --- a/profile-widget/CMakeLists.txt +++ b/profile-widget/CMakeLists.txt @@ -16,6 +16,8 @@ set(SUBSURFACE_PROFILE_LIB_SRCS diveprofileitem.h divetextitem.cpp divetextitem.h + handleitem.cpp + handleitem.h pictureitem.h pictureitem.cpp profilescene.cpp @@ -37,8 +39,6 @@ set(SUBSURFACE_PROFILE_LIB_SRCS else () set(SUBSURFACE_PROFILE_LIB_SRCS ${SUBSURFACE_PROFILE_LIB_SRCS} - divehandler.cpp - divehandler.h ) endif () source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS}) diff --git a/profile-widget/divehandler.cpp b/profile-widget/divehandler.cpp deleted file mode 100644 index ed5fb0346..000000000 --- a/profile-widget/divehandler.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "divehandler.h" -#include "profilescene.h" -#include "core/dive.h" -#include "core/gettextfromc.h" -#include "core/qthelper.h" -#include "qt-models/diveplannermodel.h" - -#include -#include -#include - -DiveHandler::DiveHandler(const struct dive *d, int currentDcNr) : dive(d), dcNr(currentDcNr) -{ - setRect(-5, -5, 10, 10); - setFlags(ItemIgnoresTransformations | ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges); - setBrush(Qt::white); - setZValue(2); - t.start(); -} - -int DiveHandler::parentIndex() -{ - //ProfileWidget2 *view = qobject_cast(scene()->views().first()); - //return view->handleIndex(this); - return 0; // FIXME -} - -void DiveHandler::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) -{ - QMenu m; - // Don't have a gas selection for the last point - emit released(); - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS); - if (index.sibling(index.row() + 1, index.column()).isValid()) { - std::vector> gases = get_dive_gas_list(dive, dcNr, true); - for (unsigned i = 0; i < gases.size(); i++) { - QAction *action = new QAction(&m); - action->setText(gases[i].second); - action->setData(gases[i].first); - connect(action, &QAction::triggered, this, &DiveHandler::changeGas); - m.addAction(action); - } - } - // don't allow removing the last point - if (plannerModel->rowCount() > 1) { - m.addSeparator(); - m.addAction(gettextFromC::tr("Remove this point"), this, &DiveHandler::selfRemove); - m.exec(event->screenPos()); - } -} - -void DiveHandler::selfRemove() -{ - setSelected(true); - //ProfileWidget2 *view = qobject_cast(scene()->views().first()); - //view->keyDeleteAction(); -} - -void DiveHandler::changeGas() -{ - //ProfileWidget2 *view = qobject_cast(scene()->views().first()); - //QAction *action = qobject_cast(sender()); - - //view->changeGas(parentIndex(), action->data().toInt()); -} - -void DiveHandler::mouseMoveEvent(QGraphicsSceneMouseEvent *event) -{ - if (t.elapsed() < 40) - return; - t.start(); - - //ProfileWidget2 *view = qobject_cast(scene()->views().first()); - //if(!view->profileScene->pointOnProfile(event->scenePos())) - //return; - - QGraphicsEllipseItem::mouseMoveEvent(event); - emit moved(); -} - -void DiveHandler::mousePressEvent(QGraphicsSceneMouseEvent *event) -{ - QGraphicsItem::mousePressEvent(event); - emit clicked(); -} - -void DiveHandler::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) -{ - QGraphicsItem::mouseReleaseEvent(event); - emit released(); -} diff --git a/profile-widget/divehandler.h b/profile-widget/divehandler.h deleted file mode 100644 index d56f3cc28..000000000 --- a/profile-widget/divehandler.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef DIVEHANDLER_HPP -#define DIVEHANDLER_HPP - -#include -#include - -struct dive; - -class DiveHandler : public QObject, public QGraphicsEllipseItem { - Q_OBJECT -public: - DiveHandler(const struct dive *d, int currentDcNr); - -protected: - void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); -signals: - void moved(); - void clicked(); - void released(); -private: - int parentIndex(); -public -slots: - void selfRemove(); - void changeGas(); -private: - const struct dive *dive; - int dcNr; - QElapsedTimer t; -}; - -#endif diff --git a/profile-widget/handleitem.cpp b/profile-widget/handleitem.cpp new file mode 100644 index 000000000..d8ee30c19 --- /dev/null +++ b/profile-widget/handleitem.cpp @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "handleitem.h" +#include "profileview.h" +#include "zvalues.h" + +#include + +static QColor handleBorderColor(Qt::black); +static QColor handleColor(Qt::white); +static QColor gasColor(Qt::black); +static constexpr double handleRadius = 5.0; + +class HandleItemHandle : public ChartDiskItem { + ProfileView &profileView; + int idx; +public: + HandleItemHandle(ChartView &view, double dpr, int idx, ProfileView &profileView) : + ChartDiskItem(view, + ProfileZValue::Handles, + QPen(handleBorderColor, dpr), + QBrush(handleColor), + true), + profileView(profileView), + idx(idx) + { + } + void setIdx(int idxIn) + { + idx = idxIn; + } + void drag(QPointF pos) override + { + profileView.handleDragged(idx, pos); + } + void startDrag(QPointF) override + { + profileView.handleSelected(idx); + } + void stopDrag(QPointF) override + { + profileView.handleReleased(idx); + } +}; + +HandleItem::HandleItem(ProfileView &view, double dpr, int idx) : + handle(view.createChartItem(dpr, idx, view)), + dpr(dpr), + view(view) +{ + handle->resize(handleRadius * dpr); +} + +HandleItem::~HandleItem() +{ +} + +void HandleItem::del() +{ + handle.del(); + if (text) + text.del(); +} + +void HandleItem::setIdx(int idx) +{ + handle->setIdx(idx); +} + +void HandleItem::setPos(QPointF pos) +{ + handle->setPos(pos); +} + +QPointF HandleItem::getPos() const +{ + return handle->getPos(); +} + +void HandleItem::setTextPos(QPointF pos) +{ + if (text) + text->setPos(pos); +} + +void HandleItem::setVisible(bool handleVisible, bool textVisible) +{ + handle->setVisible(handleVisible); + if (text) + text->setVisible(textVisible); +} + +// duplicate code in tooltipitem.cpp +static QFont makeFont(double dpr) +{ + QFont font(qApp->font()); + if (dpr != 1.0) { + int pixelSize = font.pixelSize(); + if (pixelSize > 0) { + pixelSize = lrint(static_cast(pixelSize) * dpr); + font.setPixelSize(pixelSize); + } else { + font.setPointSizeF(font.pointSizeF() * dpr); + } + } + return font; +} + +void HandleItem::setText(const QString &s) +{ + if (text && std::exchange(oldText, s) == s) + return; + if (text) + text.del(); + + QFont f = makeFont(dpr); + text = view.createChartItem(ProfileZValue::Handles, f, s); + text->setColor(gasColor); +} + +/* +void HandleItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + QMenu m; + // Don't have a gas selection for the last point + emit released(); + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS); + if (index.sibling(index.row() + 1, index.column()).isValid()) { + QStringList gases = get_dive_gas_list(dive); + for (int i = 0; i < gases.size(); i++) { + QAction *action = new QAction(&m); + action->setText(gases[i]); + action->setData(i); + connect(action, &QAction::triggered, this, &HandleItem::changeGas); + m.addAction(action); + } + } + // don't allow removing the last point + if (plannerModel->rowCount() > 1) { + m.addSeparator(); + m.addAction(gettextFromC::tr("Remove this point"), this, &HandleItem::selfRemove); + m.exec(event->screenPos()); + } +} + +void HandleItem::changeGas() +{ + QAction *action = qobject_cast(sender()); + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS); + plannerModel->gasChange(index.sibling(index.row() + 1, index.column()), action->data().toInt()); +} +*/ diff --git a/profile-widget/handleitem.h b/profile-widget/handleitem.h new file mode 100644 index 000000000..4f72a226f --- /dev/null +++ b/profile-widget/handleitem.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef DIVEHANDLER_HPP +#define DIVEHANDLER_HPP + +#include "qt-quick/chartitem.h" + +class HandleItemHandle; +class HandleItemText; +class ProfileView; + +class HandleItem { + ChartItemPtr handle; + ChartItemPtr text; + QString oldText; +public: + HandleItem(ProfileView &view, double dpr, int idx); + ~HandleItem(); + void setVisible(bool handle, bool text); + void setPos(QPointF point); + QPointF getPos() const; + void setTextPos(QPointF point); + void setText(const QString &text); + void setIdx(int idx); // we may have to rearrange the handles when editing the dive + void del(); // Deletes objects - must not be used any longer +private: + double dpr; + ProfileView &view; +protected: + //void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + //void mousePressEvent(QGraphicsSceneMouseEvent *event); + //void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); +public +slots: + //void selfRemove(); + //void changeGas(); +}; + +#endif diff --git a/profile-widget/profilescene.cpp b/profile-widget/profilescene.cpp index d9014c5bd..ab76b1565 100644 --- a/profile-widget/profilescene.cpp +++ b/profile-widget/profilescene.cpp @@ -612,6 +612,11 @@ int ProfileScene::timeAt(QPointF pos) const return lrint(timeAxis->valueAt(pos)); } +int ProfileScene::depthAt(QPointF pos) const +{ + return lrint(profileYAxis->valueAt(pos)); +} + std::pair ProfileScene::minMaxTime() const { return { timeAxis->minimum(), timeAxis->maximum() }; diff --git a/profile-widget/profilescene.h b/profile-widget/profilescene.h index 8e05b6597..7de2e12aa 100644 --- a/profile-widget/profilescene.h +++ b/profile-widget/profilescene.h @@ -51,6 +51,7 @@ public: double calcZoomPosition(double zoom, double originalPos, double delta); const plot_info &getPlotInfo() const; int timeAt(QPointF pos) const; // time in seconds + int depthAt(QPointF pos) const; // depth in mm std::pair minMaxTime() const; // time in seconds std::pair minMaxX() const; // minimum and maximum x positions of canvas std::pair minMaxY() const; // minimum and maximum y positions of canvas @@ -61,6 +62,7 @@ public: const struct dive *d; int dc; + QRectF profileRegion; // Region inside the axes, where the crosshair is painted in plan and edit mode. private: using DataAccessor = double (*)(const plot_data &data); template T *createItem(const DiveCartesianAxis &vAxis, DataAccessor accessor, int z, Args&&... args); @@ -78,7 +80,6 @@ private: int maxdepth; struct plot_info plotInfo; - QRectF profileRegion; // Region inside the axes, where the crosshair is painted in plan and edit mode. DiveCartesianAxis *profileYAxis; DiveCartesianAxis *gasYAxis; DiveCartesianAxis *temperatureAxis; diff --git a/profile-widget/profileview.cpp b/profile-widget/profileview.cpp index 58220a6b3..c0349c95c 100644 --- a/profile-widget/profileview.cpp +++ b/profile-widget/profileview.cpp @@ -2,6 +2,7 @@ #include "profileview.h" #include "pictureitem.h" #include "profilescene.h" +#include "handleitem.h" #include "ruleritem.h" #include "tooltipitem.h" #include "zvalues.h" @@ -17,6 +18,7 @@ #include "core/settings/qPrefPartialPressureGas.h" #include "core/settings/qPrefTechnicalDetails.h" #include "core/subsurface-qt/divelistnotifier.h" +#include "qt-models/diveplannermodel.h" #include "qt-quick/chartitem.h" #include @@ -25,6 +27,8 @@ #include #include +static const QColor mouseFollowerColor = QColor(Qt::red).lighter(); + // 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. @@ -73,16 +77,20 @@ std::unique_ptr> make_anim(FUNC func, int animSpe : std::unique_ptr>(); } -ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count), +ProfileView::ProfileView(QQuickItem *parent) : + ChartView(parent, ProfileZValue::Count), + initialized(false), d(nullptr), dc(0), + plannerModel(nullptr), + mode(Mode::Normal), simplified(false), dpr(1.0), zoomLevel(1.00), zoomedPosition(0.0), panning(false), empty(true), - shouldCalculateMax(true), + selectedHandleIdx(-1), highlightedPicture(nullptr) { setBackgroundColor(Qt::black); @@ -129,6 +137,7 @@ ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue:: setAcceptTouchEvents(true); setAcceptHoverEvents(true); + setFocus(true); // Necessary to get keyPress events } ProfileView::ProfileView() : ProfileView(nullptr) @@ -139,27 +148,54 @@ ProfileView::~ProfileView() { } +// Since this is a QML object, we can't easily pass the planner model in the constructor. :( +void ProfileView::setPlannerModel(DivePlannerPointsModel &model) +{ + if (plannerModel) + fprintf(stderr, "Warning: setting plannerModel twice is not supported\n"); + plannerModel = &model; + connect(plannerModel, &DivePlannerPointsModel::dataChanged, this, &ProfileView::replot); + connect(plannerModel, &DivePlannerPointsModel::cylinderModelEdited, this, &ProfileView::replot); + connect(plannerModel, &DivePlannerPointsModel::modelReset, this, &ProfileView::resetHandles); + connect(plannerModel, &DivePlannerPointsModel::rowsInserted, this, &ProfileView::pointsInserted); + connect(plannerModel, &DivePlannerPointsModel::rowsRemoved, this, &ProfileView::pointsRemoved); + connect(plannerModel, &DivePlannerPointsModel::rowsMoved, this, &ProfileView::pointsMoved); +} + void ProfileView::resetPointers() { profileItem.reset(); tooltip.reset(); ruler.reset(); + mouseFollowerHorizontal.reset(); + mouseFollowerVertical.reset(); pictures.clear(); + handles.clear(); highlightedPicture = nullptr; } +// Calculate render flags for the current state. +// Used when replotting the current plit. +int ProfileView::rerenderFlags() const +{ + int flags = simplified ? RenderFlags::Simplified : RenderFlags::None; + if (mode == Mode::Edit) + flags |= RenderFlags::EditMode; + else if (mode == Mode::Plan) + flags |= RenderFlags::PlanMode; + return flags; +} + void ProfileView::plotAreaChanged(const QSizeF &s) { - int flags = simplified ? RenderFlags::Simplified : RenderFlags::None; if (!empty) - plotDive(d, dc, flags | RenderFlags::Instant); + plotDive(d, dc, rerenderFlags() | RenderFlags::Instant); } void ProfileView::replot() { - int flags = simplified ? RenderFlags::Simplified : RenderFlags::None; if (!empty) - plotDive(d, dc, flags); + plotDive(d, dc, rerenderFlags()); } void ProfileView::clear() @@ -168,12 +204,15 @@ void ProfileView::clear() //disconnectPlannerConnections(); if (profileScene) profileScene->clear(); - //handles.clear(); - //gases.clear(); + clearHandles(); if (tooltip) tooltip->setVisible(false); if (ruler) ruler->setVisible(false); + if (mouseFollowerHorizontal) + mouseFollowerHorizontal->setVisible(false); + if (mouseFollowerVertical) + mouseFollowerVertical->setVisible(false); empty = true; d = nullptr; dc = 0; @@ -181,23 +220,29 @@ void ProfileView::clear() void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) { - d = dIn; - dc = dcIn; + bool diveChanged = std::exchange(d, dIn) != d; + diveChanged |= std::exchange(dc, dcIn) != dc; simplified = flags & RenderFlags::Simplified; if (!d) { clear(); return; } + if (flags & RenderFlags::PlanMode) + mode = Mode::Plan; + else if (flags & RenderFlags::EditMode) + mode = Mode::Edit; + else + mode = Mode::Normal; + // 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(dpr, false, false); } // If there was no previously displayed dive, turn off animations - if (empty) + if (std::exchange(empty, false)) flags |= RenderFlags::Instant; - empty = false; // If Qt decided to destroy our canvas, recreate it if (!profileItem) @@ -208,23 +253,27 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) 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; + DivePlannerPointsModel *model = flags & (RenderFlags::EditMode | RenderFlags::PlanMode) ? plannerModel : nullptr; bool inPlanner = flags & RenderFlags::PlanMode; int animSpeed = flags & RenderFlags::Instant ? 0 : qPrefDisplay::animation_speed(); + bool calculateMax = !(flags & RenderFlags::DontCalculateMax); profileScene->resize(size()); profileScene->plotDive(d, dc, animSpeed, simplified, model, inPlanner, flags & RenderFlags::DontRecalculatePlotInfo, - shouldCalculateMax, zoomLevel, zoomedPosition); + calculateMax, zoomLevel, zoomedPosition); background = inPlanner ? QColor("#D7E3EF") : getColor(::BACKGROUND, false); profileItem->draw(size(), background, *profileScene); - //if ((currentState == EDIT || currentState == PLAN) && plannerModel) { - //repositionDiveHandlers(); - //plannerModel->deleteTemporaryPlan(); - //} + if ((mode == Mode::Edit || mode == Mode::Plan) && plannerModel) { + if (diveChanged) + resetHandles(); + placeHandles(); + plannerModel->deleteTemporaryPlan(); + } else { + clearHandles(); + } // On zoom / pan don't recreate the picture thumbnails, only change their position. if (!inPlanner) { @@ -267,6 +316,15 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) ruler->setVisible(false); } + if (!mouseFollowerHorizontal) + mouseFollowerHorizontal = createChartItem(ProfileZValue::MouseFollower, + mouseFollowerColor, 1.0 * dpr); + if (!mouseFollowerVertical) + mouseFollowerVertical = createChartItem(ProfileZValue::MouseFollower, + mouseFollowerColor, 1.0 * dpr); + mouseFollowerHorizontal->setVisible(mode == Mode::Edit || mode == Mode::Plan); + mouseFollowerVertical->setVisible(mode == Mode::Edit || mode == Mode::Plan); + // Reset animation. animation = make_anim([this](double progress) { anim(progress); }, animSpeed); } @@ -290,9 +348,8 @@ void ProfileView::setZoom(double level) { level = std::clamp(level, 1.0, 20.0); double old = std::exchange(zoomLevel, level); - int flags = simplified ? RenderFlags::Simplified : RenderFlags::None; if (level != old) - plotDive(d, dc, flags | RenderFlags::DontRecalculatePlotInfo); + plotDive(d, dc, rerenderFlags() | RenderFlags::DontRecalculatePlotInfo); emit zoomLevelChanged(); } @@ -361,10 +418,8 @@ void ProfileView::mouseReleaseEvent(QMouseEvent *event) panning = false; unsetCursor(); } - //if (currentState == PLAN || currentState == EDIT) { - // shouldCalculateMax = true; - // replot(); - //} + if (mode == Mode::Plan || mode == Mode::Edit) + replot(); } void ProfileView::mouseMoveEvent(QMouseEvent *event) @@ -375,14 +430,7 @@ void ProfileView::mouseMoveEvent(QMouseEvent *event) 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()); - //} + updateMouseFollowers(pos); } int ProfileView::getDiveId() const @@ -444,9 +492,8 @@ void ProfileView::pan(double x, double y) zoomedPosition = profileScene->calcZoomPosition(zoomLevel, panningOriginalProfilePosition, panningOriginalMousePosition - x); - int flags = simplified ? RenderFlags::Simplified : RenderFlags::None; if (oldPos != zoomedPosition) - plotDive(d, dc, flags | RenderFlags::Instant | RenderFlags::DontRecalculatePlotInfo); // TODO: animations don't work when scrolling + plotDive(d, dc, rerenderFlags() | RenderFlags::Instant | RenderFlags::DontRecalculatePlotInfo); // TODO: animations don't work when scrolling } void ProfileView::hoverEnterEvent(QHoverEvent *) @@ -533,6 +580,211 @@ void ProfileView::hoverMoveEvent(QHoverEvent *event) highlightedPicture = nullptr; update(); } + + updateMouseFollowers(pos); +} + +void ProfileView::updateMouseFollowers(QPointF pos) +{ + if (!mouseFollowerHorizontal || !mouseFollowerVertical) + return; + if (mode != Mode::Plan && mode != Mode::Edit) + return; + QRectF rect = profileScene->profileRegion; + double x = std::clamp(pos.x(), rect.left(), rect.right()); + double y = std::clamp(pos.y(), rect.top(), rect.bottom()); + mouseFollowerHorizontal->setLine(QPointF(rect.left(), y), QPointF(rect.right(), y)); + mouseFollowerVertical->setLine(QPointF(x, rect.top()), QPointF(x, rect.bottom())); +} + +void ProfileView::placeHandles() +{ + if (!plannerModel) + return; + + int count = plannerModel->rowCount(); + if ((size_t)count != handles.size()) { + fprintf(stderr, "Handle number inconsistent with planner model\n"); + return; + } + + // Place the dive handles + gasmix prevgas = gasmix_invalid; + for (int i = 0; i < count; i++) { + struct divedatapoint datapoint = plannerModel->at(i); + if (datapoint.time == 0) // those are the magic entries for tanks + continue; + auto &h = *handles[i]; + if (!datapoint.entered) { + h.setVisible(false, false); + continue; + } + + h.setPos(QPointF(profileScene->posAtTime(datapoint.time), profileScene->posAtDepth(datapoint.depth.mm))); + + gasmix gas = d->get_cylinder(datapoint.cylinderid)->gasmix; + bool textVisible = datapoint.cylinderid >= 0 && datapoint.cylinderid < static_cast(d->cylinders.size()) && prevgas != gas; + prevgas = gas; + if (textVisible) { + h.setText(get_gas_string(gas)); + QPointF p1; + if (i == 0) { + if (prefs.drop_stone_mode) + // place the text on the straight line from the drop to stone position + p1 = QPointF(profileScene->posAtTime(datapoint.depth.mm / prefs.descrate), + profileScene->posAtDepth(datapoint.depth.mm)); + else + // place the text on the straight line from the origin to the first position + p1 = QPointF(profileScene->posAtTime(0), profileScene->posAtDepth(0)); + } else { + // place the text on the line from the last position + p1 = handles[i - 1]->getPos(); + } + QPointF p2 = h.getPos(); + QLineF line(p1, p2); + QPointF pos = line.pointAt(0.5); + h.setTextPos(pos); + } + h.setVisible(true, textVisible); + } +} + +void ProfileView::handleSelected(int idx) +{ + selectedHandleIdx = idx; +} + +// TODO: Move the editing code from the desktop-widget to here. +// The problem is that the dragging tramples on the dive struct, so we work on a copy of the dive. +void ProfileView::handleReleased(int) +{ + if (mode == Mode::Edit) + emit stopMoved(1); + replot(); +} + +void ProfileView::keyPressEvent(QKeyEvent *e) +{ + if ((mode != Mode::Edit && mode != Mode::Plan) || !plannerModel || selectedHandleIdx < 0) + return ChartView::keyPressEvent(e); + + switch (e->key()) { + case Qt::Key_Delete: return deleteHandle(); + case Qt::Key_Up: return moveHandle(0, -M_OR_FT(1, 5)); + case Qt::Key_Down: return moveHandle(0, M_OR_FT(1, 5)); + case Qt::Key_Left: return moveHandle(-60, 0); + case Qt::Key_Right: return moveHandle(60, 0); + } + ChartView::keyPressEvent(e); +} + +// TODO: Handle multi-selection +void ProfileView::moveHandle(int time, int mm) +{ + if (!plannerModel || selectedHandleIdx < 0 || selectedHandleIdx >= plannerModel->rowCount()) + return; + + divedatapoint dp = plannerModel->at(selectedHandleIdx); + + dp.depth.mm += mm; + dp.time += time; + if (dp.depth.mm < 0 || dp.time < 0) + return; + plannerModel->editStop(selectedHandleIdx, dp); + + if (mode == Mode::Edit) + emit stopMoved(1); // TODO: Accumulate key moves + replot(); +} + +// TODO: Handle multi-selection +void ProfileView::deleteHandle() +{ + if (!plannerModel || selectedHandleIdx < 0 || selectedHandleIdx >= plannerModel->rowCount()) + return; + + std::vector handleIndices { selectedHandleIdx }; + plannerModel->removeSelectedPoints(handleIndices); + if (mode == Mode::Edit) + emit stopRemoved(handleIndices.size()); + replot(); +} + +void ProfileView::handleDragged(int idx, QPointF pos) +{ + if (!plannerModel || (size_t)idx >= handles.size()) + return; + + if (!profileScene->pointOnProfile(pos)) + return; + // Grow the time axis if necessary. + //int minutes = lrint(profileScene->timeAxis->valueAt(pos) / 60); + //if (minutes * 60 > profileScene->timeAxis->maximum() * 0.9) + //profileScene->timeAxis->setBounds(0.0, profileScene->timeAxis->maximum() * 1.02); + + divedatapoint data = plannerModel->at(idx); + data.depth.mm = profileScene->depthAt(pos) / M_OR_FT(1, 1) * M_OR_FT(1, 1); + data.time = profileScene->timeAt(pos); + + plannerModel->editStop(idx, data); + plotDive(d, dc, rerenderFlags() | RenderFlags::DontCalculateMax); +} + +void ProfileView::pointsInserted(const QModelIndex &, int from, int to) +{ + for (int i = from; i <= to; ++i) + handles.emplace(handles.begin() + i, std::make_unique(*this, dpr, i)); + reindexHandles(); + + // Note: we don't replot the dive here, because when adding multiple + // points, these might trickle in one-by-one. Instead, the model will + // emit a data-changed signal. +} + +void ProfileView::pointsRemoved(const QModelIndex &, int start, int end) +{ + // Qt's model/view API is mad. The end-point is inclusive, which means that the empty range is [0,-1]! + if (selectedHandleIdx >= start && selectedHandleIdx <= end) + selectedHandleIdx = -1; + handles.erase(handles.begin() + start, handles.begin() + end + 1); + reindexHandles(); + + // Note: we don't replot the dive here, because when removing multiple + // points, these might trickle in one-by-one. Instead, the model will + // emit a data-changed signal. +} + +void ProfileView::pointsMoved(const QModelIndex &, int start, int end, const QModelIndex &, int row) +{ + move_in_range(handles, start, end + 1, row); + reindexHandles(); +} + +void ProfileView::reindexHandles() +{ + HandleItem *old_selected = selectedHandleIdx >= 0 ? handles[selectedHandleIdx].get() + : nullptr; + for (int i = 0; (size_t)i < handles.size(); ++i) { + if (handles[i].get() == old_selected) + selectedHandleIdx = i; + handles[i]->setIdx(i); + } +} + +void ProfileView::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (mode != Mode::Edit && mode != Mode::Plan) + return; + + QPointF pos = event->pos(); + if (!profileScene->pointOnProfile(pos)) + return; + + int minutes = lrint(timeAt(pos) / 60); + int milimeters = lrint(profileScene->depthAt(pos) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); + plannerModel->addStop(milimeters, minutes * 60); + if (mode == Mode::Edit) + emit stopAdded(); } void ProfileView::unhighlightPicture() @@ -704,6 +956,25 @@ void ProfileView::clearPictures() highlightedPicture = nullptr; } +void ProfileView::clearHandles() +{ + // The ChartItemPtrs are non-owning, so we have to delete the handles manually. Sad. + for (auto &handle: handles) + handle->del(); + handles.clear(); + selectedHandleIdx = -1; +} + +void ProfileView::resetHandles() +{ + if (!plannerModel) + return; + clearHandles(); + int count = plannerModel->rowCount(); + for (int i = 0; i < count; ++i) + handles.push_back(std::make_unique(*this, dpr, i)); +} + // Helper function to compare offset_ts. static bool operator<(offset_t o1, offset_t o2) { diff --git a/profile-widget/profileview.h b/profile-widget/profileview.h index 4f76be878..ec3d53c34 100644 --- a/profile-widget/profileview.h +++ b/profile-widget/profileview.h @@ -7,13 +7,17 @@ #include class ChartGraphicsSceneItem; +class ChartLineItem; class ChartRectItem; +class DivePlannerPointsModel; +class HandleItem; class PictureItem; class ProfileAnimation; class ProfileScene; class ToolTipItem; struct picture; class RulerItem; +class QModelIndex; class ProfileView : public ChartView { Q_OBJECT @@ -27,6 +31,10 @@ public: ProfileView(QQuickItem *parent); ~ProfileView(); + // Flag set when constructing the object. Because QtQuick may decide to destroy the old one. :( + bool initialized; + void setPlannerModel(DivePlannerPointsModel &model); // enables planning and edit mdoe + struct RenderFlags { static constexpr int None = 0; static constexpr int Instant = 1 << 0; @@ -34,6 +42,7 @@ public: static constexpr int EditMode = 1 << 2; static constexpr int PlanMode = 1 << 3; static constexpr int Simplified = 1 << 4; // For mobile's overview page + static constexpr int DontCalculateMax = 1 << 5; }; void plotDive(const struct dive *d, int dc, int flags = RenderFlags::None); @@ -41,7 +50,10 @@ public: void clear(); void resetZoom(); void anim(double fraction); - void rulerDragged(); // Called by the RulterItem when a handle was dragged. + void rulerDragged(); // Called by the RulterItem when a handle was dragged. + void handleSelected(int idx); // Called by the HandleItem when it is clicked. + void handleDragged(int idx, QPointF pos); // Called by the HandleItem when it is dragged. + void handleReleased(int idx); // Called by the HandleItem when it is released. // For mobile Q_INVOKABLE void pinchStart(); @@ -50,12 +62,23 @@ public: 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(); + void stopAdded(); // only emitted in edit mode + void stopRemoved(int count); // only emitted in edit mode + void stopMoved(int count); // only emitted in edit mode private: + enum Mode { + Normal, + Edit, + Plan + }; const struct dive *d; int dc; + DivePlannerPointsModel *plannerModel; + Mode mode; bool simplified; double dpr; double zoomLevel, zoomLevelPinchStart; @@ -64,15 +87,16 @@ private: double panningOriginalMousePosition; double panningOriginalProfilePosition; bool empty; // No dive shown. - bool shouldCalculateMax; // Calculate maximum time and depth (default). False when dragging handles. QColor background; std::unique_ptr profileScene; ChartItemPtr profileItem; std::unique_ptr animation; + ChartItemPtr mouseFollowerHorizontal, mouseFollowerVertical; void plotAreaChanged(const QSizeF &size) override; void resetPointers() override; void replot(); + int rerenderFlags() const; void setZoom(double level); void hoverEnterEvent(QHoverEvent *event) override; @@ -81,6 +105,8 @@ private: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent *e) override; ChartItemPtr tooltip; void updateTooltip(QPointF pos, bool plannerMode, int animSpeed); @@ -90,7 +116,21 @@ private: void updateRuler(int animSpeed); std::unique_ptr ruler_animation; + void updateMouseFollowers(QPointF pos); + QPointF previousHoverMovePosition; + std::vector> handles; + int selectedHandleIdx; + void clearHandles(); + void resetHandles(); + void placeHandles(); + void reindexHandles(); + void moveHandle(int time, int depth); + void deleteHandle(); + + void pointsInserted(const QModelIndex &, int from, int to); + void pointsRemoved(const QModelIndex &, int start, int end); + void pointsMoved(const QModelIndex &, int start, int end, const QModelIndex &destination, int row); // The list of pictures in this plot. The pictures are sorted by offset in seconds. // For the same offset, sort by filename. diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 975db59cb..ece3f4124 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -236,37 +236,6 @@ void ProfileWidget2::resizeEvent(QResizeEvent *event) } #ifndef SUBSURFACE_MOBILE -void ProfileWidget2::divePlannerHandlerClicked() -{ - shouldCalculateMax = false; -} - -void ProfileWidget2::divePlannerHandlerReleased() -{ - if (currentState == EDIT) - emit stopMoved(1); - shouldCalculateMax = true; - replot(); -} - -#endif - -#ifndef SUBSURFACE_MOBILE -void ProfileWidget2::mouseDoubleClickEvent(QMouseEvent *event) -{ - if ((currentState == PLAN || currentState == EDIT) && plannerModel) { - QPointF mappedPos = mapToScene(event->pos()); - if (!profileScene->pointOnProfile(mappedPos)) - return; - - int minutes = lrint(profileScene->timeAxis->valueAt(mappedPos) / 60); - int milimeters = lrint(profileScene->profileYAxis->valueAt(mappedPos) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); - plannerModel->addStop(milimeters, minutes * 60); - if (currentState == EDIT) - emit stopAdded(); - } -} - bool ProfileWidget2::eventFilter(QObject *object, QEvent *event) { QGraphicsScene *s = qobject_cast(object); @@ -298,8 +267,6 @@ void ProfileWidget2::setProfileState() if (currentState == PROFILE) return; - disconnectPlannerModel(); - currentState = PROFILE; setBackgroundBrush(getColor(::BACKGROUND, profileScene->isGrayscale)); @@ -325,11 +292,8 @@ void ProfileWidget2::setEditState(const dive *d, int dc) mouseFollowerHorizontal->setVisible(true); mouseFollowerVertical->setVisible(true); - connectPlannerModel(); - currentState = EDIT; - pointsReset(); repositionDiveHandlers(); } @@ -342,12 +306,9 @@ void ProfileWidget2::setPlanState(const dive *d, int dc) mouseFollowerHorizontal->setVisible(true); mouseFollowerVertical->setVisible(true); - connectPlannerModel(); - currentState = PLAN; setBackgroundBrush(QColor("#D7E3EF")); - pointsReset(); repositionDiveHandlers(); } #endif @@ -677,242 +638,6 @@ void ProfileWidget2::connectPlannerModel() } #endif -void ProfileWidget2::disconnectPlannerModel() -{ -#ifndef SUBSURFACE_MOBILE - if (plannerModel) { - disconnect(plannerModel, &DivePlannerPointsModel::dataChanged, this, &ProfileWidget2::replot); - disconnect(plannerModel, &DivePlannerPointsModel::cylinderModelEdited, this, &ProfileWidget2::replot); - - disconnect(plannerModel, &DivePlannerPointsModel::modelReset, this, &ProfileWidget2::pointsReset); - disconnect(plannerModel, &DivePlannerPointsModel::rowsInserted, this, &ProfileWidget2::pointInserted); - disconnect(plannerModel, &DivePlannerPointsModel::rowsRemoved, this, &ProfileWidget2::pointsRemoved); - disconnect(plannerModel, &DivePlannerPointsModel::rowsMoved, this, &ProfileWidget2::pointsMoved); - } -#endif -} - -int ProfileWidget2::handleIndex(const DiveHandler *h) const -{ - auto it = std::find_if(handles.begin(), handles.end(), - [h] (const std::unique_ptr &h2) - { return h == h2.get(); }); - return it != handles.end() ? it - handles.begin() : -1; -} - -#ifndef SUBSURFACE_MOBILE - -DiveHandler *ProfileWidget2::createHandle() -{ - DiveHandler *item = new DiveHandler(d, dc); - scene()->addItem(item); - connect(item, &DiveHandler::moved, this, &ProfileWidget2::divePlannerHandlerMoved); - connect(item, &DiveHandler::clicked, this, &ProfileWidget2::divePlannerHandlerClicked); - connect(item, &DiveHandler::released, this, &ProfileWidget2::divePlannerHandlerReleased); - return item; -} - -QGraphicsSimpleTextItem *ProfileWidget2::createGas() -{ - QGraphicsSimpleTextItem *gasChooseBtn = new QGraphicsSimpleTextItem(); - scene()->addItem(gasChooseBtn); - gasChooseBtn->setZValue(10); - gasChooseBtn->setFlag(QGraphicsItem::ItemIgnoresTransformations); - return gasChooseBtn; -} - -void ProfileWidget2::pointsReset() -{ - handles.clear(); - gases.clear(); - int count = plannerModel->rowCount(); - for (int i = 0; i < count; ++i) { - handles.emplace_back(createHandle()); - gases.emplace_back(createGas()); - } -} - -void ProfileWidget2::pointInserted(const QModelIndex &, int from, int to) -{ - for (int i = from; i <= to; ++i) { - handles.emplace(handles.begin() + i, createHandle()); - gases.emplace(gases.begin() + i, createGas()); - } - - // Note: we don't replot the dive here, because when removing multiple - // points, these might trickle in one-by-one. Instead, the model will - // emit a data-changed signal. -} - -void ProfileWidget2::pointsRemoved(const QModelIndex &, int start, int end) -{ - // Qt's model/view API is mad. The end-point is inclusive, which means that the empty range is [0,-1]! - handles.erase(handles.begin() + start, handles.begin() + end + 1); - gases.erase(gases.begin() + start, gases.begin() + end + 1); - scene()->clearSelection(); - - // Note: we don't replot the dive here, because when removing multiple - // points, these might trickle in one-by-one. Instead, the model will - // emit a data-changed signal. -} - -void ProfileWidget2::pointsMoved(const QModelIndex &, int start, int end, const QModelIndex &, int row) -{ - move_in_range(handles, start, end + 1, row); - move_in_range(gases, start, end + 1, row); -} - -void ProfileWidget2::repositionDiveHandlers() -{ - hideAll(gases); - // Re-position the user generated dive handlers - for (int i = 0; i < plannerModel->rowCount(); i++) { - struct divedatapoint datapoint = plannerModel->at(i); - if (datapoint.time == 0) // those are the magic entries for tanks - continue; - DiveHandler *h = handles[i].get(); - h->setVisible(datapoint.entered); - h->setPos(profileScene->timeAxis->posAtValue(datapoint.time), profileScene->profileYAxis->posAtValue(datapoint.depth.mm)); - QPointF p1; - if (i == 0) { - if (prefs.drop_stone_mode) - // place the text on the straight line from the drop to stone position - p1 = QPointF(profileScene->timeAxis->posAtValue(datapoint.depth.mm / prefs.descrate), - profileScene->profileYAxis->posAtValue(datapoint.depth.mm)); - else - // place the text on the straight line from the origin to the first position - p1 = QPointF(profileScene->timeAxis->posAtValue(0), profileScene->profileYAxis->posAtValue(0)); - } else { - // place the text on the line from the last position - p1 = handles[i - 1]->pos(); - } - QPointF p2 = handles[i]->pos(); - QLineF line(p1, p2); - QPointF pos = line.pointAt(0.5); - gases[i]->setPos(pos); - if (datapoint.cylinderid >= 0 && datapoint.cylinderid < static_cast(d->cylinders.size())) - gases[i]->setText(get_gas_string(d->get_cylinder(datapoint.cylinderid)->gasmix)); - else - gases[i]->setText(QString()); - gases[i]->setVisible(datapoint.entered && - (i == 0 || gases[i]->text() != gases[i-1]->text())); - } -} - -void ProfileWidget2::divePlannerHandlerMoved() -{ - DiveHandler *activeHandler = qobject_cast(sender()); - int index = handleIndex(activeHandler); - - // Grow the time axis if necessary. - int minutes = lrint(profileScene->timeAxis->valueAt(activeHandler->pos()) / 60); - if (minutes * 60 > profileScene->timeAxis->maximum() * 0.9) - profileScene->timeAxis->setBounds(0.0, profileScene->timeAxis->maximum() * 1.02); - - divedatapoint data = plannerModel->at(index); - data.depth.mm = lrint(profileScene->profileYAxis->valueAt(activeHandler->pos()) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); - data.time = lrint(profileScene->timeAxis->valueAt(activeHandler->pos())); - - plannerModel->editStop(index, data); -} - -std::vector ProfileWidget2::selectedDiveHandleIndices() const -{ - std::vector res; - res.reserve(scene()->selectedItems().size()); - for (QGraphicsItem *item: scene()->selectedItems()) { - if (DiveHandler *handler = qgraphicsitem_cast(item)) - res.push_back(handleIndex(handler)); - } - return res; -} - -void ProfileWidget2::keyDownAction() -{ - if ((currentState != EDIT && currentState != PLAN) || !plannerModel) - return; - - std::vector handleIndices = selectedDiveHandleIndices(); - for (int row: handleIndices) { - divedatapoint dp = plannerModel->at(row); - - dp.depth.mm += M_OR_FT(1, 5); - plannerModel->editStop(row, dp); - } - if (currentState == EDIT && !handleIndices.empty()) - emit stopMoved(handleIndices.size()); // TODO: Accumulate key moves -} - -void ProfileWidget2::keyUpAction() -{ - if ((currentState != EDIT && currentState != PLAN) || !plannerModel) - return; - - std::vector handleIndices = selectedDiveHandleIndices(); - for (int row: handleIndices) { - divedatapoint dp = plannerModel->at(row); - - if (dp.depth.mm <= 0) - continue; - - dp.depth.mm -= M_OR_FT(1, 5); - plannerModel->editStop(row, dp); - } - if (currentState == EDIT && !handleIndices.empty()) - emit stopMoved(handleIndices.size()); // TODO: Accumulate key moves -} - -void ProfileWidget2::keyLeftAction() -{ - if ((currentState != EDIT && currentState != PLAN) || !plannerModel) - return; - - std::vector handleIndices = selectedDiveHandleIndices(); - for (int row: handleIndices) { - divedatapoint dp = plannerModel->at(row); - - if (dp.time / 60 <= 0) - continue; - - dp.time -= 60; - plannerModel->editStop(row, dp); - } - if (currentState == EDIT && !handleIndices.empty()) - emit stopMoved(handleIndices.size()); // TODO: Accumulate key moves -} - -void ProfileWidget2::keyRightAction() -{ - if ((currentState != EDIT && currentState != PLAN) || !plannerModel) - return; - - std::vector handleIndices = selectedDiveHandleIndices(); - for (int row: handleIndices) { - divedatapoint dp = plannerModel->at(row); - - dp.time += 60; - plannerModel->editStop(row, dp); - } - if (currentState == EDIT && !handleIndices.empty()) - emit stopMoved(handleIndices.size()); // TODO: Accumulate key moves -} - -void ProfileWidget2::keyDeleteAction() -{ - if ((currentState != EDIT && currentState != PLAN) || !plannerModel) - return; - - std::vector handleIndices = selectedDiveHandleIndices(); - // For now, we have to convert to QVector. - for (int index: handleIndices) - handles[index]->hide(); - if (!handleIndices.empty()) { - plannerModel->removeSelectedPoints(handleIndices); - if (currentState == EDIT) - emit stopRemoved(handleIndices.size()); - } -} - void ProfileWidget2::profileChanged(dive *dive) { if (dive != d) diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index 574e24436..4ba902029 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -73,10 +73,6 @@ slots: // Necessary to call from QAction's signals. void actionRequestedReplot(bool triggered); void divesChanged(const QVector &dives, DiveField field); #ifndef SUBSURFACE_MOBILE - void pointsReset(); - void pointInserted(const QModelIndex &parent, int start, int end); - void pointsRemoved(const QModelIndex &, int start, int end); - void pointsMoved(const QModelIndex &, int start, int end, const QModelIndex &destination, int row); void updateThumbnail(QString filename, QImage thumbnail, duration_t duration); void profileChanged(dive *d); @@ -141,25 +137,8 @@ private: RulerItem2 *rulerItem; #endif - std::vector> gases; - #ifndef SUBSURFACE_MOBILE - void keyDeleteAction(); - void keyUpAction(); - void keyDownAction(); - void keyLeftAction(); - void keyRightAction(); -#endif - - std::vector> handles; - int handleIndex(const DiveHandler *h) const; - void disconnectPlannerModel(); -#ifndef SUBSURFACE_MOBILE - void connectPlannerModel(); void repositionDiveHandlers(); - int fixHandlerIndex(DiveHandler *activeHandler); - DiveHandler *createHandle(); - QGraphicsSimpleTextItem *createGas(); #endif friend class DiveHandler; bool shouldCalculateMax; // Calculate maximum time and depth (default). False when dragging handles. diff --git a/profile-widget/ruleritem.cpp b/profile-widget/ruleritem.cpp index c4757c638..35eb76f04 100644 --- a/profile-widget/ruleritem.cpp +++ b/profile-widget/ruleritem.cpp @@ -85,6 +85,10 @@ RulerItem::RulerItem(ProfileView &view, double dpr) : { } +RulerItem::~RulerItem() +{ +} + void RulerItem::setVisible(bool visible) { line->setVisible(visible); diff --git a/profile-widget/ruleritem.h b/profile-widget/ruleritem.h index 2b4e7b112..62184a2c6 100644 --- a/profile-widget/ruleritem.h +++ b/profile-widget/ruleritem.h @@ -24,6 +24,7 @@ class RulerItem { QPixmap title; public: RulerItem(ProfileView &view, double dpr); + ~RulerItem(); void setVisible(bool visible); void update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed); void anim(double progress); diff --git a/profile-widget/zvalues.h b/profile-widget/zvalues.h index e499aa0d3..6489fe428 100644 --- a/profile-widget/zvalues.h +++ b/profile-widget/zvalues.h @@ -14,6 +14,8 @@ struct ProfileZValue { Profile = 0, Pictures, RulerItem, + Handles, + MouseFollower, ToolTipItem, Count }; diff --git a/qt-quick/chartitem.cpp b/qt-quick/chartitem.cpp index d90dcec77..faaa65c1a 100644 --- a/qt-quick/chartitem.cpp +++ b/qt-quick/chartitem.cpp @@ -37,6 +37,10 @@ QRectF ChartItem::getRect() const return rect; } +void ChartItem::startDrag(QPointF pos) +{ +} + void ChartItem::drag(QPointF) { } diff --git a/qt-quick/chartitem.h b/qt-quick/chartitem.h index 890da9220..6c5d2d9d8 100644 --- a/qt-quick/chartitem.h +++ b/qt-quick/chartitem.h @@ -30,9 +30,11 @@ public: const bool dragable; // Item can be dragged with the mouse. Must be set in constructor. virtual ~ChartItem(); // Attention: must only be called by render thread. QRectF getRect() const; + virtual void startDrag(QPointF pos); // Called when user clicks on a draggable item virtual void drag(QPointF pos); // Called when dragging the item virtual void stopDrag(QPointF pos); // Called when dragging the item finished protected: + template friend class ChartItemPtr; ChartItem(ChartView &v, size_t z, bool dragable = false); QSizeF sceneSize() const; ChartView &view; diff --git a/qt-quick/chartitem_ptr.h b/qt-quick/chartitem_ptr.h index 6d59d59f9..51797d110 100644 --- a/qt-quick/chartitem_ptr.h +++ b/qt-quick/chartitem_ptr.h @@ -49,6 +49,12 @@ public: { return ptr; } + void del() + { + if (!ptr) + return; + ptr->view.deleteChartItem(*this); + } }; #endif diff --git a/qt-quick/chartview.cpp b/qt-quick/chartview.cpp index 71db5d4f0..aada86f96 100644 --- a/qt-quick/chartview.cpp +++ b/qt-quick/chartview.cpp @@ -315,6 +315,7 @@ void ChartView::mousePressEvent(QMouseEvent *event) dragStartMouse = pos; dragStartItem = rect.topLeft(); draggedItem = item; + draggedItem->startDrag(pos); grabMouse(); setKeepMouseGrab(true); // don't allow Qt to steal the grab event->accept();