mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
profile: port basic dive editing to QtQuick
This needed a bit of refactoring of the ChartItem code, because we have to be signaled on drag start. Currently only one handle can be selected at a time. This was (implicitly) the case anyway, as far as I can tell. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
ea0085fef6
commit
bece0a0652
21 changed files with 597 additions and 477 deletions
|
@ -182,6 +182,7 @@ SOURCES += subsurface-mobile-main.cpp \
|
||||||
profile-widget/tooltipitem.cpp \
|
profile-widget/tooltipitem.cpp \
|
||||||
profile-widget/divelineitem.cpp \
|
profile-widget/divelineitem.cpp \
|
||||||
profile-widget/divetextitem.cpp \
|
profile-widget/divetextitem.cpp \
|
||||||
|
profile-widget/handleitem.cpp \
|
||||||
profile-widget/profileview.cpp \
|
profile-widget/profileview.cpp \
|
||||||
profile-widget/ruleritem.cpp
|
profile-widget/ruleritem.cpp
|
||||||
|
|
||||||
|
@ -346,6 +347,7 @@ HEADERS += \
|
||||||
profile-widget/divelineitem.h \
|
profile-widget/divelineitem.h \
|
||||||
profile-widget/divepixmapcache.h \
|
profile-widget/divepixmapcache.h \
|
||||||
profile-widget/divetextitem.h \
|
profile-widget/divetextitem.h \
|
||||||
|
profile-widget/handleitem.h \
|
||||||
profile-widget/profileview.h \
|
profile-widget/profileview.h \
|
||||||
profile-widget/ruleritem.h \
|
profile-widget/ruleritem.h \
|
||||||
profile-widget/profiletranslations.h
|
profile-widget/profiletranslations.h
|
||||||
|
|
|
@ -15,6 +15,15 @@ struct gasmix {
|
||||||
fraction_t o2;
|
fraction_t o2;
|
||||||
fraction_t he;
|
fraction_t he;
|
||||||
std::string name() const;
|
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_invalid = { { .permille = -1 }, { .permille = -1 } };
|
||||||
static const struct gasmix gasmix_air = { 0_percent, 0_percent };
|
static const struct gasmix gasmix_air = { 0_percent, 0_percent };
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QQuickWidget>
|
#include <QQuickWidget>
|
||||||
|
#include <QSignalBlocker>
|
||||||
#include <QStackedWidget>
|
#include <QStackedWidget>
|
||||||
#include <QToolBar>
|
#include <QToolBar>
|
||||||
|
|
||||||
|
@ -201,15 +202,6 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), placingCommand(false)
|
||||||
ui.profTissues->setChecked(qPrefTechnicalDetails::percentagegraph());
|
ui.profTissues->setChecked(qPrefTechnicalDetails::percentagegraph());
|
||||||
ui.profScaled->setChecked(qPrefTechnicalDetails::zoomed_plot());
|
ui.profScaled->setChecked(qPrefTechnicalDetails::zoomed_plot());
|
||||||
ui.profInfobox->setChecked(qPrefTechnicalDetails::infobox());
|
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()
|
ProfileWidget::~ProfileWidget()
|
||||||
|
@ -229,6 +221,16 @@ ProfileView *ProfileWidget::getView()
|
||||||
viewWidget->engine()->setObjectOwnership(view, QQmlEngine::CppOwnership);
|
viewWidget->engine()->setObjectOwnership(view, QQmlEngine::CppOwnership);
|
||||||
view->setParent(this);
|
view->setParent(this);
|
||||||
view->setVisible(isVisible()); // Synchronize visibility of widget and QtQuick-view.
|
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;
|
return view;
|
||||||
}
|
}
|
||||||
|
@ -303,7 +305,7 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn)
|
||||||
auto view = getView();
|
auto view = getView();
|
||||||
setEnabledToolbar(d != nullptr);
|
setEnabledToolbar(d != nullptr);
|
||||||
if (editedDive) {
|
if (editedDive) {
|
||||||
view->plotDive(editedDive.get(), dc);
|
view->plotDive(editedDive.get(), dc, ProfileView::RenderFlags::EditMode);
|
||||||
setDive(editedDive.get(), dc);
|
setDive(editedDive.get(), dc);
|
||||||
} else if (d) {
|
} else if (d) {
|
||||||
//view->setProfileState(d, dc);
|
//view->setProfileState(d, dc);
|
||||||
|
@ -404,9 +406,14 @@ void ProfileWidget::editDive()
|
||||||
{
|
{
|
||||||
editedDive = std::make_unique<dive>();
|
editedDive = std::make_unique<dive>();
|
||||||
copy_dive(d, editedDive.get()); // Work on a copy of the dive
|
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()->setPlanMode(DivePlannerPointsModel::EDIT);
|
||||||
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
|
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
|
||||||
//view->setEditState(editedDive.get(), dc);
|
//view->setEditState(editedDive.get(), editedDc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileWidget::exitEditMode()
|
void ProfileWidget::exitEditMode()
|
||||||
|
|
|
@ -16,6 +16,8 @@ set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||||
diveprofileitem.h
|
diveprofileitem.h
|
||||||
divetextitem.cpp
|
divetextitem.cpp
|
||||||
divetextitem.h
|
divetextitem.h
|
||||||
|
handleitem.cpp
|
||||||
|
handleitem.h
|
||||||
pictureitem.h
|
pictureitem.h
|
||||||
pictureitem.cpp
|
pictureitem.cpp
|
||||||
profilescene.cpp
|
profilescene.cpp
|
||||||
|
@ -37,8 +39,6 @@ set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||||
else ()
|
else ()
|
||||||
set(SUBSURFACE_PROFILE_LIB_SRCS
|
set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||||
${SUBSURFACE_PROFILE_LIB_SRCS}
|
${SUBSURFACE_PROFILE_LIB_SRCS}
|
||||||
divehandler.cpp
|
|
||||||
divehandler.h
|
|
||||||
)
|
)
|
||||||
endif ()
|
endif ()
|
||||||
source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS})
|
source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS})
|
||||||
|
|
|
@ -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 <QMenu>
|
|
||||||
#include <QGraphicsSceneMouseEvent>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
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<ProfileWidget2 *>(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<std::pair<int, QString>> 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<ProfileWidget2 *>(scene()->views().first());
|
|
||||||
//view->keyDeleteAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiveHandler::changeGas()
|
|
||||||
{
|
|
||||||
//ProfileWidget2 *view = qobject_cast<ProfileWidget2 *>(scene()->views().first());
|
|
||||||
//QAction *action = qobject_cast<QAction *>(sender());
|
|
||||||
|
|
||||||
//view->changeGas(parentIndex(), action->data().toInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiveHandler::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|
||||||
{
|
|
||||||
if (t.elapsed() < 40)
|
|
||||||
return;
|
|
||||||
t.start();
|
|
||||||
|
|
||||||
//ProfileWidget2 *view = qobject_cast<ProfileWidget2*>(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();
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0
|
|
||||||
#ifndef DIVEHANDLER_HPP
|
|
||||||
#define DIVEHANDLER_HPP
|
|
||||||
|
|
||||||
#include <QGraphicsPathItem>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
|
|
||||||
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
|
|
153
profile-widget/handleitem.cpp
Normal file
153
profile-widget/handleitem.cpp
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
#include "handleitem.h"
|
||||||
|
#include "profileview.h"
|
||||||
|
#include "zvalues.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
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<HandleItemHandle>(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<double>(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<ChartTextItem>(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<QAction *>(sender());
|
||||||
|
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
|
||||||
|
QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS);
|
||||||
|
plannerModel->gasChange(index.sibling(index.row() + 1, index.column()), action->data().toInt());
|
||||||
|
}
|
||||||
|
*/
|
38
profile-widget/handleitem.h
Normal file
38
profile-widget/handleitem.h
Normal file
|
@ -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<HandleItemHandle> handle;
|
||||||
|
ChartItemPtr<ChartTextItem> 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
|
|
@ -612,6 +612,11 @@ int ProfileScene::timeAt(QPointF pos) const
|
||||||
return lrint(timeAxis->valueAt(pos));
|
return lrint(timeAxis->valueAt(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ProfileScene::depthAt(QPointF pos) const
|
||||||
|
{
|
||||||
|
return lrint(profileYAxis->valueAt(pos));
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<double, double> ProfileScene::minMaxTime() const
|
std::pair<double, double> ProfileScene::minMaxTime() const
|
||||||
{
|
{
|
||||||
return { timeAxis->minimum(), timeAxis->maximum() };
|
return { timeAxis->minimum(), timeAxis->maximum() };
|
||||||
|
|
|
@ -51,6 +51,7 @@ public:
|
||||||
double calcZoomPosition(double zoom, double originalPos, double delta);
|
double calcZoomPosition(double zoom, double originalPos, double delta);
|
||||||
const plot_info &getPlotInfo() const;
|
const plot_info &getPlotInfo() const;
|
||||||
int timeAt(QPointF pos) const; // time in seconds
|
int timeAt(QPointF pos) const; // time in seconds
|
||||||
|
int depthAt(QPointF pos) const; // depth in mm
|
||||||
std::pair<double, double> minMaxTime() const; // time in seconds
|
std::pair<double, double> minMaxTime() const; // time in seconds
|
||||||
std::pair<double, double> minMaxX() const; // minimum and maximum x positions of canvas
|
std::pair<double, double> minMaxX() const; // minimum and maximum x positions of canvas
|
||||||
std::pair<double, double> minMaxY() const; // minimum and maximum y positions of canvas
|
std::pair<double, double> minMaxY() const; // minimum and maximum y positions of canvas
|
||||||
|
@ -61,6 +62,7 @@ public:
|
||||||
|
|
||||||
const struct dive *d;
|
const struct dive *d;
|
||||||
int dc;
|
int dc;
|
||||||
|
QRectF profileRegion; // Region inside the axes, where the crosshair is painted in plan and edit mode.
|
||||||
private:
|
private:
|
||||||
using DataAccessor = double (*)(const plot_data &data);
|
using DataAccessor = double (*)(const plot_data &data);
|
||||||
template<typename T, class... Args> T *createItem(const DiveCartesianAxis &vAxis, DataAccessor accessor, int z, Args&&... args);
|
template<typename T, class... Args> T *createItem(const DiveCartesianAxis &vAxis, DataAccessor accessor, int z, Args&&... args);
|
||||||
|
@ -78,7 +80,6 @@ private:
|
||||||
int maxdepth;
|
int maxdepth;
|
||||||
|
|
||||||
struct plot_info plotInfo;
|
struct plot_info plotInfo;
|
||||||
QRectF profileRegion; // Region inside the axes, where the crosshair is painted in plan and edit mode.
|
|
||||||
DiveCartesianAxis *profileYAxis;
|
DiveCartesianAxis *profileYAxis;
|
||||||
DiveCartesianAxis *gasYAxis;
|
DiveCartesianAxis *gasYAxis;
|
||||||
DiveCartesianAxis *temperatureAxis;
|
DiveCartesianAxis *temperatureAxis;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "profileview.h"
|
#include "profileview.h"
|
||||||
#include "pictureitem.h"
|
#include "pictureitem.h"
|
||||||
#include "profilescene.h"
|
#include "profilescene.h"
|
||||||
|
#include "handleitem.h"
|
||||||
#include "ruleritem.h"
|
#include "ruleritem.h"
|
||||||
#include "tooltipitem.h"
|
#include "tooltipitem.h"
|
||||||
#include "zvalues.h"
|
#include "zvalues.h"
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
#include "core/settings/qPrefPartialPressureGas.h"
|
#include "core/settings/qPrefPartialPressureGas.h"
|
||||||
#include "core/settings/qPrefTechnicalDetails.h"
|
#include "core/settings/qPrefTechnicalDetails.h"
|
||||||
#include "core/subsurface-qt/divelistnotifier.h"
|
#include "core/subsurface-qt/divelistnotifier.h"
|
||||||
|
#include "qt-models/diveplannermodel.h"
|
||||||
#include "qt-quick/chartitem.h"
|
#include "qt-quick/chartitem.h"
|
||||||
|
|
||||||
#include <QAbstractAnimation>
|
#include <QAbstractAnimation>
|
||||||
|
@ -25,6 +27,8 @@
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
|
static const QColor mouseFollowerColor = QColor(Qt::red).lighter();
|
||||||
|
|
||||||
// Class templates for animations (if any). Might want to do our own.
|
// Class templates for animations (if any). Might want to do our own.
|
||||||
// Calls the function object passed in the constructor with a time argument,
|
// Calls the function object passed in the constructor with a time argument,
|
||||||
// where 0.0 = start at 1.0 = end.
|
// where 0.0 = start at 1.0 = end.
|
||||||
|
@ -73,16 +77,20 @@ std::unique_ptr<ProfileAnimationTemplate<FUNC>> make_anim(FUNC func, int animSpe
|
||||||
: std::unique_ptr<ProfileAnimationTemplate<FUNC>>();
|
: std::unique_ptr<ProfileAnimationTemplate<FUNC>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count),
|
ProfileView::ProfileView(QQuickItem *parent) :
|
||||||
|
ChartView(parent, ProfileZValue::Count),
|
||||||
|
initialized(false),
|
||||||
d(nullptr),
|
d(nullptr),
|
||||||
dc(0),
|
dc(0),
|
||||||
|
plannerModel(nullptr),
|
||||||
|
mode(Mode::Normal),
|
||||||
simplified(false),
|
simplified(false),
|
||||||
dpr(1.0),
|
dpr(1.0),
|
||||||
zoomLevel(1.00),
|
zoomLevel(1.00),
|
||||||
zoomedPosition(0.0),
|
zoomedPosition(0.0),
|
||||||
panning(false),
|
panning(false),
|
||||||
empty(true),
|
empty(true),
|
||||||
shouldCalculateMax(true),
|
selectedHandleIdx(-1),
|
||||||
highlightedPicture(nullptr)
|
highlightedPicture(nullptr)
|
||||||
{
|
{
|
||||||
setBackgroundColor(Qt::black);
|
setBackgroundColor(Qt::black);
|
||||||
|
@ -129,6 +137,7 @@ ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::
|
||||||
|
|
||||||
setAcceptTouchEvents(true);
|
setAcceptTouchEvents(true);
|
||||||
setAcceptHoverEvents(true);
|
setAcceptHoverEvents(true);
|
||||||
|
setFocus(true); // Necessary to get keyPress events
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileView::ProfileView() : ProfileView(nullptr)
|
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()
|
void ProfileView::resetPointers()
|
||||||
{
|
{
|
||||||
profileItem.reset();
|
profileItem.reset();
|
||||||
tooltip.reset();
|
tooltip.reset();
|
||||||
ruler.reset();
|
ruler.reset();
|
||||||
|
mouseFollowerHorizontal.reset();
|
||||||
|
mouseFollowerVertical.reset();
|
||||||
pictures.clear();
|
pictures.clear();
|
||||||
|
handles.clear();
|
||||||
highlightedPicture = nullptr;
|
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)
|
void ProfileView::plotAreaChanged(const QSizeF &s)
|
||||||
{
|
{
|
||||||
int flags = simplified ? RenderFlags::Simplified : RenderFlags::None;
|
|
||||||
if (!empty)
|
if (!empty)
|
||||||
plotDive(d, dc, flags | RenderFlags::Instant);
|
plotDive(d, dc, rerenderFlags() | RenderFlags::Instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileView::replot()
|
void ProfileView::replot()
|
||||||
{
|
{
|
||||||
int flags = simplified ? RenderFlags::Simplified : RenderFlags::None;
|
|
||||||
if (!empty)
|
if (!empty)
|
||||||
plotDive(d, dc, flags);
|
plotDive(d, dc, rerenderFlags());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileView::clear()
|
void ProfileView::clear()
|
||||||
|
@ -168,12 +204,15 @@ void ProfileView::clear()
|
||||||
//disconnectPlannerConnections();
|
//disconnectPlannerConnections();
|
||||||
if (profileScene)
|
if (profileScene)
|
||||||
profileScene->clear();
|
profileScene->clear();
|
||||||
//handles.clear();
|
clearHandles();
|
||||||
//gases.clear();
|
|
||||||
if (tooltip)
|
if (tooltip)
|
||||||
tooltip->setVisible(false);
|
tooltip->setVisible(false);
|
||||||
if (ruler)
|
if (ruler)
|
||||||
ruler->setVisible(false);
|
ruler->setVisible(false);
|
||||||
|
if (mouseFollowerHorizontal)
|
||||||
|
mouseFollowerHorizontal->setVisible(false);
|
||||||
|
if (mouseFollowerVertical)
|
||||||
|
mouseFollowerVertical->setVisible(false);
|
||||||
empty = true;
|
empty = true;
|
||||||
d = nullptr;
|
d = nullptr;
|
||||||
dc = 0;
|
dc = 0;
|
||||||
|
@ -181,23 +220,29 @@ void ProfileView::clear()
|
||||||
|
|
||||||
void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags)
|
void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags)
|
||||||
{
|
{
|
||||||
d = dIn;
|
bool diveChanged = std::exchange(d, dIn) != d;
|
||||||
dc = dcIn;
|
diveChanged |= std::exchange(dc, dcIn) != dc;
|
||||||
simplified = flags & RenderFlags::Simplified;
|
simplified = flags & RenderFlags::Simplified;
|
||||||
if (!d) {
|
if (!d) {
|
||||||
clear();
|
clear();
|
||||||
return;
|
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!
|
// We can't create the scene in the constructor, because we can't get the DPR property there. Oh joy!
|
||||||
if (!profileScene) {
|
if (!profileScene) {
|
||||||
dpr = std::clamp(property("dpr").toReal(), 0.5, 100.0);
|
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
|
||||||
if (empty)
|
if (std::exchange(empty, false))
|
||||||
flags |= RenderFlags::Instant;
|
flags |= RenderFlags::Instant;
|
||||||
empty = false;
|
|
||||||
|
|
||||||
// If Qt decided to destroy our canvas, recreate it
|
// If Qt decided to destroy our canvas, recreate it
|
||||||
if (!profileItem)
|
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
|
QElapsedTimer measureDuration; // let's measure how long this takes us (maybe we'll turn of TTL calculation later
|
||||||
measureDuration.start();
|
measureDuration.start();
|
||||||
|
|
||||||
//DivePlannerPointsModel *model = currentState == EDIT || currentState == PLAN ? plannerModel : nullptr;
|
DivePlannerPointsModel *model = flags & (RenderFlags::EditMode | RenderFlags::PlanMode) ? plannerModel : nullptr;
|
||||||
DivePlannerPointsModel *model = nullptr;
|
|
||||||
bool inPlanner = flags & RenderFlags::PlanMode;
|
bool inPlanner = flags & RenderFlags::PlanMode;
|
||||||
|
|
||||||
int animSpeed = flags & RenderFlags::Instant ? 0 : qPrefDisplay::animation_speed();
|
int animSpeed = flags & RenderFlags::Instant ? 0 : qPrefDisplay::animation_speed();
|
||||||
|
bool calculateMax = !(flags & RenderFlags::DontCalculateMax);
|
||||||
|
|
||||||
profileScene->resize(size());
|
profileScene->resize(size());
|
||||||
profileScene->plotDive(d, dc, animSpeed, simplified, model, inPlanner,
|
profileScene->plotDive(d, dc, animSpeed, simplified, model, inPlanner,
|
||||||
flags & RenderFlags::DontRecalculatePlotInfo,
|
flags & RenderFlags::DontRecalculatePlotInfo,
|
||||||
shouldCalculateMax, zoomLevel, zoomedPosition);
|
calculateMax, 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);
|
||||||
|
|
||||||
//if ((currentState == EDIT || currentState == PLAN) && plannerModel) {
|
if ((mode == Mode::Edit || mode == Mode::Plan) && plannerModel) {
|
||||||
//repositionDiveHandlers();
|
if (diveChanged)
|
||||||
//plannerModel->deleteTemporaryPlan();
|
resetHandles();
|
||||||
//}
|
placeHandles();
|
||||||
|
plannerModel->deleteTemporaryPlan();
|
||||||
|
} else {
|
||||||
|
clearHandles();
|
||||||
|
}
|
||||||
|
|
||||||
// On zoom / pan don't recreate the picture thumbnails, only change their position.
|
// On zoom / pan don't recreate the picture thumbnails, only change their position.
|
||||||
if (!inPlanner) {
|
if (!inPlanner) {
|
||||||
|
@ -267,6 +316,15 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags)
|
||||||
ruler->setVisible(false);
|
ruler->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mouseFollowerHorizontal)
|
||||||
|
mouseFollowerHorizontal = createChartItem<ChartLineItem>(ProfileZValue::MouseFollower,
|
||||||
|
mouseFollowerColor, 1.0 * dpr);
|
||||||
|
if (!mouseFollowerVertical)
|
||||||
|
mouseFollowerVertical = createChartItem<ChartLineItem>(ProfileZValue::MouseFollower,
|
||||||
|
mouseFollowerColor, 1.0 * dpr);
|
||||||
|
mouseFollowerHorizontal->setVisible(mode == Mode::Edit || mode == Mode::Plan);
|
||||||
|
mouseFollowerVertical->setVisible(mode == Mode::Edit || mode == Mode::Plan);
|
||||||
|
|
||||||
// Reset animation.
|
// Reset animation.
|
||||||
animation = make_anim([this](double progress) { anim(progress); }, animSpeed);
|
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);
|
level = std::clamp(level, 1.0, 20.0);
|
||||||
double old = std::exchange(zoomLevel, level);
|
double old = std::exchange(zoomLevel, level);
|
||||||
int flags = simplified ? RenderFlags::Simplified : RenderFlags::None;
|
|
||||||
if (level != old)
|
if (level != old)
|
||||||
plotDive(d, dc, flags | RenderFlags::DontRecalculatePlotInfo);
|
plotDive(d, dc, rerenderFlags() | RenderFlags::DontRecalculatePlotInfo);
|
||||||
emit zoomLevelChanged();
|
emit zoomLevelChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,10 +418,8 @@ void ProfileView::mouseReleaseEvent(QMouseEvent *event)
|
||||||
panning = false;
|
panning = false;
|
||||||
unsetCursor();
|
unsetCursor();
|
||||||
}
|
}
|
||||||
//if (currentState == PLAN || currentState == EDIT) {
|
if (mode == Mode::Plan || mode == Mode::Edit)
|
||||||
// shouldCalculateMax = true;
|
replot();
|
||||||
// replot();
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileView::mouseMoveEvent(QMouseEvent *event)
|
void ProfileView::mouseMoveEvent(QMouseEvent *event)
|
||||||
|
@ -375,14 +430,7 @@ void ProfileView::mouseMoveEvent(QMouseEvent *event)
|
||||||
if (panning)
|
if (panning)
|
||||||
pan(pos.x(), pos.y());
|
pan(pos.x(), pos.y());
|
||||||
|
|
||||||
//if (currentState == PLAN || currentState == EDIT) {
|
updateMouseFollowers(pos);
|
||||||
//QRectF rect = profileScene->profileRegion;
|
|
||||||
//auto [miny, maxy] = profileScene->profileYAxis->screenMinMax();
|
|
||||||
//double x = std::clamp(pos.x(), rect.left(), rect.right());
|
|
||||||
//double y = std::clamp(pos.y(), miny, maxy);
|
|
||||||
//mouseFollowerHorizontal->setLine(rect.left(), y, rect.right(), y);
|
|
||||||
//mouseFollowerVertical->setLine(x, rect.top(), x, rect.bottom());
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int ProfileView::getDiveId() const
|
int ProfileView::getDiveId() const
|
||||||
|
@ -444,9 +492,8 @@ void ProfileView::pan(double x, double y)
|
||||||
zoomedPosition = profileScene->calcZoomPosition(zoomLevel,
|
zoomedPosition = profileScene->calcZoomPosition(zoomLevel,
|
||||||
panningOriginalProfilePosition,
|
panningOriginalProfilePosition,
|
||||||
panningOriginalMousePosition - x);
|
panningOriginalMousePosition - x);
|
||||||
int flags = simplified ? RenderFlags::Simplified : RenderFlags::None;
|
|
||||||
if (oldPos != zoomedPosition)
|
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 *)
|
void ProfileView::hoverEnterEvent(QHoverEvent *)
|
||||||
|
@ -533,6 +580,211 @@ void ProfileView::hoverMoveEvent(QHoverEvent *event)
|
||||||
highlightedPicture = nullptr;
|
highlightedPicture = nullptr;
|
||||||
update();
|
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<int>(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<int> 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<HandleItem>(*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()
|
void ProfileView::unhighlightPicture()
|
||||||
|
@ -704,6 +956,25 @@ void ProfileView::clearPictures()
|
||||||
highlightedPicture = nullptr;
|
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<HandleItem>(*this, dpr, i));
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to compare offset_ts.
|
// Helper function to compare offset_ts.
|
||||||
static bool operator<(offset_t o1, offset_t o2)
|
static bool operator<(offset_t o1, offset_t o2)
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,13 +7,17 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class ChartGraphicsSceneItem;
|
class ChartGraphicsSceneItem;
|
||||||
|
class ChartLineItem;
|
||||||
class ChartRectItem;
|
class ChartRectItem;
|
||||||
|
class DivePlannerPointsModel;
|
||||||
|
class HandleItem;
|
||||||
class PictureItem;
|
class PictureItem;
|
||||||
class ProfileAnimation;
|
class ProfileAnimation;
|
||||||
class ProfileScene;
|
class ProfileScene;
|
||||||
class ToolTipItem;
|
class ToolTipItem;
|
||||||
struct picture;
|
struct picture;
|
||||||
class RulerItem;
|
class RulerItem;
|
||||||
|
class QModelIndex;
|
||||||
|
|
||||||
class ProfileView : public ChartView {
|
class ProfileView : public ChartView {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -27,6 +31,10 @@ public:
|
||||||
ProfileView(QQuickItem *parent);
|
ProfileView(QQuickItem *parent);
|
||||||
~ProfileView();
|
~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 {
|
struct RenderFlags {
|
||||||
static constexpr int None = 0;
|
static constexpr int None = 0;
|
||||||
static constexpr int Instant = 1 << 0;
|
static constexpr int Instant = 1 << 0;
|
||||||
|
@ -34,6 +42,7 @@ public:
|
||||||
static constexpr int EditMode = 1 << 2;
|
static constexpr int EditMode = 1 << 2;
|
||||||
static constexpr int PlanMode = 1 << 3;
|
static constexpr int PlanMode = 1 << 3;
|
||||||
static constexpr int Simplified = 1 << 4; // For mobile's overview page
|
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);
|
void plotDive(const struct dive *d, int dc, int flags = RenderFlags::None);
|
||||||
|
@ -41,7 +50,10 @@ public:
|
||||||
void clear();
|
void clear();
|
||||||
void resetZoom();
|
void resetZoom();
|
||||||
void anim(double fraction);
|
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
|
// For mobile
|
||||||
Q_INVOKABLE void pinchStart();
|
Q_INVOKABLE void pinchStart();
|
||||||
|
@ -50,12 +62,23 @@ public:
|
||||||
Q_INVOKABLE void prevDC();
|
Q_INVOKABLE void prevDC();
|
||||||
Q_INVOKABLE void panStart(double x, double y);
|
Q_INVOKABLE void panStart(double x, double y);
|
||||||
Q_INVOKABLE void pan(double x, double y);
|
Q_INVOKABLE void pan(double x, double y);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void numDCChanged();
|
void numDCChanged();
|
||||||
void zoomLevelChanged();
|
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:
|
private:
|
||||||
|
enum Mode {
|
||||||
|
Normal,
|
||||||
|
Edit,
|
||||||
|
Plan
|
||||||
|
};
|
||||||
const struct dive *d;
|
const struct dive *d;
|
||||||
int dc;
|
int dc;
|
||||||
|
DivePlannerPointsModel *plannerModel;
|
||||||
|
Mode mode;
|
||||||
bool simplified;
|
bool simplified;
|
||||||
double dpr;
|
double dpr;
|
||||||
double zoomLevel, zoomLevelPinchStart;
|
double zoomLevel, zoomLevelPinchStart;
|
||||||
|
@ -64,15 +87,16 @@ private:
|
||||||
double panningOriginalMousePosition;
|
double panningOriginalMousePosition;
|
||||||
double panningOriginalProfilePosition;
|
double panningOriginalProfilePosition;
|
||||||
bool empty; // No dive shown.
|
bool empty; // No dive shown.
|
||||||
bool shouldCalculateMax; // Calculate maximum time and depth (default). False when dragging handles.
|
|
||||||
QColor background;
|
QColor background;
|
||||||
std::unique_ptr<ProfileScene> profileScene;
|
std::unique_ptr<ProfileScene> profileScene;
|
||||||
ChartItemPtr<ChartGraphicsSceneItem> profileItem;
|
ChartItemPtr<ChartGraphicsSceneItem> profileItem;
|
||||||
std::unique_ptr<ProfileAnimation> animation;
|
std::unique_ptr<ProfileAnimation> animation;
|
||||||
|
ChartItemPtr<ChartLineItem> mouseFollowerHorizontal, mouseFollowerVertical;
|
||||||
|
|
||||||
void plotAreaChanged(const QSizeF &size) override;
|
void plotAreaChanged(const QSizeF &size) override;
|
||||||
void resetPointers() override;
|
void resetPointers() override;
|
||||||
void replot();
|
void replot();
|
||||||
|
int rerenderFlags() const;
|
||||||
void setZoom(double level);
|
void setZoom(double level);
|
||||||
|
|
||||||
void hoverEnterEvent(QHoverEvent *event) override;
|
void hoverEnterEvent(QHoverEvent *event) override;
|
||||||
|
@ -81,6 +105,8 @@ private:
|
||||||
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;
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
|
|
||||||
ChartItemPtr<ToolTipItem> tooltip;
|
ChartItemPtr<ToolTipItem> tooltip;
|
||||||
void updateTooltip(QPointF pos, bool plannerMode, int animSpeed);
|
void updateTooltip(QPointF pos, bool plannerMode, int animSpeed);
|
||||||
|
@ -90,7 +116,21 @@ private:
|
||||||
void updateRuler(int animSpeed);
|
void updateRuler(int animSpeed);
|
||||||
std::unique_ptr<ProfileAnimation> ruler_animation;
|
std::unique_ptr<ProfileAnimation> ruler_animation;
|
||||||
|
|
||||||
|
void updateMouseFollowers(QPointF pos);
|
||||||
|
|
||||||
QPointF previousHoverMovePosition;
|
QPointF previousHoverMovePosition;
|
||||||
|
std::vector<std::unique_ptr<HandleItem>> 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.
|
// The list of pictures in this plot. The pictures are sorted by offset in seconds.
|
||||||
// For the same offset, sort by filename.
|
// For the same offset, sort by filename.
|
||||||
|
|
|
@ -236,37 +236,6 @@ void ProfileWidget2::resizeEvent(QResizeEvent *event)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef SUBSURFACE_MOBILE
|
#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)
|
bool ProfileWidget2::eventFilter(QObject *object, QEvent *event)
|
||||||
{
|
{
|
||||||
QGraphicsScene *s = qobject_cast<QGraphicsScene *>(object);
|
QGraphicsScene *s = qobject_cast<QGraphicsScene *>(object);
|
||||||
|
@ -298,8 +267,6 @@ void ProfileWidget2::setProfileState()
|
||||||
if (currentState == PROFILE)
|
if (currentState == PROFILE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
disconnectPlannerModel();
|
|
||||||
|
|
||||||
currentState = PROFILE;
|
currentState = PROFILE;
|
||||||
setBackgroundBrush(getColor(::BACKGROUND, profileScene->isGrayscale));
|
setBackgroundBrush(getColor(::BACKGROUND, profileScene->isGrayscale));
|
||||||
|
|
||||||
|
@ -325,11 +292,8 @@ void ProfileWidget2::setEditState(const dive *d, int dc)
|
||||||
mouseFollowerHorizontal->setVisible(true);
|
mouseFollowerHorizontal->setVisible(true);
|
||||||
mouseFollowerVertical->setVisible(true);
|
mouseFollowerVertical->setVisible(true);
|
||||||
|
|
||||||
connectPlannerModel();
|
|
||||||
|
|
||||||
currentState = EDIT;
|
currentState = EDIT;
|
||||||
|
|
||||||
pointsReset();
|
|
||||||
repositionDiveHandlers();
|
repositionDiveHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,12 +306,9 @@ void ProfileWidget2::setPlanState(const dive *d, int dc)
|
||||||
mouseFollowerHorizontal->setVisible(true);
|
mouseFollowerHorizontal->setVisible(true);
|
||||||
mouseFollowerVertical->setVisible(true);
|
mouseFollowerVertical->setVisible(true);
|
||||||
|
|
||||||
connectPlannerModel();
|
|
||||||
|
|
||||||
currentState = PLAN;
|
currentState = PLAN;
|
||||||
setBackgroundBrush(QColor("#D7E3EF"));
|
setBackgroundBrush(QColor("#D7E3EF"));
|
||||||
|
|
||||||
pointsReset();
|
|
||||||
repositionDiveHandlers();
|
repositionDiveHandlers();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -677,242 +638,6 @@ void ProfileWidget2::connectPlannerModel()
|
||||||
}
|
}
|
||||||
#endif
|
#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<DiveHandler> &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<int>(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<DiveHandler *>(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<int> ProfileWidget2::selectedDiveHandleIndices() const
|
|
||||||
{
|
|
||||||
std::vector<int> res;
|
|
||||||
res.reserve(scene()->selectedItems().size());
|
|
||||||
for (QGraphicsItem *item: scene()->selectedItems()) {
|
|
||||||
if (DiveHandler *handler = qgraphicsitem_cast<DiveHandler *>(item))
|
|
||||||
res.push_back(handleIndex(handler));
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProfileWidget2::keyDownAction()
|
|
||||||
{
|
|
||||||
if ((currentState != EDIT && currentState != PLAN) || !plannerModel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::vector<int> 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<int> 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<int> 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<int> 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<int> 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)
|
void ProfileWidget2::profileChanged(dive *dive)
|
||||||
{
|
{
|
||||||
if (dive != d)
|
if (dive != d)
|
||||||
|
|
|
@ -73,10 +73,6 @@ slots: // Necessary to call from QAction's signals.
|
||||||
void actionRequestedReplot(bool triggered);
|
void actionRequestedReplot(bool triggered);
|
||||||
void divesChanged(const QVector<dive *> &dives, DiveField field);
|
void divesChanged(const QVector<dive *> &dives, DiveField field);
|
||||||
#ifndef SUBSURFACE_MOBILE
|
#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 updateThumbnail(QString filename, QImage thumbnail, duration_t duration);
|
||||||
void profileChanged(dive *d);
|
void profileChanged(dive *d);
|
||||||
|
|
||||||
|
@ -141,25 +137,8 @@ private:
|
||||||
RulerItem2 *rulerItem;
|
RulerItem2 *rulerItem;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::vector<std::unique_ptr<QGraphicsSimpleTextItem>> gases;
|
|
||||||
|
|
||||||
#ifndef SUBSURFACE_MOBILE
|
#ifndef SUBSURFACE_MOBILE
|
||||||
void keyDeleteAction();
|
|
||||||
void keyUpAction();
|
|
||||||
void keyDownAction();
|
|
||||||
void keyLeftAction();
|
|
||||||
void keyRightAction();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<DiveHandler>> handles;
|
|
||||||
int handleIndex(const DiveHandler *h) const;
|
|
||||||
void disconnectPlannerModel();
|
|
||||||
#ifndef SUBSURFACE_MOBILE
|
|
||||||
void connectPlannerModel();
|
|
||||||
void repositionDiveHandlers();
|
void repositionDiveHandlers();
|
||||||
int fixHandlerIndex(DiveHandler *activeHandler);
|
|
||||||
DiveHandler *createHandle();
|
|
||||||
QGraphicsSimpleTextItem *createGas();
|
|
||||||
#endif
|
#endif
|
||||||
friend class DiveHandler;
|
friend class DiveHandler;
|
||||||
bool shouldCalculateMax; // Calculate maximum time and depth (default). False when dragging handles.
|
bool shouldCalculateMax; // Calculate maximum time and depth (default). False when dragging handles.
|
||||||
|
|
|
@ -85,6 +85,10 @@ RulerItem::RulerItem(ProfileView &view, double dpr) :
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RulerItem::~RulerItem()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void RulerItem::setVisible(bool visible)
|
void RulerItem::setVisible(bool visible)
|
||||||
{
|
{
|
||||||
line->setVisible(visible);
|
line->setVisible(visible);
|
||||||
|
|
|
@ -24,6 +24,7 @@ class RulerItem {
|
||||||
QPixmap title;
|
QPixmap title;
|
||||||
public:
|
public:
|
||||||
RulerItem(ProfileView &view, double dpr);
|
RulerItem(ProfileView &view, double dpr);
|
||||||
|
~RulerItem();
|
||||||
void setVisible(bool visible);
|
void setVisible(bool visible);
|
||||||
void update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed);
|
void update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed);
|
||||||
void anim(double progress);
|
void anim(double progress);
|
||||||
|
|
|
@ -14,6 +14,8 @@ struct ProfileZValue {
|
||||||
Profile = 0,
|
Profile = 0,
|
||||||
Pictures,
|
Pictures,
|
||||||
RulerItem,
|
RulerItem,
|
||||||
|
Handles,
|
||||||
|
MouseFollower,
|
||||||
ToolTipItem,
|
ToolTipItem,
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,6 +37,10 @@ QRectF ChartItem::getRect() const
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChartItem::startDrag(QPointF pos)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void ChartItem::drag(QPointF)
|
void ChartItem::drag(QPointF)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,11 @@ public:
|
||||||
const bool dragable; // Item can be dragged with the mouse. Must be set in constructor.
|
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.
|
virtual ~ChartItem(); // Attention: must only be called by render thread.
|
||||||
QRectF getRect() const;
|
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 drag(QPointF pos); // Called when dragging the item
|
||||||
virtual void stopDrag(QPointF pos); // Called when dragging the item finished
|
virtual void stopDrag(QPointF pos); // Called when dragging the item finished
|
||||||
protected:
|
protected:
|
||||||
|
template <typename T> friend class ChartItemPtr;
|
||||||
ChartItem(ChartView &v, size_t z, bool dragable = false);
|
ChartItem(ChartView &v, size_t z, bool dragable = false);
|
||||||
QSizeF sceneSize() const;
|
QSizeF sceneSize() const;
|
||||||
ChartView &view;
|
ChartView &view;
|
||||||
|
|
|
@ -49,6 +49,12 @@ public:
|
||||||
{
|
{
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
void del()
|
||||||
|
{
|
||||||
|
if (!ptr)
|
||||||
|
return;
|
||||||
|
ptr->view.deleteChartItem(*this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -315,6 +315,7 @@ void ChartView::mousePressEvent(QMouseEvent *event)
|
||||||
dragStartMouse = pos;
|
dragStartMouse = pos;
|
||||||
dragStartItem = rect.topLeft();
|
dragStartItem = rect.topLeft();
|
||||||
draggedItem = item;
|
draggedItem = item;
|
||||||
|
draggedItem->startDrag(pos);
|
||||||
grabMouse();
|
grabMouse();
|
||||||
setKeepMouseGrab(true); // don't allow Qt to steal the grab
|
setKeepMouseGrab(true); // don't allow Qt to steal the grab
|
||||||
event->accept();
|
event->accept();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue