aboutsummaryrefslogtreecommitdiffstats
path: root/profile-widget/profilewidget2.cpp
diff options
context:
space:
mode:
authorGravatar Tomaz Canabrava <tomaz.canabrava@intel.com>2015-09-03 15:56:37 -0300
committerGravatar Dirk Hohndel <dirk@hohndel.org>2015-10-30 10:36:49 -0700
commit1d6683f3e07d9a73af5fab702bc3a551ec7dabc9 (patch)
tree80a64ced06489bf0dca866b2097ca7048b1f0ab8 /profile-widget/profilewidget2.cpp
parent50ec7200e66637abefe685e1875f3d4de2101158 (diff)
downloadsubsurface-1d6683f3e07d9a73af5fab702bc3a551ec7dabc9.tar.gz
Move Profile widget out of desktop-widgets
The reason for that is, even if profile widget is made with qpainter and for that reason it should be a desktop widget, it's being used on the mobile version because of a lack of QML plotting library that is fast and reliable. We discovered that it was faster just to encapsulate our Profile in a QML class and call it directly. Signed-off-by: Tomaz Canabrava <tomaz.canabrava@intel.com> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Diffstat (limited to 'profile-widget/profilewidget2.cpp')
-rw-r--r--profile-widget/profilewidget2.cpp1836
1 files changed, 1836 insertions, 0 deletions
diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp
new file mode 100644
index 000000000..3ccd1bb6d
--- /dev/null
+++ b/profile-widget/profilewidget2.cpp
@@ -0,0 +1,1836 @@
+#include "profilewidget2.h"
+#include "diveplotdatamodel.h"
+#include "helpers.h"
+#include "profile.h"
+#include "diveeventitem.h"
+#include "divetextitem.h"
+#include "divetooltipitem.h"
+#include "planner.h"
+#include "device.h"
+#include "ruleritem.h"
+#include "tankitem.h"
+#include "pref.h"
+#include "divepicturewidget.h"
+#include "diveplannermodel.h"
+#include "models.h"
+#include "divepicturemodel.h"
+#include "maintab.h"
+#include "diveplanner.h"
+
+#include <libdivecomputer/parser.h>
+#include <QScrollBar>
+#include <QtCore/qmath.h>
+#include <QMessageBox>
+#include <QInputDialog>
+#include <QDebug>
+#include <QWheelEvent>
+
+#ifndef QT_NO_DEBUG
+#include <QTableView>
+#endif
+#include "mainwindow.h"
+#include <preferences.h>
+
+/* This is the global 'Item position' variable.
+ * it should tell you where to position things up
+ * on the canvas.
+ *
+ * please, please, please, use this instead of
+ * hard coding the item on the scene with a random
+ * value.
+ */
+static struct _ItemPos {
+ struct _Pos {
+ QPointF on;
+ QPointF off;
+ };
+ struct _Axis {
+ _Pos pos;
+ QLineF shrinked;
+ QLineF expanded;
+ QLineF intermediate;
+ };
+ _Pos background;
+ _Pos dcLabel;
+ _Pos tankBar;
+ _Axis depth;
+ _Axis partialPressure;
+ _Axis partialPressureTissue;
+ _Axis partialPressureWithTankBar;
+ _Axis percentage;
+ _Axis percentageWithTankBar;
+ _Axis time;
+ _Axis cylinder;
+ _Axis temperature;
+ _Axis temperatureAll;
+ _Axis heartBeat;
+ _Axis heartBeatWithTankBar;
+} itemPos;
+
+ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent),
+ currentState(INVALID),
+ dataModel(new DivePlotDataModel(this)),
+ zoomLevel(0),
+ zoomFactor(1.15),
+ background(new DivePixmapItem()),
+ backgroundFile(":poster"),
+ toolTipItem(new ToolTipItem()),
+ isPlotZoomed(prefs.zoomed_plot),// no! bad use of prefs. 'PreferencesDialog::loadSettings' NOT CALLED yet.
+ profileYAxis(new DepthAxis()),
+ gasYAxis(new PartialGasPressureAxis()),
+ temperatureAxis(new TemperatureAxis()),
+ timeAxis(new TimeAxis()),
+ diveProfileItem(new DiveProfileItem()),
+ temperatureItem(new DiveTemperatureItem()),
+ meanDepthItem(new DiveMeanDepthItem()),
+ cylinderPressureAxis(new DiveCartesianAxis()),
+ gasPressureItem(new DiveGasPressureItem()),
+ diveComputerText(new DiveTextItem()),
+ diveCeiling(new DiveCalculatedCeiling()),
+ gradientFactor(new DiveTextItem()),
+ reportedCeiling(new DiveReportedCeiling()),
+ pn2GasItem(new PartialPressureGasItem()),
+ pheGasItem(new PartialPressureGasItem()),
+ po2GasItem(new PartialPressureGasItem()),
+ o2SetpointGasItem(new PartialPressureGasItem()),
+ ccrsensor1GasItem(new PartialPressureGasItem()),
+ ccrsensor2GasItem(new PartialPressureGasItem()),
+ ccrsensor3GasItem(new PartialPressureGasItem()),
+ heartBeatAxis(new DiveCartesianAxis()),
+ heartBeatItem(new DiveHeartrateItem()),
+ percentageAxis(new DiveCartesianAxis()),
+ ambPressureItem(new DiveAmbPressureItem()),
+ gflineItem(new DiveGFLineItem()),
+ mouseFollowerVertical(new DiveLineItem()),
+ mouseFollowerHorizontal(new DiveLineItem()),
+ rulerItem(new RulerItem2()),
+ tankItem(new TankItem()),
+ isGrayscale(false),
+ printMode(false),
+ shouldCalculateMaxTime(true),
+ shouldCalculateMaxDepth(true),
+ fontPrintScale(1.0)
+{
+ // would like to be able to ASSERT here that PreferencesDialog::loadSettings has been called.
+ isPlotZoomed = prefs.zoomed_plot; // now it seems that 'prefs' has loaded our preferences
+
+ memset(&plotInfo, 0, sizeof(plotInfo));
+
+ setupSceneAndFlags();
+ setupItemSizes();
+ setupItemOnScene();
+ addItemsToScene();
+ scene()->installEventFilter(this);
+ connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged()));
+ QAction *action = NULL;
+#define ADD_ACTION(SHORTCUT, Slot) \
+ action = new QAction(this); \
+ action->setShortcut(SHORTCUT); \
+ action->setShortcutContext(Qt::WindowShortcut); \
+ addAction(action); \
+ connect(action, SIGNAL(triggered(bool)), this, SLOT(Slot)); \
+ actionsForKeys[SHORTCUT] = action;
+
+ ADD_ACTION(Qt::Key_Escape, keyEscAction());
+ ADD_ACTION(Qt::Key_Delete, keyDeleteAction());
+ ADD_ACTION(Qt::Key_Up, keyUpAction());
+ ADD_ACTION(Qt::Key_Down, keyDownAction());
+ ADD_ACTION(Qt::Key_Left, keyLeftAction());
+ ADD_ACTION(Qt::Key_Right, keyRightAction());
+#undef ADD_ACTION
+
+#if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE)
+ QTableView *diveDepthTableView = new QTableView();
+ diveDepthTableView->setModel(dataModel);
+ diveDepthTableView->show();
+#endif
+}
+
+
+ProfileWidget2::~ProfileWidget2()
+{
+ delete background;
+ delete toolTipItem;
+ delete profileYAxis;
+ delete gasYAxis;
+ delete temperatureAxis;
+ delete timeAxis;
+ delete diveProfileItem;
+ delete temperatureItem;
+ delete meanDepthItem;
+ delete cylinderPressureAxis;
+ delete gasPressureItem;
+ delete diveComputerText;
+ delete diveCeiling;
+ delete reportedCeiling;
+ delete pn2GasItem;
+ delete pheGasItem;
+ delete po2GasItem;
+ delete o2SetpointGasItem;
+ delete ccrsensor1GasItem;
+ delete ccrsensor2GasItem;
+ delete ccrsensor3GasItem;
+ delete heartBeatAxis;
+ delete heartBeatItem;
+ delete percentageAxis;
+ delete ambPressureItem;
+ delete gflineItem;
+ delete mouseFollowerVertical;
+ delete mouseFollowerHorizontal;
+ delete rulerItem;
+ delete tankItem;
+}
+
+#define SUBSURFACE_OBJ_DATA 1
+#define SUBSURFACE_OBJ_DC_TEXT 0x42
+
+void ProfileWidget2::addItemsToScene()
+{
+ scene()->addItem(background);
+ scene()->addItem(toolTipItem);
+ scene()->addItem(profileYAxis);
+ scene()->addItem(gasYAxis);
+ scene()->addItem(temperatureAxis);
+ scene()->addItem(timeAxis);
+ scene()->addItem(diveProfileItem);
+ scene()->addItem(cylinderPressureAxis);
+ scene()->addItem(temperatureItem);
+ scene()->addItem(meanDepthItem);
+ scene()->addItem(gasPressureItem);
+ // I cannot seem to figure out if an object that I find with itemAt() on the scene
+ // is the object I am looking for - my guess is there's a simple way in Qt to do that
+ // but nothing I tried worked.
+ // so instead this adds a special magic key/value pair to the object to mark it
+ diveComputerText->setData(SUBSURFACE_OBJ_DATA, SUBSURFACE_OBJ_DC_TEXT);
+ scene()->addItem(diveComputerText);
+ scene()->addItem(diveCeiling);
+ scene()->addItem(gradientFactor);
+ scene()->addItem(reportedCeiling);
+ scene()->addItem(pn2GasItem);
+ scene()->addItem(pheGasItem);
+ scene()->addItem(po2GasItem);
+ scene()->addItem(o2SetpointGasItem);
+ scene()->addItem(ccrsensor1GasItem);
+ scene()->addItem(ccrsensor2GasItem);
+ scene()->addItem(ccrsensor3GasItem);
+ scene()->addItem(percentageAxis);
+ scene()->addItem(heartBeatAxis);
+ scene()->addItem(heartBeatItem);
+ scene()->addItem(rulerItem);
+ scene()->addItem(rulerItem->sourceNode());
+ scene()->addItem(rulerItem->destNode());
+ scene()->addItem(tankItem);
+ scene()->addItem(mouseFollowerHorizontal);
+ scene()->addItem(mouseFollowerVertical);
+ QPen pen(QColor(Qt::red).lighter());
+ pen.setWidth(0);
+ mouseFollowerHorizontal->setPen(pen);
+ mouseFollowerVertical->setPen(pen);
+ Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) {
+ scene()->addItem(tissue);
+ }
+ Q_FOREACH (DivePercentageItem *percentage, allPercentages) {
+ scene()->addItem(percentage);
+ }
+ scene()->addItem(ambPressureItem);
+ scene()->addItem(gflineItem);
+}
+
+void ProfileWidget2::setupItemOnScene()
+{
+ background->setZValue(9999);
+ toolTipItem->setZValue(9998);
+ toolTipItem->setTimeAxis(timeAxis);
+ rulerItem->setZValue(9997);
+ tankItem->setZValue(100);
+
+ profileYAxis->setOrientation(DiveCartesianAxis::TopToBottom);
+ profileYAxis->setMinimum(0);
+ profileYAxis->setTickInterval(M_OR_FT(10, 30));
+ profileYAxis->setTickSize(0.5);
+ profileYAxis->setLineSize(96);
+
+ timeAxis->setLineSize(92);
+ timeAxis->setTickSize(-0.5);
+
+ gasYAxis->setOrientation(DiveCartesianAxis::BottomToTop);
+ gasYAxis->setTickInterval(1);
+ gasYAxis->setTickSize(1);
+ gasYAxis->setMinimum(0);
+ gasYAxis->setModel(dataModel);
+ gasYAxis->setFontLabelScale(0.7);
+ gasYAxis->setLineSize(96);
+
+ heartBeatAxis->setOrientation(DiveCartesianAxis::BottomToTop);
+ heartBeatAxis->setTickSize(0.2);
+ heartBeatAxis->setTickInterval(10);
+ heartBeatAxis->setFontLabelScale(0.7);
+ heartBeatAxis->setLineSize(96);
+
+ percentageAxis->setOrientation(DiveCartesianAxis::BottomToTop);
+ percentageAxis->setTickSize(0.2);
+ percentageAxis->setTickInterval(10);
+ percentageAxis->setFontLabelScale(0.7);
+ percentageAxis->setLineSize(96);
+
+ temperatureAxis->setOrientation(DiveCartesianAxis::BottomToTop);
+ temperatureAxis->setTickSize(2);
+ temperatureAxis->setTickInterval(300);
+
+ cylinderPressureAxis->setOrientation(DiveCartesianAxis::BottomToTop);
+ cylinderPressureAxis->setTickSize(2);
+ cylinderPressureAxis->setTickInterval(30000);
+
+
+ diveComputerText->setAlignment(Qt::AlignRight | Qt::AlignTop);
+ diveComputerText->setBrush(getColor(TIME_TEXT, isGrayscale));
+
+ rulerItem->setAxis(timeAxis, profileYAxis);
+ tankItem->setHorizontalAxis(timeAxis);
+
+ // show the gradient factor at the top in the center
+ gradientFactor->setY(0);
+ gradientFactor->setX(50);
+ gradientFactor->setBrush(getColor(PRESSURE_TEXT));
+ gradientFactor->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
+
+ setupItem(reportedCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1);
+ setupItem(diveCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1);
+ for (int i = 0; i < 16; i++) {
+ DiveCalculatedTissue *tissueItem = new DiveCalculatedTissue();
+ setupItem(tissueItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::TISSUE_1 + i, DivePlotDataModel::TIME, 1 + i);
+ allTissues.append(tissueItem);
+ DivePercentageItem *percentageItem = new DivePercentageItem(i);
+ setupItem(percentageItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::PERCENTAGE_1 + i, DivePlotDataModel::TIME, 1 + i);
+ allPercentages.append(percentageItem);
+ }
+ setupItem(gasPressureItem, timeAxis, cylinderPressureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1);
+ setupItem(temperatureItem, timeAxis, temperatureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1);
+ setupItem(heartBeatItem, timeAxis, heartBeatAxis, dataModel, DivePlotDataModel::HEARTBEAT, DivePlotDataModel::TIME, 1);
+ setupItem(ambPressureItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::AMBPRESSURE, DivePlotDataModel::TIME, 1);
+ setupItem(gflineItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::GFLINE, DivePlotDataModel::TIME, 1);
+ setupItem(diveProfileItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::DEPTH, DivePlotDataModel::TIME, 0);
+ setupItem(meanDepthItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::INSTANT_MEANDEPTH, DivePlotDataModel::TIME, 1);
+
+
+#define CREATE_PP_GAS(ITEM, VERTICAL_COLUMN, COLOR, COLOR_ALERT, THRESHOULD_SETTINGS, VISIBILITY_SETTINGS) \
+ setupItem(ITEM, timeAxis, gasYAxis, dataModel, DivePlotDataModel::VERTICAL_COLUMN, DivePlotDataModel::TIME, 0); \
+ ITEM->setThreshouldSettingsKey(THRESHOULD_SETTINGS); \
+ ITEM->setVisibilitySettingsKey(VISIBILITY_SETTINGS); \
+ ITEM->setColors(getColor(COLOR, isGrayscale), getColor(COLOR_ALERT, isGrayscale)); \
+ ITEM->settingsChanged(); \
+ ITEM->setZValue(99);
+
+ CREATE_PP_GAS(pn2GasItem, PN2, PN2, PN2_ALERT, &prefs.pp_graphs.pn2_threshold, "pn2graph");
+ CREATE_PP_GAS(pheGasItem, PHE, PHE, PHE_ALERT, &prefs.pp_graphs.phe_threshold, "phegraph");
+ CREATE_PP_GAS(po2GasItem, PO2, PO2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph");
+ CREATE_PP_GAS(o2SetpointGasItem, O2SETPOINT, PO2_ALERT, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph");
+ CREATE_PP_GAS(ccrsensor1GasItem, CCRSENSOR1, CCRSENSOR1, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph");
+ CREATE_PP_GAS(ccrsensor2GasItem, CCRSENSOR2, CCRSENSOR2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph");
+ CREATE_PP_GAS(ccrsensor3GasItem, CCRSENSOR3, CCRSENSOR3, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph");
+#undef CREATE_PP_GAS
+
+ temperatureAxis->setTextVisible(false);
+ temperatureAxis->setLinesVisible(false);
+ cylinderPressureAxis->setTextVisible(false);
+ cylinderPressureAxis->setLinesVisible(false);
+ timeAxis->setLinesVisible(true);
+ profileYAxis->setLinesVisible(true);
+ gasYAxis->setZValue(timeAxis->zValue() + 1);
+ heartBeatAxis->setTextVisible(true);
+ heartBeatAxis->setLinesVisible(true);
+ percentageAxis->setTextVisible(true);
+ percentageAxis->setLinesVisible(true);
+
+ replotEnabled = true;
+}
+
+void ProfileWidget2::replot(struct dive *d)
+{
+ if (!replotEnabled)
+ return;
+ dataModel->clear();
+ plotDive(d, true);
+}
+
+void ProfileWidget2::setupItemSizes()
+{
+ // Scene is *always* (double) 100 / 100.
+ // Background Config
+ /* Much probably a better math is needed here.
+ * good thing is that we only need to change the
+ * Axis and everything else is auto-adjusted.*
+ */
+
+ itemPos.background.on.setX(0);
+ itemPos.background.on.setY(0);
+ itemPos.background.off.setX(0);
+ itemPos.background.off.setY(110);
+
+ //Depth Axis Config
+ itemPos.depth.pos.on.setX(3);
+ itemPos.depth.pos.on.setY(3);
+ itemPos.depth.pos.off.setX(-2);
+ itemPos.depth.pos.off.setY(3);
+ itemPos.depth.expanded.setP1(QPointF(0, 0));
+ itemPos.depth.expanded.setP2(QPointF(0, 85));
+ itemPos.depth.shrinked.setP1(QPointF(0, 0));
+ itemPos.depth.shrinked.setP2(QPointF(0, 55));
+ itemPos.depth.intermediate.setP1(QPointF(0, 0));
+ itemPos.depth.intermediate.setP2(QPointF(0, 65));
+
+ // Time Axis Config
+ itemPos.time.pos.on.setX(3);
+ itemPos.time.pos.on.setY(95);
+ itemPos.time.pos.off.setX(3);
+ itemPos.time.pos.off.setY(110);
+ itemPos.time.expanded.setP1(QPointF(0, 0));
+ itemPos.time.expanded.setP2(QPointF(94, 0));
+
+ // Partial Gas Axis Config
+ itemPos.partialPressure.pos.on.setX(97);
+ itemPos.partialPressure.pos.on.setY(75);
+ itemPos.partialPressure.pos.off.setX(110);
+ itemPos.partialPressure.pos.off.setY(63);
+ itemPos.partialPressure.expanded.setP1(QPointF(0, 0));
+ itemPos.partialPressure.expanded.setP2(QPointF(0, 19));
+ itemPos.partialPressureWithTankBar = itemPos.partialPressure;
+ itemPos.partialPressureWithTankBar.expanded.setP2(QPointF(0, 17));
+ itemPos.partialPressureTissue = itemPos.partialPressure;
+ itemPos.partialPressureTissue.pos.on.setX(97);
+ itemPos.partialPressureTissue.pos.on.setY(65);
+ itemPos.partialPressureTissue.expanded.setP2(QPointF(0, 16));
+
+ // cylinder axis config
+ itemPos.cylinder.pos.on.setX(3);
+ itemPos.cylinder.pos.on.setY(20);
+ itemPos.cylinder.pos.off.setX(-10);
+ itemPos.cylinder.pos.off.setY(20);
+ itemPos.cylinder.expanded.setP1(QPointF(0, 15));
+ itemPos.cylinder.expanded.setP2(QPointF(0, 50));
+ itemPos.cylinder.shrinked.setP1(QPointF(0, 0));
+ itemPos.cylinder.shrinked.setP2(QPointF(0, 20));
+ itemPos.cylinder.intermediate.setP1(QPointF(0, 0));
+ itemPos.cylinder.intermediate.setP2(QPointF(0, 20));
+
+ // Temperature axis config
+ itemPos.temperature.pos.on.setX(3);
+ itemPos.temperature.pos.on.setY(60);
+ itemPos.temperatureAll.pos.on.setY(51);
+ itemPos.temperature.pos.off.setX(-10);
+ itemPos.temperature.pos.off.setY(40);
+ itemPos.temperature.expanded.setP1(QPointF(0, 20));
+ itemPos.temperature.expanded.setP2(QPointF(0, 33));
+ itemPos.temperature.shrinked.setP1(QPointF(0, 2));
+ itemPos.temperature.shrinked.setP2(QPointF(0, 12));
+ itemPos.temperature.intermediate.setP1(QPointF(0, 2));
+ itemPos.temperature.intermediate.setP2(QPointF(0, 12));
+
+ // Heartbeat axis config
+ itemPos.heartBeat.pos.on.setX(3);
+ itemPos.heartBeat.pos.on.setY(82);
+ itemPos.heartBeat.expanded.setP1(QPointF(0, 0));
+ itemPos.heartBeat.expanded.setP2(QPointF(0, 10));
+ itemPos.heartBeatWithTankBar = itemPos.heartBeat;
+ itemPos.heartBeatWithTankBar.expanded.setP2(QPointF(0, 7));
+
+ // Percentage axis config
+ itemPos.percentage.pos.on.setX(3);
+ itemPos.percentage.pos.on.setY(80);
+ itemPos.percentage.expanded.setP1(QPointF(0, 0));
+ itemPos.percentage.expanded.setP2(QPointF(0, 15));
+ itemPos.percentageWithTankBar = itemPos.percentage;
+ itemPos.percentageWithTankBar.expanded.setP2(QPointF(0, 12));
+
+ itemPos.dcLabel.on.setX(3);
+ itemPos.dcLabel.on.setY(100);
+ itemPos.dcLabel.off.setX(-10);
+ itemPos.dcLabel.off.setY(100);
+
+ itemPos.tankBar.on.setX(0);
+ itemPos.tankBar.on.setY(91.5);
+}
+
+void ProfileWidget2::setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *hAxis,
+ DiveCartesianAxis *vAxis, DivePlotDataModel *model,
+ int vData, int hData, int zValue)
+{
+ item->setHorizontalAxis(hAxis);
+ item->setVerticalAxis(vAxis);
+ item->setModel(model);
+ item->setVerticalDataColumn(vData);
+ item->setHorizontalDataColumn(hData);
+ item->setZValue(zValue);
+}
+
+void ProfileWidget2::setupSceneAndFlags()
+{
+ setScene(new QGraphicsScene(this));
+ scene()->setSceneRect(0, 0, 100, 100);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ scene()->setItemIndexMethod(QGraphicsScene::NoIndex);
+ setOptimizationFlags(QGraphicsView::DontSavePainterState);
+ setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
+ setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
+ setMouseTracking(true);
+ background->setFlag(QGraphicsItem::ItemIgnoresTransformations);
+}
+
+void ProfileWidget2::resetZoom()
+{
+ if (!zoomLevel)
+ return;
+ const qreal defScale = 1.0 / qPow(zoomFactor, (qreal)zoomLevel);
+ scale(defScale, defScale);
+ zoomLevel = 0;
+}
+
+// Currently just one dive, but the plan is to enable All of the selected dives.
+void ProfileWidget2::plotDive(struct dive *d, bool force)
+{
+ static bool firstCall = true;
+ QTime measureDuration; // let's measure how long this takes us (maybe we'll turn of TTL calculation later
+ measureDuration.start();
+
+ if (currentState != ADD && currentState != PLAN) {
+ if (!d) {
+ if (selected_dive == -1)
+ return;
+ d = current_dive; // display the current dive
+ }
+
+ // No need to do this again if we are already showing the same dive
+ // computer of the same dive, so we check the unique id of the dive
+ // and the selected dive computer number against the ones we are
+ // showing (can't compare the dive pointers as those might change).
+ if (d->id == displayed_dive.id && dc_number == dataModel->dcShown() && !force)
+ return;
+
+ // this copies the dive and makes copies of all the relevant additional data
+ copy_dive(d, &displayed_dive);
+ gradientFactor->setText(QString("GF %1/%2").arg(prefs.gflow).arg(prefs.gfhigh));
+ } else {
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ plannerModel->createTemporaryPlan();
+ struct diveplan &diveplan = plannerModel->getDiveplan();
+ if (!diveplan.dp) {
+ plannerModel->deleteTemporaryPlan();
+ return;
+ }
+ gradientFactor->setText(QString("GF %1/%2").arg(diveplan.gflow).arg(diveplan.gfhigh));
+ }
+
+ // special handling for the first time we display things
+ int animSpeedBackup = 0;
+ if (firstCall && MainWindow::instance()->filesFromCommandLine()) {
+ animSpeedBackup = prefs.animation_speed;
+ prefs.animation_speed = 0;
+ firstCall = false;
+ }
+
+ // restore default zoom level
+ resetZoom();
+
+ // reset some item visibility on printMode changes
+ toolTipItem->setVisible(!printMode);
+ rulerItem->setVisible(prefs.rulergraph && !printMode && currentState != PLAN && currentState != ADD);
+
+ if (currentState == EMPTY)
+ setProfileState();
+
+ // next get the dive computer structure - if there are no samples
+ // let's create a fake profile that's somewhat reasonable for the
+ // data that we have
+ struct divecomputer *currentdc = select_dc(&displayed_dive);
+ Q_ASSERT(currentdc);
+ if (!currentdc || !currentdc->samples) {
+ currentdc = fake_dc(currentdc);
+ }
+
+ bool setpointflag = (currentdc->divemode == CCR) && prefs.pp_graphs.po2 && current_dive;
+ bool sensorflag = setpointflag && prefs.show_ccr_sensors;
+ o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint);
+ ccrsensor1GasItem->setVisible(sensorflag);
+ ccrsensor2GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 1));
+ ccrsensor3GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 2));
+
+ /* This struct holds all the data that's about to be plotted.
+ * I'm not sure this is the best approach ( but since we are
+ * interpolating some points of the Dive, maybe it is... )
+ * The Calculation of the points should be done per graph,
+ * so I'll *not* calculate everything if something is not being
+ * shown.
+ */
+ plotInfo = calculate_max_limits_new(&displayed_dive, currentdc);
+ create_plot_info_new(&displayed_dive, currentdc, &plotInfo, !shouldCalculateMaxDepth);
+ if (shouldCalculateMaxTime)
+ maxtime = get_maxtime(&plotInfo);
+
+ /* Only update the max depth if it's bigger than the current ones
+ * when we are dragging the handler to plan / add dive.
+ * otherwhise, update normally.
+ */
+ int newMaxDepth = get_maxdepth(&plotInfo);
+ if (!shouldCalculateMaxDepth) {
+ if (maxdepth < newMaxDepth) {
+ maxdepth = newMaxDepth;
+ }
+ } else {
+ maxdepth = newMaxDepth;
+ }
+
+ dataModel->setDive(&displayed_dive, plotInfo);
+ toolTipItem->setPlotInfo(plotInfo);
+
+ // It seems that I'll have a lot of boilerplate setting the model / axis for
+ // each item, I'll mostly like to fix this in the future, but I'll keep at this for now.
+ profileYAxis->setMaximum(maxdepth);
+ profileYAxis->updateTicks();
+
+ temperatureAxis->setMinimum(plotInfo.mintemp);
+ temperatureAxis->setMaximum(plotInfo.maxtemp - plotInfo.mintemp > 2000 ? plotInfo.maxtemp : plotInfo.mintemp + 2000);
+
+ if (plotInfo.maxhr) {
+ heartBeatAxis->setMinimum(plotInfo.minhr);
+ heartBeatAxis->setMaximum(plotInfo.maxhr);
+ heartBeatAxis->updateTicks(HR_AXIS); // this shows the ticks
+ }
+ heartBeatAxis->setVisible(prefs.hrgraph && plotInfo.maxhr);
+
+ percentageAxis->setMinimum(0);
+ percentageAxis->setMaximum(100);
+ percentageAxis->setVisible(false);
+ percentageAxis->updateTicks(HR_AXIS);
+
+ timeAxis->setMaximum(maxtime);
+ int i, incr;
+ static int increments[8] = { 10, 20, 30, 60, 5 * 60, 10 * 60, 15 * 60, 30 * 60 };
+ /* Time markers: at most every 10 seconds, but no more than 12 markers.
+ * We start out with 10 seconds and increment up to 30 minutes,
+ * depending on the dive time.
+ * This allows for 6h dives - enough (I hope) for even the craziest
+ * divers - but just in case, for those 8h depth-record-breaking dives,
+ * we double the interval if this still doesn't get us to 12 or fewer
+ * time markers */
+ i = 0;
+ while (i < 7 && maxtime / increments[i] > 12)
+ i++;
+ incr = increments[i];
+ while (maxtime / incr > 12)
+ incr *= 2;
+ timeAxis->setTickInterval(incr);
+ timeAxis->updateTicks();
+ cylinderPressureAxis->setMinimum(plotInfo.minpressure);
+ cylinderPressureAxis->setMaximum(plotInfo.maxpressure);
+
+ rulerItem->setPlotInfo(plotInfo);
+ tankItem->setData(dataModel, &plotInfo, &displayed_dive);
+
+ dataModel->emitDataChanged();
+ // The event items are a bit special since we don't know how many events are going to
+ // exist on a dive, so I cant create cache items for that. that's why they are here
+ // while all other items are up there on the constructor.
+ qDeleteAll(eventItems);
+ eventItems.clear();
+ struct event *event = currentdc->events;
+ while (event) {
+ // if print mode is selected only draw headings, SP change, gas events or bookmark event
+ if (printMode) {
+ if (same_string(event->name, "") ||
+ !(strcmp(event->name, "heading") == 0 ||
+ (same_string(event->name, "SP change") && event->time.seconds == 0) ||
+ event_is_gaschange(event) ||
+ event->type == SAMPLE_EVENT_BOOKMARK)) {
+ event = event->next;
+ continue;
+ }
+ }
+ DiveEventItem *item = new DiveEventItem();
+ item->setHorizontalAxis(timeAxis);
+ item->setVerticalAxis(profileYAxis);
+ item->setModel(dataModel);
+ item->setEvent(event);
+ item->setZValue(2);
+ scene()->addItem(item);
+ eventItems.push_back(item);
+ event = event->next;
+ }
+ // Only set visible the events that should be visible
+ Q_FOREACH (DiveEventItem *event, eventItems) {
+ event->setVisible(!event->shouldBeHidden());
+ }
+ QString dcText = get_dc_nickname(currentdc->model, currentdc->deviceid);
+ int nr;
+ if ((nr = number_of_computers(&displayed_dive)) > 1)
+ dcText += tr(" (#%1 of %2)").arg(dc_number + 1).arg(nr);
+ if (dcText.isEmpty())
+ dcText = tr("Unknown dive computer");
+ diveComputerText->setText(dcText);
+ if (MainWindow::instance()->filesFromCommandLine() && animSpeedBackup != 0) {
+ prefs.animation_speed = animSpeedBackup;
+ }
+
+ if (currentState == ADD || currentState == PLAN) { // TODO: figure a way to move this from here.
+ repositionDiveHandlers();
+ DivePlannerPointsModel *model = DivePlannerPointsModel::instance();
+ model->deleteTemporaryPlan();
+ }
+ plotPictures();
+
+ // OK, how long did this take us? Anything above the second is way too long,
+ // so if we are calculation TTS / NDL then let's force that off.
+ if (measureDuration.elapsed() > 1000 && prefs.calcndltts) {
+ MainWindow::instance()->turnOffNdlTts();
+ MainWindow::instance()->getNotificationWidget()->showNotification(tr("Show NDL / TTS was disabled because of excessive processing time"), KMessageWidget::Error);
+ }
+ MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error);
+
+}
+
+void ProfileWidget2::recalcCeiling()
+{
+ diveCeiling->recalc();
+}
+
+void ProfileWidget2::settingsChanged()
+{
+ // if we are showing calculated ceilings then we have to replot()
+ // because the GF could have changed; otherwise we try to avoid replot()
+ bool needReplot = prefs.calcceiling;
+ if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) {
+ profileYAxis->animateChangeLine(itemPos.depth.shrinked);
+ temperatureAxis->setPos(itemPos.temperatureAll.pos.on);
+ temperatureAxis->animateChangeLine(itemPos.temperature.shrinked);
+ cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked);
+
+ if (prefs.tankbar) {
+ percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on);
+ percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded);
+ heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on);
+ heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded);
+ }else {
+ percentageAxis->setPos(itemPos.percentage.pos.on);
+ percentageAxis->animateChangeLine(itemPos.percentage.expanded);
+ heartBeatAxis->setPos(itemPos.heartBeat.pos.on);
+ heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded);
+ }
+ gasYAxis->setPos(itemPos.partialPressureTissue.pos.on);
+ gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded);
+
+ } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) {
+ profileYAxis->animateChangeLine(itemPos.depth.intermediate);
+ temperatureAxis->setPos(itemPos.temperature.pos.on);
+ temperatureAxis->animateChangeLine(itemPos.temperature.intermediate);
+ cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate);
+ if (prefs.tankbar) {
+ percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on);
+ percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded);
+ gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on);
+ gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded);
+ heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on);
+ heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded);
+ } else {
+ gasYAxis->setPos(itemPos.partialPressure.pos.on);
+ gasYAxis->animateChangeLine(itemPos.partialPressure.expanded);
+ percentageAxis->setPos(itemPos.percentage.pos.on);
+ percentageAxis->setLine(itemPos.percentage.expanded);
+ heartBeatAxis->setPos(itemPos.heartBeat.pos.on);
+ heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded);
+ }
+ } else {
+ profileYAxis->animateChangeLine(itemPos.depth.expanded);
+ if (prefs.tankbar) {
+ temperatureAxis->setPos(itemPos.temperatureAll.pos.on);
+ } else {
+ temperatureAxis->setPos(itemPos.temperature.pos.on);
+ }
+ temperatureAxis->animateChangeLine(itemPos.temperature.expanded);
+ cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded);
+ }
+
+ tankItem->setVisible(prefs.tankbar);
+ if (prefs.zoomed_plot != isPlotZoomed) {
+ isPlotZoomed = prefs.zoomed_plot;
+ needReplot = true;
+ }
+ if (needReplot)
+ replot();
+}
+
+void ProfileWidget2::resizeEvent(QResizeEvent *event)
+{
+ QGraphicsView::resizeEvent(event);
+ fitInView(sceneRect(), Qt::IgnoreAspectRatio);
+ fixBackgroundPos();
+}
+
+void ProfileWidget2::mousePressEvent(QMouseEvent *event)
+{
+ if (zoomLevel)
+ return;
+ QGraphicsView::mousePressEvent(event);
+ if (currentState == PLAN)
+ shouldCalculateMaxTime = false;
+}
+
+void ProfileWidget2::divePlannerHandlerClicked()
+{
+ if (zoomLevel)
+ return;
+ shouldCalculateMaxDepth = false;
+ replot();
+}
+
+void ProfileWidget2::divePlannerHandlerReleased()
+{
+ if (zoomLevel)
+ return;
+ shouldCalculateMaxDepth = true;
+ replot();
+}
+
+void ProfileWidget2::mouseReleaseEvent(QMouseEvent *event)
+{
+ if (zoomLevel)
+ return;
+ QGraphicsView::mouseReleaseEvent(event);
+ if (currentState == PLAN) {
+ shouldCalculateMaxTime = true;
+ replot();
+ }
+}
+
+void ProfileWidget2::fixBackgroundPos()
+{
+ static QPixmap toBeScaled(backgroundFile);
+ if (currentState != EMPTY)
+ return;
+ QPixmap p = toBeScaled.scaledToHeight(viewport()->height() - 40, Qt::SmoothTransformation);
+ int x = viewport()->width() / 2 - p.width() / 2;
+ int y = viewport()->height() / 2 - p.height() / 2;
+ background->setPixmap(p);
+ background->setX(mapToScene(x, 0).x());
+ background->setY(mapToScene(y, 20).y());
+}
+
+void ProfileWidget2::wheelEvent(QWheelEvent *event)
+{
+ if (currentState == EMPTY)
+ return;
+ QPoint toolTipPos = mapFromScene(toolTipItem->pos());
+ if (event->buttons() == Qt::LeftButton)
+ return;
+ if (event->delta() > 0 && zoomLevel < 20) {
+ scale(zoomFactor, zoomFactor);
+ zoomLevel++;
+ } else if (event->delta() < 0 && zoomLevel > 0) {
+ // Zooming out
+ scale(1.0 / zoomFactor, 1.0 / zoomFactor);
+ zoomLevel--;
+ }
+ scrollViewTo(event->pos());
+ toolTipItem->setPos(mapToScene(toolTipPos));
+}
+
+void ProfileWidget2::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ if (currentState == PLAN || currentState == ADD) {
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ QPointF mappedPos = mapToScene(event->pos());
+ if (isPointOutOfBoundaries(mappedPos))
+ return;
+
+ int minutes = rint(timeAxis->valueAt(mappedPos) / 60);
+ int milimeters = rint(profileYAxis->valueAt(mappedPos) / M_OR_FT(1, 1)) * M_OR_FT(1, 1);
+ plannerModel->addStop(milimeters, minutes * 60, 0, 0, true);
+ }
+}
+
+bool ProfileWidget2::isPointOutOfBoundaries(const QPointF &point) const
+{
+ double xpos = timeAxis->valueAt(point);
+ double ypos = profileYAxis->valueAt(point);
+ return (xpos > timeAxis->maximum() ||
+ xpos < timeAxis->minimum() ||
+ ypos > profileYAxis->maximum() ||
+ ypos < profileYAxis->minimum());
+}
+
+void ProfileWidget2::scrollViewTo(const QPoint &pos)
+{
+ /* since we cannot use translate() directly on the scene we hack on
+ * the scroll bars (hidden) functionality */
+ if (!zoomLevel || currentState == EMPTY)
+ return;
+ QScrollBar *vs = verticalScrollBar();
+ QScrollBar *hs = horizontalScrollBar();
+ const qreal yRat = (qreal)pos.y() / viewport()->height();
+ const qreal xRat = (qreal)pos.x() / viewport()->width();
+ vs->setValue(yRat * vs->maximum());
+ hs->setValue(xRat * hs->maximum());
+}
+
+void ProfileWidget2::mouseMoveEvent(QMouseEvent *event)
+{
+ QPointF pos = mapToScene(event->pos());
+ toolTipItem->refresh(pos);
+ if (zoomLevel == 0) {
+ QGraphicsView::mouseMoveEvent(event);
+ } else {
+ QPoint toolTipPos = mapFromScene(toolTipItem->pos());
+ scrollViewTo(event->pos());
+ toolTipItem->setPos(mapToScene(toolTipPos));
+ }
+
+ qreal vValue = profileYAxis->valueAt(pos);
+ qreal hValue = timeAxis->valueAt(pos);
+ if (profileYAxis->maximum() >= vValue && profileYAxis->minimum() <= vValue) {
+ mouseFollowerHorizontal->setPos(timeAxis->pos().x(), pos.y());
+ }
+ if (timeAxis->maximum() >= hValue && timeAxis->minimum() <= hValue) {
+ mouseFollowerVertical->setPos(pos.x(), profileYAxis->line().y1());
+ }
+}
+
+bool ProfileWidget2::eventFilter(QObject *object, QEvent *event)
+{
+ QGraphicsScene *s = qobject_cast<QGraphicsScene *>(object);
+ if (s && event->type() == QEvent::GraphicsSceneHelp) {
+ event->ignore();
+ return true;
+ }
+ return QGraphicsView::eventFilter(object, event);
+}
+
+void ProfileWidget2::setEmptyState()
+{
+ // Then starting Empty State, move the background up.
+ if (currentState == EMPTY)
+ return;
+
+ disconnectTemporaryConnections();
+ setBackgroundBrush(getColor(::BACKGROUND, isGrayscale));
+ dataModel->clear();
+ currentState = EMPTY;
+ MainWindow::instance()->setEnabledToolbar(false);
+
+ fixBackgroundPos();
+ background->setVisible(true);
+
+ profileYAxis->setVisible(false);
+ gasYAxis->setVisible(false);
+ timeAxis->setVisible(false);
+ temperatureAxis->setVisible(false);
+ cylinderPressureAxis->setVisible(false);
+ toolTipItem->setVisible(false);
+ diveComputerText->setVisible(false);
+ diveCeiling->setVisible(false);
+ gradientFactor->setVisible(false);
+ reportedCeiling->setVisible(false);
+ rulerItem->setVisible(false);
+ tankItem->setVisible(false);
+ pn2GasItem->setVisible(false);
+ po2GasItem->setVisible(false);
+ o2SetpointGasItem->setVisible(false);
+ ccrsensor1GasItem->setVisible(false);
+ ccrsensor2GasItem->setVisible(false);
+ ccrsensor3GasItem->setVisible(false);
+ pheGasItem->setVisible(false);
+ ambPressureItem->setVisible(false);
+ gflineItem->setVisible(false);
+ mouseFollowerHorizontal->setVisible(false);
+ mouseFollowerVertical->setVisible(false);
+
+#define HIDE_ALL(TYPE, CONTAINER) \
+ Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false);
+ HIDE_ALL(DiveCalculatedTissue, allTissues);
+ HIDE_ALL(DivePercentageItem, allPercentages);
+ HIDE_ALL(DiveEventItem, eventItems);
+ HIDE_ALL(DiveHandler, handles);
+ HIDE_ALL(QGraphicsSimpleTextItem, gases);
+#undef HIDE_ALL
+}
+
+void ProfileWidget2::setProfileState()
+{
+ // Then starting Empty State, move the background up.
+ if (currentState == PROFILE)
+ return;
+
+ disconnectTemporaryConnections();
+ connect(DivePictureModel::instance(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(plotPictures()));
+ connect(DivePictureModel::instance(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(plotPictures()));
+ connect(DivePictureModel::instance(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(plotPictures()));
+ /* show the same stuff that the profile shows. */
+
+ //TODO: Move the DC handling to another method.
+ MainWindow::instance()->enableShortcuts();
+
+ currentState = PROFILE;
+ MainWindow::instance()->setEnabledToolbar(true);
+ toolTipItem->readPos();
+ setBackgroundBrush(getColor(::BACKGROUND, isGrayscale));
+
+ background->setVisible(false);
+ toolTipItem->setVisible(true);
+ profileYAxis->setVisible(true);
+ gasYAxis->setVisible(true);
+ timeAxis->setVisible(true);
+ temperatureAxis->setVisible(true);
+ cylinderPressureAxis->setVisible(true);
+
+ profileYAxis->setPos(itemPos.depth.pos.on);
+ if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) {
+ profileYAxis->animateChangeLine(itemPos.depth.shrinked);
+ temperatureAxis->setPos(itemPos.temperatureAll.pos.on);
+ temperatureAxis->animateChangeLine(itemPos.temperature.shrinked);
+ cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked);
+
+ if (prefs.tankbar) {
+ percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on);
+ percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded);
+ heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on);
+ heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded);
+ }else {
+ percentageAxis->setPos(itemPos.percentage.pos.on);
+ percentageAxis->animateChangeLine(itemPos.percentage.expanded);
+ heartBeatAxis->setPos(itemPos.heartBeat.pos.on);
+ heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded);
+ }
+ gasYAxis->setPos(itemPos.partialPressureTissue.pos.on);
+ gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded);
+
+ } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) {
+ profileYAxis->animateChangeLine(itemPos.depth.intermediate);
+ temperatureAxis->setPos(itemPos.temperature.pos.on);
+ temperatureAxis->animateChangeLine(itemPos.temperature.intermediate);
+ cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate);
+ if (prefs.tankbar) {
+ percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on);
+ percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded);
+ gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on);
+ gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded);
+ heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on);
+ heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded);
+ } else {
+ gasYAxis->setPos(itemPos.partialPressure.pos.on);
+ gasYAxis->animateChangeLine(itemPos.partialPressure.expanded);
+ percentageAxis->setPos(itemPos.percentage.pos.on);
+ percentageAxis->setLine(itemPos.percentage.expanded);
+ heartBeatAxis->setPos(itemPos.heartBeat.pos.on);
+ heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded);
+ }
+ } else {
+ profileYAxis->animateChangeLine(itemPos.depth.expanded);
+ if (prefs.tankbar) {
+ temperatureAxis->setPos(itemPos.temperatureAll.pos.on);
+ } else {
+ temperatureAxis->setPos(itemPos.temperature.pos.on);
+ }
+ temperatureAxis->animateChangeLine(itemPos.temperature.expanded);
+ cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded);
+ }
+ pn2GasItem->setVisible(prefs.pp_graphs.pn2);
+ po2GasItem->setVisible(prefs.pp_graphs.po2);
+ pheGasItem->setVisible(prefs.pp_graphs.phe);
+
+ bool setpointflag = current_dive && (current_dc->divemode == CCR) && prefs.pp_graphs.po2;
+ bool sensorflag = setpointflag && prefs.show_ccr_sensors;
+ o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint);
+ ccrsensor1GasItem->setVisible(sensorflag);
+ ccrsensor2GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 1));
+ ccrsensor3GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 2));
+
+ timeAxis->setPos(itemPos.time.pos.on);
+ timeAxis->setLine(itemPos.time.expanded);
+
+ cylinderPressureAxis->setPos(itemPos.cylinder.pos.on);
+ heartBeatItem->setVisible(prefs.hrgraph);
+ meanDepthItem->setVisible(prefs.show_average_depth);
+
+ diveComputerText->setVisible(true);
+ diveComputerText->setPos(itemPos.dcLabel.on);
+
+ diveCeiling->setVisible(prefs.calcceiling);
+ gradientFactor->setVisible(prefs.calcceiling);
+ reportedCeiling->setVisible(prefs.dcceiling);
+
+ if (prefs.calcalltissues) {
+ Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) {
+ tissue->setVisible(true);
+ }
+ }
+
+ if (prefs.percentagegraph) {
+ Q_FOREACH (DivePercentageItem *percentage, allPercentages) {
+ percentage->setVisible(true);
+ }
+
+ ambPressureItem->setVisible(true);
+ gflineItem->setVisible(true);
+ }
+
+ rulerItem->setVisible(prefs.rulergraph);
+ tankItem->setVisible(prefs.tankbar);
+ tankItem->setPos(itemPos.tankBar.on);
+
+#define HIDE_ALL(TYPE, CONTAINER) \
+ Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false);
+ HIDE_ALL(DiveHandler, handles);
+ HIDE_ALL(QGraphicsSimpleTextItem, gases);
+#undef HIDE_ALL
+ mouseFollowerHorizontal->setVisible(false);
+ mouseFollowerVertical->setVisible(false);
+}
+
+void ProfileWidget2::clearHandlers()
+{
+ if (handles.count()) {
+ foreach (DiveHandler *handle, handles) {
+ scene()->removeItem(handle);
+ delete handle;
+ }
+ handles.clear();
+ }
+}
+
+void ProfileWidget2::setToolTipVisibile(bool visible)
+{
+ toolTipItem->setVisible(visible);
+}
+
+void ProfileWidget2::setAddState()
+{
+ if (currentState == ADD)
+ return;
+
+ clearHandlers();
+ setProfileState();
+ mouseFollowerHorizontal->setVisible(true);
+ mouseFollowerVertical->setVisible(true);
+ mouseFollowerHorizontal->setLine(timeAxis->line());
+ mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y()));
+ disconnectTemporaryConnections();
+ //TODO: Move this method to another place, shouldn't be on mainwindow.
+ MainWindow::instance()->disableShortcuts(false);
+ actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left);
+ actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right);
+ actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up);
+ actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down);
+ actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape);
+ actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete);
+
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot()));
+ connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot()));
+ connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(pointInserted(const QModelIndex &, int, int)));
+ connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(pointsRemoved(const QModelIndex &, int, int)));
+ /* show the same stuff that the profile shows. */
+ currentState = ADD; /* enable the add state. */
+ diveCeiling->setVisible(true);
+ gradientFactor->setVisible(true);
+ setBackgroundBrush(QColor("#A7DCFF"));
+}
+
+void ProfileWidget2::setPlanState()
+{
+ if (currentState == PLAN)
+ return;
+
+ setProfileState();
+ mouseFollowerHorizontal->setVisible(true);
+ mouseFollowerVertical->setVisible(true);
+ mouseFollowerHorizontal->setLine(timeAxis->line());
+ mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y()));
+ disconnectTemporaryConnections();
+ //TODO: Move this method to another place, shouldn't be on mainwindow.
+ MainWindow::instance()->disableShortcuts();
+ actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left);
+ actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right);
+ actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up);
+ actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down);
+ actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape);
+ actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete);
+
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot()));
+ connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot()));
+ connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(pointInserted(const QModelIndex &, int, int)));
+ connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(pointsRemoved(const QModelIndex &, int, int)));
+ /* show the same stuff that the profile shows. */
+ currentState = PLAN; /* enable the add state. */
+ diveCeiling->setVisible(true);
+ gradientFactor->setVisible(true);
+ setBackgroundBrush(QColor("#D7E3EF"));
+}
+
+extern struct ev_select *ev_namelist;
+extern int evn_allocated;
+extern int evn_used;
+
+bool ProfileWidget2::isPlanner()
+{
+ return currentState == PLAN;
+}
+
+bool ProfileWidget2::isAddOrPlanner()
+{
+ return currentState == PLAN || currentState == ADD;
+}
+
+struct plot_data *ProfileWidget2::getEntryFromPos(QPointF pos)
+{
+ // find the time stamp corresponding to the mouse position
+ int seconds = timeAxis->valueAt(pos);
+ struct plot_data *entry = NULL;
+
+ for (int i = 0; i < plotInfo.nr; i++) {
+ entry = plotInfo.entry + i;
+ if (entry->sec >= seconds)
+ break;
+ }
+ return entry;
+}
+
+void ProfileWidget2::setReplot(bool state)
+{
+ replotEnabled = state;
+}
+
+void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
+{
+ if (currentState == ADD || currentState == PLAN) {
+ QGraphicsView::contextMenuEvent(event);
+ return;
+ }
+ QMenu m;
+ bool isDCName = false;
+ if (selected_dive == -1)
+ return;
+ // figure out if we are ontop of the dive computer name in the profile
+ QGraphicsItem *sceneItem = itemAt(mapFromGlobal(event->globalPos()));
+ if (sceneItem) {
+ QGraphicsItem *parentItem = sceneItem;
+ while (parentItem) {
+ if (parentItem->data(SUBSURFACE_OBJ_DATA) == SUBSURFACE_OBJ_DC_TEXT) {
+ isDCName = true;
+ break;
+ }
+ parentItem = parentItem->parentItem();
+ }
+ if (isDCName) {
+ if (dc_number == 0 && count_divecomputers() == 1)
+ // nothing to do, can't delete or reorder
+ return;
+ // create menu to show when right clicking on dive computer name
+ if (dc_number > 0)
+ m.addAction(tr("Make first divecomputer"), this, SLOT(makeFirstDC()));
+ if (count_divecomputers() > 1)
+ m.addAction(tr("Delete this divecomputer"), this, SLOT(deleteCurrentDC()));
+ m.exec(event->globalPos());
+ // don't show the regular profile context menu
+ return;
+ }
+ }
+ // create the profile context menu
+ QPointF scenePos = mapToScene(event->pos());
+ struct plot_data *entry = getEntryFromPos(scenePos);
+ GasSelectionModel *model = GasSelectionModel::instance();
+ model->repopulate();
+ int rowCount = model->rowCount();
+ if (rowCount > 1) {
+ // if we have more than one gas, offer to switch to another one
+ QMenu *gasChange = m.addMenu(tr("Add gas change"));
+ for (int i = 0; i < rowCount; i++) {
+ QAction *action = new QAction(&m);
+ action->setText(model->data(model->index(i, 0), Qt::DisplayRole).toString() + QString(tr(" (Tank %1)")).arg(i + 1));
+ connect(action, SIGNAL(triggered(bool)), this, SLOT(changeGas()));
+ action->setData(event->globalPos());
+ if (i == entry->cylinderindex)
+ action->setDisabled(true);
+ gasChange->addAction(action);
+ }
+ }
+ QAction *setpointAction = m.addAction(tr("Add set-point change"), this, SLOT(addSetpointChange()));
+ setpointAction->setData(event->globalPos());
+ QAction *action = m.addAction(tr("Add bookmark"), this, SLOT(addBookmark()));
+ action->setData(event->globalPos());
+
+ if (same_string(current_dc->model, "manually added dive"))
+ QAction *editProfileAction = m.addAction(tr("Edit the profile"), MainWindow::instance(), SLOT(editCurrentDive()));
+
+ if (DiveEventItem *item = dynamic_cast<DiveEventItem *>(sceneItem)) {
+ action = new QAction(&m);
+ action->setText(tr("Remove event"));
+ action->setData(QVariant::fromValue<void *>(item)); // so we know what to remove.
+ connect(action, SIGNAL(triggered(bool)), this, SLOT(removeEvent()));
+ m.addAction(action);
+ action = new QAction(&m);
+ action->setText(tr("Hide similar events"));
+ action->setData(QVariant::fromValue<void *>(item));
+ connect(action, SIGNAL(triggered(bool)), this, SLOT(hideEvents()));
+ m.addAction(action);
+ struct event *dcEvent = item->getEvent();
+ if (dcEvent->type == SAMPLE_EVENT_BOOKMARK) {
+ action = new QAction(&m);
+ action->setText(tr("Edit name"));
+ action->setData(QVariant::fromValue<void *>(item));
+ connect(action, SIGNAL(triggered(bool)), this, SLOT(editName()));
+ m.addAction(action);
+ }
+#if 0 // FIXME::: FINISH OR DISABLE
+ // this shows how to figure out if we should ask the user if they want adjust interpolated pressures
+ // at either side of a gas change
+ if (dcEvent->type == SAMPLE_EVENT_GASCHANGE || dcEvent->type == SAMPLE_EVENT_GASCHANGE2) {
+ qDebug() << "figure out if there are interpolated pressures";
+ struct plot_data *gasChangeEntry = entry;
+ struct plot_data *newGasEntry;
+ while (gasChangeEntry > plotInfo.entry) {
+ --gasChangeEntry;
+ if (gasChangeEntry->sec <= dcEvent->time.seconds)
+ break;
+ }
+ qDebug() << "at gas change at" << gasChangeEntry->sec << ": sensor pressure" << gasChangeEntry->pressure[0] << "interpolated" << gasChangeEntry->pressure[1];
+ // now gasChangeEntry points at the gas change, that entry has the final pressure of
+ // the old tank, the next entry has the starting pressure of the next tank
+ if (gasChangeEntry + 1 <= plotInfo.entry + plotInfo.nr) {
+ newGasEntry = gasChangeEntry + 1;
+ qDebug() << "after gas change at " << newGasEntry->sec << ": sensor pressure" << newGasEntry->pressure[0] << "interpolated" << newGasEntry->pressure[1];
+ if (SENSOR_PRESSURE(gasChangeEntry) == 0 || displayed_dive.cylinder[gasChangeEntry->cylinderindex].sample_start.mbar == 0) {
+ // if we have no sensorpressure or if we have no pressure from samples we can assume that
+ // we only have interpolated pressure (the pressure in the entry may be stored in the sensor
+ // pressure field if this is the first or last entry for this tank... see details in gaspressures.c
+ pressure_t pressure;
+ pressure.mbar = INTERPOLATED_PRESSURE(gasChangeEntry) ? : SENSOR_PRESSURE(gasChangeEntry);
+ QAction *adjustOldPressure = m.addAction(tr("Adjust pressure of tank %1 (currently interpolated as %2)")
+ .arg(gasChangeEntry->cylinderindex + 1).arg(get_pressure_string(pressure)));
+ }
+ if (SENSOR_PRESSURE(newGasEntry) == 0 || displayed_dive.cylinder[newGasEntry->cylinderindex].sample_start.mbar == 0) {
+ // we only have interpolated press -- see commend above
+ pressure_t pressure;
+ pressure.mbar = INTERPOLATED_PRESSURE(newGasEntry) ? : SENSOR_PRESSURE(newGasEntry);
+ QAction *adjustOldPressure = m.addAction(tr("Adjust pressure of tank %1 (currently interpolated as %2)")
+ .arg(newGasEntry->cylinderindex + 1).arg(get_pressure_string(pressure)));
+ }
+ }
+ }
+#endif
+ }
+ bool some_hidden = false;
+ for (int i = 0; i < evn_used; i++) {
+ if (ev_namelist[i].plot_ev == false) {
+ some_hidden = true;
+ break;
+ }
+ }
+ if (some_hidden) {
+ action = m.addAction(tr("Unhide all events"), this, SLOT(unhideEvents()));
+ action->setData(event->globalPos());
+ }
+ m.exec(event->globalPos());
+}
+
+void ProfileWidget2::deleteCurrentDC()
+{
+ delete_current_divecomputer();
+ mark_divelist_changed(true);
+ // we need to force it since it's likely the same dive and same dc_number - but that's a different dive computer now
+ MainWindow::instance()->graphics()->plotDive(0, true);
+ MainWindow::instance()->refreshDisplay();
+}
+
+void ProfileWidget2::makeFirstDC()
+{
+ make_first_dc();
+ mark_divelist_changed(true);
+ // this is now the first DC, so we need to redraw the profile and refresh the dive list
+ // (and no, it's not just enough to rewrite the text - the first DC is special so values in the
+ // dive list may change).
+ // As a side benefit, this returns focus to the dive list.
+ dc_number = 0;
+ MainWindow::instance()->refreshDisplay();
+}
+
+void ProfileWidget2::hideEvents()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ DiveEventItem *item = static_cast<DiveEventItem *>(action->data().value<void *>());
+ struct event *event = item->getEvent();
+
+ if (QMessageBox::question(MainWindow::instance(),
+ TITLE_OR_TEXT(tr("Hide events"), tr("Hide all %1 events?").arg(event->name)),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
+ if (!same_string(event->name, "")) {
+ for (int i = 0; i < evn_used; i++) {
+ if (same_string(event->name, ev_namelist[i].ev_name)) {
+ ev_namelist[i].plot_ev = false;
+ break;
+ }
+ }
+ Q_FOREACH (DiveEventItem *evItem, eventItems) {
+ if (same_string(evItem->getEvent()->name, event->name))
+ evItem->hide();
+ }
+ } else {
+ item->hide();
+ }
+ }
+}
+
+void ProfileWidget2::unhideEvents()
+{
+ for (int i = 0; i < evn_used; i++) {
+ ev_namelist[i].plot_ev = true;
+ }
+ Q_FOREACH (DiveEventItem *item, eventItems)
+ item->show();
+}
+
+void ProfileWidget2::removeEvent()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ DiveEventItem *item = static_cast<DiveEventItem *>(action->data().value<void *>());
+ struct event *event = item->getEvent();
+
+ if (QMessageBox::question(MainWindow::instance(), TITLE_OR_TEXT(
+ tr("Remove the selected event?"),
+ tr("%1 @ %2:%3").arg(event->name).arg(event->time.seconds / 60).arg(event->time.seconds % 60, 2, 10, QChar('0'))),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
+ remove_event(event);
+ mark_divelist_changed(true);
+ replot();
+ }
+}
+
+void ProfileWidget2::addBookmark()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint()));
+ add_event(current_dc, timeAxis->valueAt(scenePos), SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark");
+ mark_divelist_changed(true);
+ replot();
+}
+
+void ProfileWidget2::addSetpointChange()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint()));
+ SetpointDialog::instance()->setpointData(current_dc, timeAxis->valueAt(scenePos));
+ SetpointDialog::instance()->show();
+}
+
+void ProfileWidget2::changeGas()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint()));
+ QString gas = action->text();
+ gas.remove(QRegExp(" \\(.*\\)"));
+
+ // backup the things on the dataModel, since we will clear that out.
+ struct gasmix gasmix;
+ qreal sec_val = timeAxis->valueAt(scenePos);
+
+ // no gas changes before the dive starts
+ unsigned int seconds = (sec_val < 0.0) ? 0 : (unsigned int)sec_val;
+
+ // if there is a gas change at this time stamp, remove it before adding the new one
+ struct event *gasChangeEvent = current_dc->events;
+ while ((gasChangeEvent = get_next_event(gasChangeEvent, "gaschange")) != NULL) {
+ if (gasChangeEvent->time.seconds == seconds) {
+ remove_event(gasChangeEvent);
+ gasChangeEvent = current_dc->events;
+ } else {
+ gasChangeEvent = gasChangeEvent->next;
+ }
+ }
+ validate_gas(gas.toUtf8().constData(), &gasmix);
+ QRegExp rx("\\(\\D*(\\d+)");
+ int tank;
+ if (rx.indexIn(action->text()) > -1) {
+ tank = rx.cap(1).toInt() - 1; // we display the tank 1 based
+ } else {
+ qDebug() << "failed to parse tank number";
+ tank = get_gasidx(&displayed_dive, &gasmix);
+ }
+ // add this both to the displayed dive and the current dive
+ add_gas_switch_event(current_dive, current_dc, seconds, tank);
+ add_gas_switch_event(&displayed_dive, get_dive_dc(&displayed_dive, dc_number), seconds, tank);
+ // this means we potentially have a new tank that is being used and needs to be shown
+ fixup_dive(&displayed_dive);
+
+ // FIXME - this no longer gets written to the dive list - so we need to enableEdition() here
+
+ MainWindow::instance()->information()->updateDiveInfo();
+ mark_divelist_changed(true);
+ replot();
+}
+
+bool ProfileWidget2::getPrintMode()
+{
+ return printMode;
+}
+
+void ProfileWidget2::setPrintMode(bool mode, bool grayscale)
+{
+ printMode = mode;
+ resetZoom();
+
+ // set printMode for axes
+ profileYAxis->setPrintMode(mode);
+ gasYAxis->setPrintMode(mode);
+ temperatureAxis->setPrintMode(mode);
+ timeAxis->setPrintMode(mode);
+ cylinderPressureAxis->setPrintMode(mode);
+ heartBeatAxis->setPrintMode(mode);
+ percentageAxis->setPrintMode(mode);
+
+ isGrayscale = mode ? grayscale : false;
+ mouseFollowerHorizontal->setVisible(!mode);
+ mouseFollowerVertical->setVisible(!mode);
+}
+
+void ProfileWidget2::setFontPrintScale(double scale)
+{
+ fontPrintScale = scale;
+ emit fontPrintScaleChanged(scale);
+}
+
+double ProfileWidget2::getFontPrintScale()
+{
+ if (printMode)
+ return fontPrintScale;
+ else
+ return 1.0;
+}
+
+void ProfileWidget2::editName()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ DiveEventItem *item = static_cast<DiveEventItem *>(action->data().value<void *>());
+ struct event *event = item->getEvent();
+ bool ok;
+ QString newName = QInputDialog::getText(MainWindow::instance(), tr("Edit name of bookmark"),
+ tr("Custom name:"), QLineEdit::Normal,
+ event->name, &ok);
+ if (ok && !newName.isEmpty()) {
+ if (newName.length() > 22) { //longer names will display as garbage.
+ QMessageBox lengthWarning;
+ lengthWarning.setText(tr("Name is too long!"));
+ lengthWarning.exec();
+ return;
+ }
+ // order is important! first update the current dive (by matching the unchanged event),
+ // then update the displayed dive (as event is part of the events on displayed dive
+ // and will be freed as part of changing the name!
+ update_event_name(current_dive, event, newName.toUtf8().data());
+ update_event_name(&displayed_dive, event, newName.toUtf8().data());
+ mark_divelist_changed(true);
+ replot();
+ }
+}
+
+void ProfileWidget2::disconnectTemporaryConnections()
+{
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ disconnect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot()));
+ disconnect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot()));
+
+ disconnect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(pointInserted(const QModelIndex &, int, int)));
+ disconnect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(pointsRemoved(const QModelIndex &, int, int)));
+
+ Q_FOREACH (QAction *action, actionsForKeys.values()) {
+ action->setShortcut(QKeySequence());
+ action->setShortcutContext(Qt::WidgetShortcut);
+ }
+}
+
+void ProfileWidget2::pointInserted(const QModelIndex &parent, int start, int end)
+{
+ DiveHandler *item = new DiveHandler();
+ scene()->addItem(item);
+ handles << item;
+
+ connect(item, SIGNAL(moved()), this, SLOT(recreatePlannedDive()));
+ connect(item, SIGNAL(clicked()), this, SLOT(divePlannerHandlerClicked()));
+ connect(item, SIGNAL(released()), this, SLOT(divePlannerHandlerReleased()));
+ QGraphicsSimpleTextItem *gasChooseBtn = new QGraphicsSimpleTextItem();
+ scene()->addItem(gasChooseBtn);
+ gasChooseBtn->setZValue(10);
+ gasChooseBtn->setFlag(QGraphicsItem::ItemIgnoresTransformations);
+ gases << gasChooseBtn;
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ if (plannerModel->recalcQ())
+ replot();
+}
+
+void ProfileWidget2::pointsRemoved(const QModelIndex &, int start, int end)
+{ // start and end are inclusive.
+ int num = (end - start) + 1;
+ for (int i = num; i != 0; i--) {
+ delete handles.back();
+ handles.pop_back();
+ delete gases.back();
+ gases.pop_back();
+ }
+ scene()->clearSelection();
+ replot();
+}
+
+void ProfileWidget2::repositionDiveHandlers()
+{
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ // Re-position the user generated dive handlers
+ struct gasmix mix, lastmix;
+ 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.at(i);
+ h->setVisible(datapoint.entered);
+ h->setPos(timeAxis->posAtValue(datapoint.time), profileYAxis->posAtValue(datapoint.depth));
+ 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(timeAxis->posAtValue(datapoint.depth / prefs.descrate),
+ profileYAxis->posAtValue(datapoint.depth));
+ else
+ // place the text on the straight line from the origin to the first position
+ p1 = QPointF(timeAxis->posAtValue(0), 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);
+ gases[i]->setText(get_divepoint_gas_string(datapoint));
+ gases[i]->setVisible(datapoint.entered &&
+ (i == 0 || gases[i]->text() != gases[i-1]->text()));
+ }
+}
+
+int ProfileWidget2::fixHandlerIndex(DiveHandler *activeHandler)
+{
+ int index = handles.indexOf(activeHandler);
+ if (index > 0 && index < handles.count() - 1) {
+ DiveHandler *before = handles[index - 1];
+ if (before->pos().x() > activeHandler->pos().x()) {
+ handles.swap(index, index - 1);
+ return index - 1;
+ }
+ DiveHandler *after = handles[index + 1];
+ if (after->pos().x() < activeHandler->pos().x()) {
+ handles.swap(index, index + 1);
+ return index + 1;
+ }
+ }
+ return index;
+}
+
+void ProfileWidget2::recreatePlannedDive()
+{
+ DiveHandler *activeHandler = qobject_cast<DiveHandler *>(sender());
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ int index = fixHandlerIndex(activeHandler);
+ int mintime = 0, maxtime = (timeAxis->maximum() + 10) * 60;
+ if (index > 0)
+ mintime = plannerModel->at(index - 1).time;
+ if (index < plannerModel->size() - 1)
+ maxtime = plannerModel->at(index + 1).time;
+
+ int minutes = rint(timeAxis->valueAt(activeHandler->pos()) / 60);
+ if (minutes * 60 <= mintime || minutes * 60 >= maxtime)
+ return;
+
+ divedatapoint data = plannerModel->at(index);
+ data.depth = rint(profileYAxis->valueAt(activeHandler->pos()) / M_OR_FT(1, 1)) * M_OR_FT(1, 1);
+ data.time = rint(timeAxis->valueAt(activeHandler->pos()));
+
+ plannerModel->editStop(index, data);
+}
+
+void ProfileWidget2::keyDownAction()
+{
+ if (currentState != ADD && currentState != PLAN)
+ return;
+
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) {
+ if (DiveHandler *handler = qgraphicsitem_cast<DiveHandler *>(i)) {
+ int row = handles.indexOf(handler);
+ divedatapoint dp = plannerModel->at(row);
+ if (dp.depth >= profileYAxis->maximum())
+ continue;
+
+ dp.depth += M_OR_FT(1, 5);
+ plannerModel->editStop(row, dp);
+ }
+ }
+}
+
+void ProfileWidget2::keyUpAction()
+{
+ if (currentState != ADD && currentState != PLAN)
+ return;
+
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) {
+ if (DiveHandler *handler = qgraphicsitem_cast<DiveHandler *>(i)) {
+ int row = handles.indexOf(handler);
+ divedatapoint dp = plannerModel->at(row);
+
+ if (dp.depth <= 0)
+ continue;
+
+ dp.depth -= M_OR_FT(1, 5);
+ plannerModel->editStop(row, dp);
+ }
+ }
+}
+
+void ProfileWidget2::keyLeftAction()
+{
+ if (currentState != ADD && currentState != PLAN)
+ return;
+
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) {
+ if (DiveHandler *handler = qgraphicsitem_cast<DiveHandler *>(i)) {
+ int row = handles.indexOf(handler);
+ divedatapoint dp = plannerModel->at(row);
+
+ if (dp.time / 60 <= 0)
+ continue;
+
+ // don't overlap positions.
+ // maybe this is a good place for a 'goto'?
+ double xpos = timeAxis->posAtValue((dp.time - 60) / 60);
+ bool nextStep = false;
+ Q_FOREACH (DiveHandler *h, handles) {
+ if (IS_FP_SAME(h->pos().x(), xpos)) {
+ nextStep = true;
+ break;
+ }
+ }
+ if (nextStep)
+ continue;
+
+ dp.time -= 60;
+ plannerModel->editStop(row, dp);
+ }
+ }
+}
+
+void ProfileWidget2::keyRightAction()
+{
+ if (currentState != ADD && currentState != PLAN)
+ return;
+
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) {
+ if (DiveHandler *handler = qgraphicsitem_cast<DiveHandler *>(i)) {
+ int row = handles.indexOf(handler);
+ divedatapoint dp = plannerModel->at(row);
+ if (dp.time / 60.0 >= timeAxis->maximum())
+ continue;
+
+ // don't overlap positions.
+ // maybe this is a good place for a 'goto'?
+ double xpos = timeAxis->posAtValue((dp.time + 60) / 60);
+ bool nextStep = false;
+ Q_FOREACH (DiveHandler *h, handles) {
+ if (IS_FP_SAME(h->pos().x(), xpos)) {
+ nextStep = true;
+ break;
+ }
+ }
+ if (nextStep)
+ continue;
+
+ dp.time += 60;
+ plannerModel->editStop(row, dp);
+ }
+ }
+}
+
+void ProfileWidget2::keyDeleteAction()
+{
+ if (currentState != ADD && currentState != PLAN)
+ return;
+
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ int selCount = scene()->selectedItems().count();
+ if (selCount) {
+ QVector<int> selectedIndexes;
+ Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) {
+ if (DiveHandler *handler = qgraphicsitem_cast<DiveHandler *>(i)) {
+ selectedIndexes.push_back(handles.indexOf(handler));
+ handler->hide();
+ }
+ }
+ plannerModel->removeSelectedPoints(selectedIndexes);
+ }
+}
+
+void ProfileWidget2::keyEscAction()
+{
+ if (currentState != ADD && currentState != PLAN)
+ return;
+
+ if (scene()->selectedItems().count()) {
+ scene()->clearSelection();
+ return;
+ }
+
+ DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
+ if (plannerModel->isPlanner())
+ plannerModel->cancelPlan();
+}
+
+void ProfileWidget2::plotPictures()
+{
+ Q_FOREACH (DivePictureItem *item, pictures) {
+ item->hide();
+ item->deleteLater();
+ }
+ pictures.clear();
+
+ if (printMode)
+ return;
+
+ double x, y, lastX = -1.0, lastY = -1.0;
+ DivePictureModel *m = DivePictureModel::instance();
+ for (int i = 0; i < m->rowCount(); i++) {
+ int offsetSeconds = m->index(i, 1).data(Qt::UserRole).value<int>();
+ // it's a correct picture, but doesn't have a timestamp: only show on the widget near the
+ // information area.
+ if (!offsetSeconds)
+ continue;
+ DivePictureItem *item = new DivePictureItem();
+ item->setPixmap(m->index(i, 0).data(Qt::DecorationRole).value<QPixmap>());
+ item->setFileUrl(m->index(i, 1).data().toString());
+ // let's put the picture at the correct time, but at a fixed "depth" on the profile
+ // not sure this is ideal, but it seems to look right.
+ x = timeAxis->posAtValue(offsetSeconds);
+ if (i == 0)
+ y = 10;
+ else if (fabs(x - lastX) < 4)
+ y = lastY + 3;
+ else
+ y = 10;
+ lastX = x;
+ lastY = y;
+ item->setPos(x, y);
+ scene()->addItem(item);
+ pictures.push_back(item);
+ }
+}