summaryrefslogtreecommitdiffstats
path: root/profile-widget
diff options
context:
space:
mode:
authorGravatar Dirk Hohndel <dirk@hohndel.org>2015-11-02 19:54:34 -0800
committerGravatar Dirk Hohndel <dirk@hohndel.org>2015-11-02 19:54:34 -0800
commit8ea7f404574c2ee571d2dde6bb6be3791e962150 (patch)
tree6a050178bfc71bf10558968f2a3bc0a12d8c525f /profile-widget
parentb273c1b0ca7bfe933e7c83742f1610f6bbe3f4d3 (diff)
parentdf7818a9b8495285b4d9812e5d6d50d6f9c08813 (diff)
downloadsubsurface-8ea7f404574c2ee571d2dde6bb6be3791e962150.tar.gz
Merge branch 'cmakeAndPreferences'
Diffstat (limited to 'profile-widget')
-rw-r--r--profile-widget/CMakeLists.txt19
-rw-r--r--profile-widget/animationfunctions.cpp75
-rw-r--r--profile-widget/animationfunctions.h18
-rw-r--r--profile-widget/divecartesianaxis.cpp459
-rw-r--r--profile-widget/divecartesianaxis.h122
-rw-r--r--profile-widget/diveeventitem.cpp172
-rw-r--r--profile-widget/diveeventitem.h34
-rw-r--r--profile-widget/divelineitem.cpp5
-rw-r--r--profile-widget/divelineitem.h15
-rw-r--r--profile-widget/divepixmapitem.cpp130
-rw-r--r--profile-widget/divepixmapitem.h57
-rw-r--r--profile-widget/diveprofileitem.cpp979
-rw-r--r--profile-widget/diveprofileitem.h225
-rw-r--r--profile-widget/diverectitem.cpp5
-rw-r--r--profile-widget/diverectitem.h17
-rw-r--r--profile-widget/divetextitem.cpp113
-rw-r--r--profile-widget/divetextitem.h38
-rw-r--r--profile-widget/divetooltipitem.cpp285
-rw-r--r--profile-widget/divetooltipitem.h67
-rw-r--r--profile-widget/profilewidget2.cpp1842
-rw-r--r--profile-widget/profilewidget2.h211
-rw-r--r--profile-widget/ruleritem.cpp179
-rw-r--r--profile-widget/ruleritem.h59
-rw-r--r--profile-widget/tankitem.cpp120
-rw-r--r--profile-widget/tankitem.h39
25 files changed, 5285 insertions, 0 deletions
diff --git a/profile-widget/CMakeLists.txt b/profile-widget/CMakeLists.txt
new file mode 100644
index 000000000..f0a1d8439
--- /dev/null
+++ b/profile-widget/CMakeLists.txt
@@ -0,0 +1,19 @@
+# the profile widget
+set(SUBSURFACE_PROFILE_LIB_SRCS
+ profilewidget2.cpp
+ diverectitem.cpp
+ divepixmapitem.cpp
+ divelineitem.cpp
+ divetextitem.cpp
+ animationfunctions.cpp
+ divecartesianaxis.cpp
+ diveprofileitem.cpp
+ diveeventitem.cpp
+ divetooltipitem.cpp
+ ruleritem.cpp
+ tankitem.cpp
+)
+source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS})
+
+add_library(subsurface_profile STATIC ${SUBSURFACE_PROFILE_LIB_SRCS})
+target_link_libraries(subsurface_profile ${QT_LIBRARIES}) \ No newline at end of file
diff --git a/profile-widget/animationfunctions.cpp b/profile-widget/animationfunctions.cpp
new file mode 100644
index 000000000..a19d50c9d
--- /dev/null
+++ b/profile-widget/animationfunctions.cpp
@@ -0,0 +1,75 @@
+#include "animationfunctions.h"
+#include "pref.h"
+#include <QPropertyAnimation>
+
+namespace Animations {
+
+ void hide(QObject *obj)
+ {
+ if (prefs.animation_speed != 0) {
+ QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity");
+ animation->setStartValue(1);
+ animation->setEndValue(0);
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else {
+ obj->setProperty("opacity", 0);
+ }
+ }
+
+ void show(QObject *obj)
+ {
+ if (prefs.animation_speed != 0) {
+ QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity");
+ animation->setStartValue(0);
+ animation->setEndValue(1);
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else {
+ obj->setProperty("opacity", 1);
+ }
+ }
+
+ void animDelete(QObject *obj)
+ {
+ if (prefs.animation_speed != 0) {
+ QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity");
+ obj->connect(animation, SIGNAL(finished()), SLOT(deleteLater()));
+ animation->setStartValue(1);
+ animation->setEndValue(0);
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else {
+ obj->setProperty("opacity", 0);
+ }
+ }
+
+ void moveTo(QObject *obj, qreal x, qreal y)
+ {
+ if (prefs.animation_speed != 0) {
+ QPropertyAnimation *animation = new QPropertyAnimation(obj, "pos");
+ animation->setDuration(prefs.animation_speed);
+ animation->setStartValue(obj->property("pos").toPointF());
+ animation->setEndValue(QPointF(x, y));
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else {
+ obj->setProperty("pos", QPointF(x, y));
+ }
+ }
+
+ void scaleTo(QObject *obj, qreal scale)
+ {
+ if (prefs.animation_speed != 0) {
+ QPropertyAnimation *animation = new QPropertyAnimation(obj, "scale");
+ animation->setDuration(prefs.animation_speed);
+ animation->setStartValue(obj->property("scale").toReal());
+ animation->setEndValue(QVariant::fromValue(scale));
+ animation->setEasingCurve(QEasingCurve::InCubic);
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else {
+ obj->setProperty("scale", QVariant::fromValue(scale));
+ }
+ }
+
+ void moveTo(QObject *obj, const QPointF &pos)
+ {
+ moveTo(obj, pos.x(), pos.y());
+ }
+}
diff --git a/profile-widget/animationfunctions.h b/profile-widget/animationfunctions.h
new file mode 100644
index 000000000..3cfcff563
--- /dev/null
+++ b/profile-widget/animationfunctions.h
@@ -0,0 +1,18 @@
+#ifndef ANIMATIONFUNCTIONS_H
+#define ANIMATIONFUNCTIONS_H
+
+#include <QtGlobal>
+#include <QPointF>
+
+class QObject;
+
+namespace Animations {
+ void hide(QObject *obj);
+ void show(QObject *obj);
+ void moveTo(QObject *obj, qreal x, qreal y);
+ void moveTo(QObject *obj, const QPointF &pos);
+ void animDelete(QObject *obj);
+ void scaleTo(QObject *obj, qreal scale);
+}
+
+#endif // ANIMATIONFUNCTIONS_H
diff --git a/profile-widget/divecartesianaxis.cpp b/profile-widget/divecartesianaxis.cpp
new file mode 100644
index 000000000..f40e1c3e5
--- /dev/null
+++ b/profile-widget/divecartesianaxis.cpp
@@ -0,0 +1,459 @@
+#include "divecartesianaxis.h"
+#include "divetextitem.h"
+#include "helpers.h"
+#include "preferences/preferencesdialog.h"
+#include "diveplotdatamodel.h"
+#include "animationfunctions.h"
+#include "mainwindow.h"
+#include "divelineitem.h"
+#include "profilewidget2.h"
+
+QPen DiveCartesianAxis::gridPen()
+{
+ QPen pen;
+ pen.setColor(getColor(TIME_GRID));
+ /* cosmetic width() == 0 for lines in printMode
+ * having setCosmetic(true) and width() > 0 does not work when
+ * printing on OSX and Linux */
+ pen.setWidth(DiveCartesianAxis::printMode ? 0 : 2);
+ pen.setCosmetic(true);
+ return pen;
+}
+
+double DiveCartesianAxis::tickInterval() const
+{
+ return interval;
+}
+
+double DiveCartesianAxis::tickSize() const
+{
+ return tick_size;
+}
+
+void DiveCartesianAxis::setFontLabelScale(qreal scale)
+{
+ labelScale = scale;
+ changed = true;
+}
+
+void DiveCartesianAxis::setPrintMode(bool mode)
+{
+ printMode = mode;
+ // update the QPen of all lines depending on printMode
+ QPen newPen = gridPen();
+ QColor oldColor = pen().brush().color();
+ newPen.setBrush(oldColor);
+ setPen(newPen);
+ Q_FOREACH (DiveLineItem *item, lines)
+ item->setPen(pen());
+}
+
+void DiveCartesianAxis::setMaximum(double maximum)
+{
+ if (IS_FP_SAME(max, maximum))
+ return;
+ max = maximum;
+ changed = true;
+ emit maxChanged();
+}
+
+void DiveCartesianAxis::setMinimum(double minimum)
+{
+ if (IS_FP_SAME(min, minimum))
+ return;
+ min = minimum;
+ changed = true;
+}
+
+void DiveCartesianAxis::setTextColor(const QColor &color)
+{
+ textColor = color;
+}
+
+DiveCartesianAxis::DiveCartesianAxis() : QObject(),
+ QGraphicsLineItem(),
+ printMode(false),
+ unitSystem(0),
+ orientation(LeftToRight),
+ min(0),
+ max(0),
+ interval(1),
+ tick_size(0),
+ textVisibility(true),
+ lineVisibility(true),
+ labelScale(1.0),
+ line_size(1),
+ changed(true)
+{
+ setPen(gridPen());
+}
+
+DiveCartesianAxis::~DiveCartesianAxis()
+{
+}
+
+void DiveCartesianAxis::setLineSize(qreal lineSize)
+{
+ line_size = lineSize;
+ changed = true;
+}
+
+void DiveCartesianAxis::setOrientation(Orientation o)
+{
+ orientation = o;
+ changed = true;
+}
+
+QColor DiveCartesianAxis::colorForValue(double value)
+{
+ return QColor(Qt::black);
+}
+
+void DiveCartesianAxis::setTextVisible(bool arg1)
+{
+ if (textVisibility == arg1) {
+ return;
+ }
+ textVisibility = arg1;
+ Q_FOREACH (DiveTextItem *item, labels) {
+ item->setVisible(textVisibility);
+ }
+}
+
+void DiveCartesianAxis::setLinesVisible(bool arg1)
+{
+ if (lineVisibility == arg1) {
+ return;
+ }
+ lineVisibility = arg1;
+ Q_FOREACH (DiveLineItem *item, lines) {
+ item->setVisible(lineVisibility);
+ }
+}
+
+template <typename T>
+void emptyList(QList<T *> &list, double steps)
+{
+ if (!list.isEmpty() && list.size() > steps) {
+ while (list.size() > steps) {
+ T *removedItem = list.takeLast();
+ Animations::animDelete(removedItem);
+ }
+ }
+}
+
+void DiveCartesianAxis::updateTicks(color_indice_t color)
+{
+ if (!scene() || (!changed && !MainWindow::instance()->graphics()->getPrintMode()))
+ return;
+ QLineF m = line();
+ // unused so far:
+ // QGraphicsView *view = scene()->views().first();
+ double steps = (max - min) / interval;
+ double currValueText = min;
+ double currValueLine = min;
+
+ if (steps < 1)
+ return;
+
+ emptyList(labels, steps);
+ emptyList(lines, steps);
+
+ // Move the remaining Ticks / Text to it's corerct position
+ // Regartind the possibly new values for the Axis
+ qreal begin, stepSize;
+ if (orientation == TopToBottom) {
+ begin = m.y1();
+ stepSize = (m.y2() - m.y1());
+ } else if (orientation == BottomToTop) {
+ begin = m.y2();
+ stepSize = (m.y2() - m.y1());
+ } else if (orientation == LeftToRight) {
+ begin = m.x1();
+ stepSize = (m.x2() - m.x1());
+ } else /* if (orientation == RightToLeft) */ {
+ begin = m.x2();
+ stepSize = (m.x2() - m.x1());
+ }
+ stepSize = stepSize / steps;
+
+ for (int i = 0, count = labels.size(); i < count; i++, currValueText += interval) {
+ qreal childPos = (orientation == TopToBottom || orientation == LeftToRight) ?
+ begin + i * stepSize :
+ begin - i * stepSize;
+
+ labels[i]->setText(textForValue(currValueText));
+ if (orientation == LeftToRight || orientation == RightToLeft) {
+ Animations::moveTo(labels[i],childPos, m.y1() + tick_size);
+ } else {
+ Animations::moveTo(labels[i],m.x1() - tick_size, childPos);
+ }
+ }
+
+ for (int i = 0, count = lines.size(); i < count; i++, currValueLine += interval) {
+ qreal childPos = (orientation == TopToBottom || orientation == LeftToRight) ?
+ begin + i * stepSize :
+ begin - i * stepSize;
+
+ if (orientation == LeftToRight || orientation == RightToLeft) {
+ Animations::moveTo(lines[i],childPos, m.y1());
+ } else {
+ Animations::moveTo(lines[i],m.x1(), childPos);
+ }
+ }
+
+ // Add's the rest of the needed Ticks / Text.
+ for (int i = labels.size(); i < steps; i++, currValueText += interval) {
+ qreal childPos;
+ if (orientation == TopToBottom || orientation == LeftToRight) {
+ childPos = begin + i * stepSize;
+ } else {
+ childPos = begin - i * stepSize;
+ }
+ DiveTextItem *label = new DiveTextItem(this);
+ label->setText(textForValue(currValueText));
+ label->setBrush(colorForValue(currValueText));
+ label->setScale(fontLabelScale());
+ label->setZValue(1);
+ labels.push_back(label);
+ if (orientation == RightToLeft || orientation == LeftToRight) {
+ label->setAlignment(Qt::AlignBottom | Qt::AlignHCenter);
+ label->setPos(scene()->sceneRect().width() + 10, m.y1() + tick_size); // position it outside of the scene);
+ Animations::moveTo(label,childPos, m.y1() + tick_size);
+ } else {
+ label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
+ label->setPos(m.x1() - tick_size, scene()->sceneRect().height() + 10);
+ Animations::moveTo(label,m.x1() - tick_size, childPos);
+ }
+ }
+
+ // Add's the rest of the needed Ticks / Text.
+ for (int i = lines.size(); i < steps; i++, currValueText += interval) {
+ qreal childPos;
+ if (orientation == TopToBottom || orientation == LeftToRight) {
+ childPos = begin + i * stepSize;
+ } else {
+ childPos = begin - i * stepSize;
+ }
+ DiveLineItem *line = new DiveLineItem(this);
+ QPen pen = gridPen();
+ pen.setBrush(getColor(color));
+ line->setPen(pen);
+ line->setZValue(0);
+ lines.push_back(line);
+ if (orientation == RightToLeft || orientation == LeftToRight) {
+ line->setLine(0, -line_size, 0, 0);
+ line->setPos(scene()->sceneRect().width() + 10, m.y1()); // position it outside of the scene);
+ Animations::moveTo(line,childPos, m.y1());
+ } else {
+ QPointF p1 = mapFromScene(3, 0);
+ QPointF p2 = mapFromScene(line_size, 0);
+ line->setLine(p1.x(), 0, p2.x(), 0);
+ line->setPos(m.x1(), scene()->sceneRect().height() + 10);
+ Animations::moveTo(line,m.x1(), childPos);
+ }
+ }
+
+ Q_FOREACH (DiveTextItem *item, labels)
+ item->setVisible(textVisibility);
+ Q_FOREACH (DiveLineItem *item, lines)
+ item->setVisible(lineVisibility);
+ changed = false;
+}
+
+void DiveCartesianAxis::setLine(const QLineF &line)
+{
+ QGraphicsLineItem::setLine(line);
+ changed = true;
+}
+
+void DiveCartesianAxis::animateChangeLine(const QLineF &newLine)
+{
+ setLine(newLine);
+ updateTicks();
+ sizeChanged();
+}
+
+QString DiveCartesianAxis::textForValue(double value)
+{
+ return QString::number(value);
+}
+
+void DiveCartesianAxis::setTickSize(qreal size)
+{
+ tick_size = size;
+}
+
+void DiveCartesianAxis::setTickInterval(double i)
+{
+ interval = i;
+}
+
+qreal DiveCartesianAxis::valueAt(const QPointF &p) const
+{
+ QLineF m = line();
+ QPointF relativePosition = p;
+ relativePosition -= pos(); // normalize p based on the axis' offset on screen
+
+ double retValue = (orientation == LeftToRight || orientation == RightToLeft) ?
+ max * (relativePosition.x() - m.x1()) / (m.x2() - m.x1()) :
+ max * (relativePosition.y() - m.y1()) / (m.y2() - m.y1());
+ return retValue;
+}
+
+qreal DiveCartesianAxis::posAtValue(qreal value)
+{
+ QLineF m = line();
+ QPointF p = pos();
+
+ double size = max - min;
+ // unused for now:
+ // double distanceFromOrigin = value - min;
+ double percent = IS_FP_SAME(min, max) ? 0.0 : (value - min) / size;
+
+
+ double realSize = orientation == LeftToRight || orientation == RightToLeft ?
+ m.x2() - m.x1() :
+ m.y2() - m.y1();
+
+ // Inverted axis, just invert the percentage.
+ if (orientation == RightToLeft || orientation == BottomToTop)
+ percent = 1 - percent;
+
+ double retValue = realSize * percent;
+ double adjusted =
+ orientation == LeftToRight ? retValue + m.x1() + p.x() :
+ orientation == RightToLeft ? retValue + m.x1() + p.x() :
+ orientation == TopToBottom ? retValue + m.y1() + p.y() :
+ /* entation == BottomToTop */ retValue + m.y1() + p.y();
+ return adjusted;
+}
+
+qreal DiveCartesianAxis::percentAt(const QPointF &p)
+{
+ qreal value = valueAt(p);
+ double size = max - min;
+ double percent = value / size;
+ return percent;
+}
+
+double DiveCartesianAxis::maximum() const
+{
+ return max;
+}
+
+double DiveCartesianAxis::minimum() const
+{
+ return min;
+}
+
+double DiveCartesianAxis::fontLabelScale() const
+{
+ return labelScale;
+}
+
+void DiveCartesianAxis::setColor(const QColor &color)
+{
+ QPen defaultPen = gridPen();
+ defaultPen.setColor(color);
+ defaultPen.setJoinStyle(Qt::RoundJoin);
+ defaultPen.setCapStyle(Qt::RoundCap);
+ setPen(defaultPen);
+}
+
+QString DepthAxis::textForValue(double value)
+{
+ if (value == 0)
+ return QString();
+ return get_depth_string(value, false, false);
+}
+
+QColor DepthAxis::colorForValue(double value)
+{
+ Q_UNUSED(value);
+ return QColor(Qt::red);
+}
+
+DepthAxis::DepthAxis()
+{
+ connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged()));
+ changed = true;
+ settingsChanged();
+}
+
+void DepthAxis::settingsChanged()
+{
+ static int unitSystem = prefs.units.length;
+ if ( unitSystem == prefs.units.length )
+ return;
+ changed = true;
+ updateTicks();
+ unitSystem = prefs.units.length;
+}
+
+QColor TimeAxis::colorForValue(double value)
+{
+ Q_UNUSED(value);
+ return QColor(Qt::blue);
+}
+
+QString TimeAxis::textForValue(double value)
+{
+ int nr = value / 60;
+ if (maximum() < 600)
+ return QString("%1:%2").arg(nr).arg((int)value % 60, 2, 10, QChar('0'));
+ return QString::number(nr);
+}
+
+void TimeAxis::updateTicks()
+{
+ DiveCartesianAxis::updateTicks();
+ if (maximum() > 600) {
+ for (int i = 0; i < labels.count(); i++) {
+ labels[i]->setVisible(i % 2);
+ }
+ }
+}
+
+QString TemperatureAxis::textForValue(double value)
+{
+ return QString::number(mkelvin_to_C((int)value));
+}
+
+PartialGasPressureAxis::PartialGasPressureAxis() :
+ DiveCartesianAxis(),
+ model(NULL)
+{
+ connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged()));
+}
+
+void PartialGasPressureAxis::setModel(DivePlotDataModel *m)
+{
+ model = m;
+ connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(settingsChanged()));
+ settingsChanged();
+}
+
+void PartialGasPressureAxis::settingsChanged()
+{
+ bool showPhe = prefs.pp_graphs.phe;
+ bool showPn2 = prefs.pp_graphs.pn2;
+ bool showPo2 = prefs.pp_graphs.po2;
+ setVisible(showPhe || showPn2 || showPo2);
+ if (!model->rowCount())
+ return;
+
+ double max = showPhe ? model->pheMax() : -1;
+ if (showPn2 && model->pn2Max() > max)
+ max = model->pn2Max();
+ if (showPo2 && model->po2Max() > max)
+ max = model->po2Max();
+
+ qreal pp = floor(max * 10.0) / 10.0 + 0.2;
+ if (IS_FP_SAME(maximum(), pp))
+ return;
+
+ setMaximum(pp);
+ setTickInterval(pp > 4 ? 0.5 : 0.25);
+ updateTicks();
+}
diff --git a/profile-widget/divecartesianaxis.h b/profile-widget/divecartesianaxis.h
new file mode 100644
index 000000000..cc7d0bcf7
--- /dev/null
+++ b/profile-widget/divecartesianaxis.h
@@ -0,0 +1,122 @@
+#ifndef DIVECARTESIANAXIS_H
+#define DIVECARTESIANAXIS_H
+
+#include <QObject>
+#include <QGraphicsLineItem>
+#include "subsurface-core/color.h"
+
+class QPropertyAnimation;
+class DiveTextItem;
+class DiveLineItem;
+class DivePlotDataModel;
+
+class DiveCartesianAxis : public QObject, public QGraphicsLineItem {
+ Q_OBJECT
+ Q_PROPERTY(QLineF line WRITE setLine READ line)
+ Q_PROPERTY(QPointF pos WRITE setPos READ pos)
+ Q_PROPERTY(qreal x WRITE setX READ x)
+ Q_PROPERTY(qreal y WRITE setY READ y)
+private:
+ bool printMode;
+ QPen gridPen();
+public:
+ enum Orientation {
+ TopToBottom,
+ BottomToTop,
+ LeftToRight,
+ RightToLeft
+ };
+ DiveCartesianAxis();
+ virtual ~DiveCartesianAxis();
+ void setPrintMode(bool mode);
+ void setMinimum(double minimum);
+ void setMaximum(double maximum);
+ void setTickInterval(double interval);
+ void setOrientation(Orientation orientation);
+ void setTickSize(qreal size);
+ void setFontLabelScale(qreal scale);
+ double minimum() const;
+ double maximum() const;
+ double tickInterval() const;
+ double tickSize() const;
+ double fontLabelScale() const;
+ qreal valueAt(const QPointF &p) const;
+ qreal percentAt(const QPointF &p);
+ qreal posAtValue(qreal value);
+ void setColor(const QColor &color);
+ void setTextColor(const QColor &color);
+ void animateChangeLine(const QLineF &newLine);
+ void setTextVisible(bool arg1);
+ void setLinesVisible(bool arg1);
+ void setLineSize(qreal lineSize);
+ void setLine(const QLineF& line);
+ int unitSystem;
+public
+slots:
+ virtual void updateTicks(color_indice_t color = TIME_GRID);
+
+signals:
+ void sizeChanged();
+ void maxChanged();
+
+protected:
+ virtual QString textForValue(double value);
+ virtual QColor colorForValue(double value);
+ Orientation orientation;
+ QList<DiveTextItem *> labels;
+ QList<DiveLineItem *> lines;
+ double min;
+ double max;
+ double interval;
+ double tick_size;
+ QColor textColor;
+ bool textVisibility;
+ bool lineVisibility;
+ double labelScale;
+ qreal line_size;
+ bool changed;
+};
+
+class DepthAxis : public DiveCartesianAxis {
+ Q_OBJECT
+public:
+ DepthAxis();
+
+protected:
+ QString textForValue(double value);
+ QColor colorForValue(double value);
+private
+slots:
+ void settingsChanged();
+};
+
+class TimeAxis : public DiveCartesianAxis {
+ Q_OBJECT
+public:
+ virtual void updateTicks();
+
+protected:
+ QString textForValue(double value);
+ QColor colorForValue(double value);
+};
+
+class TemperatureAxis : public DiveCartesianAxis {
+ Q_OBJECT
+protected:
+ QString textForValue(double value);
+};
+
+class PartialGasPressureAxis : public DiveCartesianAxis {
+ Q_OBJECT
+public:
+ PartialGasPressureAxis();
+ void setModel(DivePlotDataModel *model);
+public
+slots:
+ void settingsChanged();
+
+private:
+ DivePlotDataModel *model;
+};
+
+#endif // DIVECARTESIANAXIS_H
diff --git a/profile-widget/diveeventitem.cpp b/profile-widget/diveeventitem.cpp
new file mode 100644
index 000000000..0bbc84267
--- /dev/null
+++ b/profile-widget/diveeventitem.cpp
@@ -0,0 +1,172 @@
+#include "diveeventitem.h"
+#include "diveplotdatamodel.h"
+#include "divecartesianaxis.h"
+#include "animationfunctions.h"
+#include "libdivecomputer.h"
+#include "profile.h"
+#include "gettextfromc.h"
+#include "metrics.h"
+
+extern struct ev_select *ev_namelist;
+extern int evn_used;
+
+DiveEventItem::DiveEventItem(QObject *parent) : DivePixmapItem(parent),
+ vAxis(NULL),
+ hAxis(NULL),
+ dataModel(NULL),
+ internalEvent(NULL)
+{
+ setFlag(ItemIgnoresTransformations);
+}
+
+
+void DiveEventItem::setHorizontalAxis(DiveCartesianAxis *axis)
+{
+ hAxis = axis;
+ recalculatePos(true);
+}
+
+void DiveEventItem::setModel(DivePlotDataModel *model)
+{
+ dataModel = model;
+ recalculatePos(true);
+}
+
+void DiveEventItem::setVerticalAxis(DiveCartesianAxis *axis)
+{
+ vAxis = axis;
+ recalculatePos(true);
+ connect(vAxis, SIGNAL(sizeChanged()), this, SLOT(recalculatePos()));
+}
+
+struct event *DiveEventItem::getEvent()
+{
+ return internalEvent;
+}
+
+void DiveEventItem::setEvent(struct event *ev)
+{
+ if (!ev)
+ return;
+ internalEvent = ev;
+ setupPixmap();
+ setupToolTipString();
+ recalculatePos(true);
+}
+
+void DiveEventItem::setupPixmap()
+{
+ const IconMetrics& metrics = defaultIconMetrics();
+ int sz_bigger = metrics.sz_med + metrics.sz_small; // ex 40px
+ int sz_pix = sz_bigger/2; // ex 20px
+
+#define EVENT_PIXMAP(PIX) QPixmap(QString(PIX)).scaled(sz_pix, sz_pix, Qt::KeepAspectRatio, Qt::SmoothTransformation)
+#define EVENT_PIXMAP_BIGGER(PIX) QPixmap(QString(PIX)).scaled(sz_bigger, sz_bigger, Qt::KeepAspectRatio, Qt::SmoothTransformation)
+ if (same_string(internalEvent->name, "")) {
+ setPixmap(EVENT_PIXMAP(":warning"));
+ } else if (internalEvent->type == SAMPLE_EVENT_BOOKMARK) {
+ setPixmap(EVENT_PIXMAP(":flag"));
+ } else if (strcmp(internalEvent->name, "heading") == 0 ||
+ (same_string(internalEvent->name, "SP change") && internalEvent->time.seconds == 0)) {
+ // 2 cases:
+ // a) some dive computers have heading in every sample
+ // b) at t=0 we might have an "SP change" to indicate dive type
+ // in both cases we want to get the right data into the tooltip but don't want the visual clutter
+ // so set an "almost invisible" pixmap (a narrow but somewhat tall, basically transparent pixmap)
+ // that allows tooltips to work when we don't want to show a specific
+ // pixmap for an event, but want to show the event value in the tooltip
+ QPixmap transparentPixmap(4, 20);
+ transparentPixmap.fill(QColor::fromRgbF(1.0, 1.0, 1.0, 0.01));
+ setPixmap(transparentPixmap);
+ } else if (event_is_gaschange(internalEvent)) {
+ if (internalEvent->gas.mix.he.permille)
+ setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeTrimix"));
+ else if (gasmix_is_air(&internalEvent->gas.mix))
+ setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeAir"));
+ else
+ setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeNitrox"));
+ } else {
+ setPixmap(EVENT_PIXMAP(":warning"));
+ }
+#undef EVENT_PIXMAP
+}
+
+void DiveEventItem::setupToolTipString()
+{
+ // we display the event on screen - so translate
+ QString name = gettextFromC::instance()->tr(internalEvent->name);
+ int value = internalEvent->value;
+ int type = internalEvent->type;
+ if (value) {
+ if (event_is_gaschange(internalEvent)) {
+ name += ": ";
+ name += gasname(&internalEvent->gas.mix);
+
+ /* Do we have an explicit cylinder index? Show it. */
+ if (internalEvent->gas.index >= 0)
+ name += QString(" (cyl %1)").arg(internalEvent->gas.index+1);
+ } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") {
+ name += QString(":%1").arg((double)value / 1000);
+ } else {
+ name += QString(":%1").arg(value);
+ }
+ } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") {
+ // this is a bad idea - we are abusing an existing event type that is supposed to
+ // warn of high or low pO₂ and are turning it into a set point change event
+ name += "\n" + tr("Manual switch to OC");
+ } else {
+ name += internalEvent->flags == SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") :
+ internalEvent->flags == SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : "";
+ }
+ // qDebug() << name;
+ setToolTip(name);
+}
+
+void DiveEventItem::eventVisibilityChanged(const QString &eventName, bool visible)
+{
+}
+
+bool DiveEventItem::shouldBeHidden()
+{
+ struct event *event = internalEvent;
+
+ /*
+ * Some gas change events are special. Some dive computers just tell us the initial gas this way.
+ * Don't bother showing those
+ */
+ struct sample *first_sample = &get_dive_dc(&displayed_dive, dc_number)->sample[0];
+ if (!strcmp(event->name, "gaschange") &&
+ (event->time.seconds == 0 ||
+ (first_sample && event->time.seconds == first_sample->time.seconds)))
+ return true;
+
+ for (int i = 0; i < evn_used; i++) {
+ if (!strcmp(event->name, ev_namelist[i].ev_name) && ev_namelist[i].plot_ev == false)
+ return true;
+ }
+ return false;
+}
+
+void DiveEventItem::recalculatePos(bool instant)
+{
+ if (!vAxis || !hAxis || !internalEvent || !dataModel)
+ return;
+
+ QModelIndexList result = dataModel->match(dataModel->index(0, DivePlotDataModel::TIME), Qt::DisplayRole, internalEvent->time.seconds);
+ if (result.isEmpty()) {
+ Q_ASSERT("can't find a spot in the dataModel");
+ hide();
+ return;
+ }
+ if (!isVisible() && !shouldBeHidden())
+ show();
+ int depth = dataModel->data(dataModel->index(result.first().row(), DivePlotDataModel::DEPTH)).toInt();
+ qreal x = hAxis->posAtValue(internalEvent->time.seconds);
+ qreal y = vAxis->posAtValue(depth);
+ if (!instant)
+ Animations::moveTo(this, x, y);
+ else
+ setPos(x, y);
+ if (isVisible() && shouldBeHidden())
+ hide();
+}
diff --git a/profile-widget/diveeventitem.h b/profile-widget/diveeventitem.h
new file mode 100644
index 000000000..f358fee6d
--- /dev/null
+++ b/profile-widget/diveeventitem.h
@@ -0,0 +1,34 @@
+#ifndef DIVEEVENTITEM_H
+#define DIVEEVENTITEM_H
+
+#include "divepixmapitem.h"
+
+class DiveCartesianAxis;
+class DivePlotDataModel;
+struct event;
+
+class DiveEventItem : public DivePixmapItem {
+ Q_OBJECT
+public:
+ DiveEventItem(QObject *parent = 0);
+ void setEvent(struct event *ev);
+ struct event *getEvent();
+ void eventVisibilityChanged(const QString &eventName, bool visible);
+ void setVerticalAxis(DiveCartesianAxis *axis);
+ void setHorizontalAxis(DiveCartesianAxis *axis);
+ void setModel(DivePlotDataModel *model);
+ bool shouldBeHidden();
+public
+slots:
+ void recalculatePos(bool instant = false);
+
+private:
+ void setupToolTipString();
+ void setupPixmap();
+ DiveCartesianAxis *vAxis;
+ DiveCartesianAxis *hAxis;
+ DivePlotDataModel *dataModel;
+ struct event *internalEvent;
+};
+
+#endif // DIVEEVENTITEM_H
diff --git a/profile-widget/divelineitem.cpp b/profile-widget/divelineitem.cpp
new file mode 100644
index 000000000..f9e288a44
--- /dev/null
+++ b/profile-widget/divelineitem.cpp
@@ -0,0 +1,5 @@
+#include "divelineitem.h"
+
+DiveLineItem::DiveLineItem(QGraphicsItem *parent) : QGraphicsLineItem(parent)
+{
+}
diff --git a/profile-widget/divelineitem.h b/profile-widget/divelineitem.h
new file mode 100644
index 000000000..ec88e9da5
--- /dev/null
+++ b/profile-widget/divelineitem.h
@@ -0,0 +1,15 @@
+#ifndef DIVELINEITEM_H
+#define DIVELINEITEM_H
+
+#include <QObject>
+#include <QGraphicsLineItem>
+
+class DiveLineItem : public QObject, public QGraphicsLineItem {
+ Q_OBJECT
+ Q_PROPERTY(QPointF pos READ pos WRITE setPos)
+ Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)
+public:
+ DiveLineItem(QGraphicsItem *parent = 0);
+};
+
+#endif // DIVELINEITEM_H
diff --git a/profile-widget/divepixmapitem.cpp b/profile-widget/divepixmapitem.cpp
new file mode 100644
index 000000000..627473c2f
--- /dev/null
+++ b/profile-widget/divepixmapitem.cpp
@@ -0,0 +1,130 @@
+#include "divepixmapitem.h"
+#include "animationfunctions.h"
+#include "divepicturemodel.h"
+#include "preferences/preferencesdialog.h"
+
+#include <QDesktopServices>
+#include <QGraphicsView>
+#include <QUrl>
+
+DivePixmapItem::DivePixmapItem(QObject *parent) : QObject(parent), QGraphicsPixmapItem()
+{
+}
+
+DiveButtonItem::DiveButtonItem(QObject *parent): DivePixmapItem(parent)
+{
+}
+
+void DiveButtonItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ QGraphicsItem::mousePressEvent(event);
+ emit clicked();
+}
+
+// If we have many many pictures on screen, maybe a shared-pixmap would be better to
+// paint on screen, but for now, this.
+CloseButtonItem::CloseButtonItem(QObject *parent): DiveButtonItem(parent)
+{
+ static QPixmap p = QPixmap(":trash");
+ setPixmap(p);
+ setFlag(ItemIgnoresTransformations);
+}
+
+void CloseButtonItem::hide()
+{
+ DiveButtonItem::hide();
+}
+
+void CloseButtonItem::show()
+{
+ DiveButtonItem::show();
+}
+
+DivePictureItem::DivePictureItem(QObject *parent): DivePixmapItem(parent),
+ canvas(new QGraphicsRectItem(this)),
+ shadow(new QGraphicsRectItem(this))
+{
+ setFlag(ItemIgnoresTransformations);
+ setAcceptHoverEvents(true);
+ setScale(0.2);
+ connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged()));
+ setVisible(prefs.show_pictures_in_profile);
+
+ canvas->setPen(Qt::NoPen);
+ canvas->setBrush(QColor(Qt::white));
+ canvas->setFlag(ItemStacksBehindParent);
+ canvas->setZValue(-1);
+
+ shadow->setPos(5,5);
+ shadow->setPen(Qt::NoPen);
+ shadow->setBrush(QColor(Qt::lightGray));
+ shadow->setFlag(ItemStacksBehindParent);
+ shadow->setZValue(-2);
+}
+
+void DivePictureItem::settingsChanged()
+{
+ setVisible(prefs.show_pictures_in_profile);
+}
+
+void DivePictureItem::setPixmap(const QPixmap &pix)
+{
+ DivePixmapItem::setPixmap(pix);
+ QRectF r = boundingRect();
+ canvas->setRect(0 - 10, 0 -10, r.width() + 20, r.height() + 20);
+ shadow->setRect(canvas->rect());
+}
+
+CloseButtonItem *button = NULL;
+void DivePictureItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
+{
+ Animations::scaleTo(this, 1.0);
+ setZValue(5);
+
+ if(!button) {
+ button = new CloseButtonItem();
+ button->setScale(0.2);
+ button->setZValue(7);
+ scene()->addItem(button);
+ }
+ button->setParentItem(this);
+ button->setPos(boundingRect().width() - button->boundingRect().width() * 0.2,
+ boundingRect().height() - button->boundingRect().height() * 0.2);
+ button->setOpacity(0);
+ button->show();
+ Animations::show(button);
+ button->disconnect();
+ connect(button, SIGNAL(clicked()), this, SLOT(removePicture()));
+}
+
+void DivePictureItem::setFileUrl(const QString &s)
+{
+ fileUrl = s;
+}
+
+void DivePictureItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
+{
+ Animations::scaleTo(this, 0.2);
+ setZValue(0);
+ if(button){
+ button->setParentItem(NULL);
+ Animations::hide(button);
+ }
+}
+
+DivePictureItem::~DivePictureItem(){
+ if(button){
+ button->setParentItem(NULL);
+ Animations::hide(button);
+ }
+}
+
+void DivePictureItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ QDesktopServices::openUrl(QUrl::fromLocalFile(fileUrl));
+}
+
+void DivePictureItem::removePicture()
+{
+ DivePictureModel::instance()->removePicture(fileUrl);
+}
diff --git a/profile-widget/divepixmapitem.h b/profile-widget/divepixmapitem.h
new file mode 100644
index 000000000..02c1523f7
--- /dev/null
+++ b/profile-widget/divepixmapitem.h
@@ -0,0 +1,57 @@
+#ifndef DIVEPIXMAPITEM_H
+#define DIVEPIXMAPITEM_H
+
+#include <QObject>
+#include <QGraphicsPixmapItem>
+
+class DivePixmapItem : public QObject, public QGraphicsPixmapItem {
+ Q_OBJECT
+ Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity)
+ Q_PROPERTY(QPointF pos WRITE setPos READ pos)
+ Q_PROPERTY(qreal x WRITE setX READ x)
+ Q_PROPERTY(qreal y WRITE setY READ y)
+public:
+ DivePixmapItem(QObject *parent = 0);
+};
+
+class DivePictureItem : public DivePixmapItem {
+ Q_OBJECT
+ Q_PROPERTY(qreal scale WRITE setScale READ scale)
+public:
+ DivePictureItem(QObject *parent = 0);
+ virtual ~DivePictureItem();
+ void setPixmap(const QPixmap& pix);
+public slots:
+ void settingsChanged();
+ void removePicture();
+ void setFileUrl(const QString& s);
+protected:
+ void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
+ void mousePressEvent(QGraphicsSceneMouseEvent *event);
+private:
+ QString fileUrl;
+ QGraphicsRectItem *canvas;
+ QGraphicsRectItem *shadow;
+};
+
+class DiveButtonItem : public DivePixmapItem {
+ Q_OBJECT
+public:
+ DiveButtonItem(QObject *parent = 0);
+protected:
+ virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
+signals:
+ void clicked();
+};
+
+class CloseButtonItem : public DiveButtonItem {
+ Q_OBJECT
+public:
+ CloseButtonItem(QObject *parent = 0);
+public slots:
+ void hide();
+ void show();
+};
+
+#endif // DIVEPIXMAPITEM_H
diff --git a/profile-widget/diveprofileitem.cpp b/profile-widget/diveprofileitem.cpp
new file mode 100644
index 000000000..14efa9123
--- /dev/null
+++ b/profile-widget/diveprofileitem.cpp
@@ -0,0 +1,979 @@
+#include "diveprofileitem.h"
+#include "diveplotdatamodel.h"
+#include "divecartesianaxis.h"
+#include "divetextitem.h"
+#include "animationfunctions.h"
+#include "dive.h"
+#include "profile.h"
+#include "preferences/preferencesdialog.h"
+#include "diveplannermodel.h"
+#include "helpers.h"
+#include "libdivecomputer/parser.h"
+#include "mainwindow.h"
+#include "maintab.h"
+#include "profilewidget2.h"
+#include "diveplanner.h"
+
+#include <QSettings>
+
+AbstractProfilePolygonItem::AbstractProfilePolygonItem() : QObject(), QGraphicsPolygonItem(), hAxis(NULL), vAxis(NULL), dataModel(NULL), hDataColumn(-1), vDataColumn(-1)
+{
+ setCacheMode(DeviceCoordinateCache);
+ connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged()));
+}
+
+void AbstractProfilePolygonItem::settingsChanged()
+{
+}
+
+void AbstractProfilePolygonItem::setHorizontalAxis(DiveCartesianAxis *horizontal)
+{
+ hAxis = horizontal;
+ connect(hAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged()));
+ modelDataChanged();
+}
+
+void AbstractProfilePolygonItem::setHorizontalDataColumn(int column)
+{
+ hDataColumn = column;
+ modelDataChanged();
+}
+
+void AbstractProfilePolygonItem::setModel(DivePlotDataModel *model)
+{
+ dataModel = model;
+ connect(dataModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(modelDataChanged(QModelIndex, QModelIndex)));
+ connect(dataModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(modelDataRemoved(QModelIndex, int, int)));
+ modelDataChanged();
+}
+
+void AbstractProfilePolygonItem::modelDataRemoved(const QModelIndex &parent, int from, int to)
+{
+ setPolygon(QPolygonF());
+ qDeleteAll(texts);
+ texts.clear();
+}
+
+void AbstractProfilePolygonItem::setVerticalAxis(DiveCartesianAxis *vertical)
+{
+ vAxis = vertical;
+ connect(vAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged()));
+ connect(vAxis, SIGNAL(maxChanged()), this, SLOT(modelDataChanged()));
+ modelDataChanged();
+}
+
+void AbstractProfilePolygonItem::setVerticalDataColumn(int column)
+{
+ vDataColumn = column;
+ modelDataChanged();
+}
+
+bool AbstractProfilePolygonItem::shouldCalculateStuff(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ if (!hAxis || !vAxis)
+ return false;
+ if (!dataModel || dataModel->rowCount() == 0)
+ return false;
+ if (hDataColumn == -1 || vDataColumn == -1)
+ return false;
+ if (topLeft.isValid() && bottomRight.isValid()) {
+ if ((topLeft.column() >= vDataColumn || topLeft.column() >= hDataColumn) &&
+ (bottomRight.column() <= vDataColumn || topLeft.column() <= hDataColumn)) {
+ return true;
+ }
+ }
+ return true;
+}
+
+void AbstractProfilePolygonItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ // We don't have enougth data to calculate things, quit.
+
+ // Calculate the polygon. This is the polygon that will be painted on screen
+ // on the ::paint method. Here we calculate the correct position of the points
+ // regarting our cartesian plane ( made by the hAxis and vAxis ), the QPolygonF
+ // is an array of QPointF's, so we basically get the point from the model, convert
+ // to our coordinates, store. no painting is done here.
+ QPolygonF poly;
+ for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) {
+ qreal horizontalValue = dataModel->index(i, hDataColumn).data().toReal();
+ qreal verticalValue = dataModel->index(i, vDataColumn).data().toReal();
+ QPointF point(hAxis->posAtValue(horizontalValue), vAxis->posAtValue(verticalValue));
+ poly.append(point);
+ }
+ setPolygon(poly);
+
+ qDeleteAll(texts);
+ texts.clear();
+}
+
+DiveProfileItem::DiveProfileItem() : show_reported_ceiling(0), reported_ceiling_in_red(0)
+{
+}
+
+void DiveProfileItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(widget);
+ if (polygon().isEmpty())
+ return;
+
+ painter->save();
+ // This paints the Polygon + Background. I'm setting the pen to QPen() so we don't get a black line here,
+ // after all we need to plot the correct velocities colors later.
+ setPen(Qt::NoPen);
+ QGraphicsPolygonItem::paint(painter, option, widget);
+
+ // Here we actually paint the boundaries of the Polygon using the colors that the model provides.
+ // Those are the speed colors of the dives.
+ QPen pen;
+ pen.setCosmetic(true);
+ pen.setWidth(2);
+ QPolygonF poly = polygon();
+ // This paints the colors of the velocities.
+ for (int i = 1, count = dataModel->rowCount(); i < count; i++) {
+ QModelIndex colorIndex = dataModel->index(i, DivePlotDataModel::COLOR);
+ pen.setBrush(QBrush(colorIndex.data(Qt::BackgroundRole).value<QColor>()));
+ painter->setPen(pen);
+ painter->drawLine(poly[i - 1], poly[i]);
+ }
+ painter->restore();
+}
+
+int DiveProfileItem::maxCeiling(int row)
+{
+ int max = -1;
+ plot_data *entry = dataModel->data().entry + row;
+ for (int tissue = 0; tissue < 16; tissue++) {
+ if (max < entry->ceilings[tissue])
+ max = entry->ceilings[tissue];
+ }
+ return max;
+}
+
+void DiveProfileItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ bool eventAdded = false;
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ AbstractProfilePolygonItem::modelDataChanged(topLeft, bottomRight);
+ if (polygon().isEmpty())
+ return;
+
+ show_reported_ceiling = prefs.dcceiling;
+ reported_ceiling_in_red = prefs.redceiling;
+ profileColor = getColor(DEPTH_BOTTOM);
+
+ int currState = qobject_cast<ProfileWidget2 *>(scene()->views().first())->currentState;
+ if (currState == ProfileWidget2::PLAN) {
+ plot_data *entry = dataModel->data().entry;
+ for (int i = 0; i < dataModel->rowCount(); i++, entry++) {
+ int max = maxCeiling(i);
+ // Don't scream if we violate the ceiling by a few cm
+ if (entry->depth < max - 100 && entry->sec > 0) {
+ profileColor = QColor(Qt::red);
+ if (!eventAdded) {
+ add_event(&displayed_dive.dc, entry->sec, SAMPLE_EVENT_CEILING, -1, max / 1000, "planned waypoint above ceiling");
+ eventAdded = true;
+ }
+ }
+ }
+ }
+
+ /* Show any ceiling we may have encountered */
+ if (prefs.dcceiling && !prefs.redceiling) {
+ QPolygonF p = polygon();
+ plot_data *entry = dataModel->data().entry + dataModel->rowCount() - 1;
+ for (int i = dataModel->rowCount() - 1; i >= 0; i--, entry--) {
+ if (!entry->in_deco) {
+ /* not in deco implies this is a safety stop, no ceiling */
+ p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(0)));
+ } else {
+ p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(qMin(entry->stopdepth, entry->depth))));
+ }
+ }
+ setPolygon(p);
+ }
+
+ // This is the blueish gradient that the Depth Profile should have.
+ // It's a simple QLinearGradient with 2 stops, starting from top to bottom.
+ QLinearGradient pat(0, polygon().boundingRect().top(), 0, polygon().boundingRect().bottom());
+ pat.setColorAt(1, profileColor);
+ pat.setColorAt(0, getColor(DEPTH_TOP));
+ setBrush(QBrush(pat));
+
+ int last = -1;
+ for (int i = 0, count = dataModel->rowCount(); i < count; i++) {
+
+ struct plot_data *entry = dataModel->data().entry + i;
+ if (entry->depth < 2000)
+ continue;
+
+ if ((entry == entry->max[2]) && entry->depth / 100 != last) {
+ plot_depth_sample(entry, Qt::AlignHCenter | Qt::AlignBottom, getColor(SAMPLE_DEEP));
+ last = entry->depth / 100;
+ }
+
+ if ((entry == entry->min[2]) && entry->depth / 100 != last) {
+ plot_depth_sample(entry, Qt::AlignHCenter | Qt::AlignTop, getColor(SAMPLE_SHALLOW));
+ last = entry->depth / 100;
+ }
+
+ if (entry->depth != last)
+ last = -1;
+ }
+}
+
+void DiveProfileItem::settingsChanged()
+{
+ //TODO: Only modelDataChanged() here if we need to rebuild the graph ( for instance,
+ // if the prefs.dcceiling are enabled, but prefs.redceiling is disabled
+ // and only if it changed something. let's not waste cpu cycles repoloting something we don't need to.
+ modelDataChanged();
+}
+
+void DiveProfileItem::plot_depth_sample(struct plot_data *entry, QFlags<Qt::AlignmentFlag> flags, const QColor &color)
+{
+ int decimals;
+ double d = get_depth_units(entry->depth, &decimals, NULL);
+ DiveTextItem *item = new DiveTextItem(this);
+ item->setPos(hAxis->posAtValue(entry->sec), vAxis->posAtValue(entry->depth));
+ item->setText(QString("%1").arg(d, 0, 'f', 1));
+ item->setAlignment(flags);
+ item->setBrush(color);
+ texts.append(item);
+}
+
+DiveHeartrateItem::DiveHeartrateItem()
+{
+ QPen pen;
+ pen.setBrush(QBrush(getColor(::HR_PLOT)));
+ pen.setCosmetic(true);
+ pen.setWidth(1);
+ setPen(pen);
+ settingsChanged();
+}
+
+void DiveHeartrateItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ int last = -300, last_printed_hr = 0, sec = 0;
+ struct {
+ int sec;
+ int hr;
+ } hist[3] = {};
+
+ // We don't have enougth data to calculate things, quit.
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ qDeleteAll(texts);
+ texts.clear();
+ // Ignore empty values. a heartrate of 0 would be a bad sign.
+ QPolygonF poly;
+ for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) {
+ int hr = dataModel->index(i, vDataColumn).data().toInt();
+ if (!hr)
+ continue;
+ sec = dataModel->index(i, hDataColumn).data().toInt();
+ QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr));
+ poly.append(point);
+ if (hr == hist[2].hr)
+ // same as last one, no point in looking at printing
+ continue;
+ hist[0] = hist[1];
+ hist[1] = hist[2];
+ hist[2].sec = sec;
+ hist[2].hr = hr;
+ // don't print a HR
+ // if it's not a local min / max
+ // if it's been less than 5min and less than a 20 beats change OR
+ // if it's been less than 2min OR if the change from the
+ // last print is less than 10 beats
+ // to test min / max requires three points, so we now look at the
+ // previous one
+ sec = hist[1].sec;
+ hr = hist[1].hr;
+ if ((hist[0].hr < hr && hr < hist[2].hr) ||
+ (hist[0].hr > hr && hr > hist[2].hr) ||
+ ((sec < last + 300) && (abs(hr - last_printed_hr) < 20)) ||
+ (sec < last + 120) ||
+ (abs(hr - last_printed_hr) < 10))
+ continue;
+ last = sec;
+ createTextItem(sec, hr);
+ last_printed_hr = hr;
+ }
+ setPolygon(poly);
+
+ if (texts.count())
+ texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
+}
+
+void DiveHeartrateItem::createTextItem(int sec, int hr)
+{
+ DiveTextItem *text = new DiveTextItem(this);
+ text->setAlignment(Qt::AlignRight | Qt::AlignBottom);
+ text->setBrush(getColor(HR_TEXT));
+ text->setPos(QPointF(hAxis->posAtValue(sec), vAxis->posAtValue(hr)));
+ text->setScale(0.7); // need to call this BEFORE setText()
+ text->setText(QString("%1").arg(hr));
+ texts.append(text);
+}
+
+void DiveHeartrateItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ painter->save();
+ painter->setPen(pen());
+ painter->drawPolyline(polygon());
+ painter->restore();
+}
+
+void DiveHeartrateItem::settingsChanged()
+{
+ setVisible(prefs.hrgraph);
+}
+
+DivePercentageItem::DivePercentageItem(int i)
+{
+ QPen pen;
+ QColor color;
+ color.setHsl(100 + 10 * i, 200, 100);
+ pen.setBrush(QBrush(color));
+ pen.setCosmetic(true);
+ pen.setWidth(1);
+ setPen(pen);
+ settingsChanged();
+}
+
+void DivePercentageItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ int sec = 0;
+
+ // We don't have enougth data to calculate things, quit.
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ // Ignore empty values. a heartrate of 0 would be a bad sign.
+ QPolygonF poly;
+ for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) {
+ int hr = dataModel->index(i, vDataColumn).data().toInt();
+ if (!hr)
+ continue;
+ sec = dataModel->index(i, hDataColumn).data().toInt();
+ QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr));
+ poly.append(point);
+ }
+ setPolygon(poly);
+
+ if (texts.count())
+ texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
+}
+
+void DivePercentageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ painter->save();
+ painter->setPen(pen());
+ painter->drawPolyline(polygon());
+ painter->restore();
+}
+
+void DivePercentageItem::settingsChanged()
+{
+ setVisible(prefs.percentagegraph);
+}
+
+DiveAmbPressureItem::DiveAmbPressureItem()
+{
+ QPen pen;
+ pen.setBrush(QBrush(getColor(::AMB_PRESSURE_LINE)));
+ pen.setCosmetic(true);
+ pen.setWidth(2);
+ setPen(pen);
+ settingsChanged();
+}
+
+void DiveAmbPressureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ int sec = 0;
+
+ // We don't have enougth data to calculate things, quit.
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ // Ignore empty values. a heartrate of 0 would be a bad sign.
+ QPolygonF poly;
+ for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) {
+ int hr = dataModel->index(i, vDataColumn).data().toInt();
+ if (!hr)
+ continue;
+ sec = dataModel->index(i, hDataColumn).data().toInt();
+ QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr));
+ poly.append(point);
+ }
+ setPolygon(poly);
+
+ if (texts.count())
+ texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
+}
+
+void DiveAmbPressureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ painter->save();
+ painter->setPen(pen());
+ painter->drawPolyline(polygon());
+ painter->restore();
+}
+
+void DiveAmbPressureItem::settingsChanged()
+{
+ setVisible(prefs.percentagegraph);
+}
+
+DiveGFLineItem::DiveGFLineItem()
+{
+ QPen pen;
+ pen.setBrush(QBrush(getColor(::GF_LINE)));
+ pen.setCosmetic(true);
+ pen.setWidth(2);
+ setPen(pen);
+ settingsChanged();
+}
+
+void DiveGFLineItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ int sec = 0;
+
+ // We don't have enougth data to calculate things, quit.
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ // Ignore empty values. a heartrate of 0 would be a bad sign.
+ QPolygonF poly;
+ for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) {
+ int hr = dataModel->index(i, vDataColumn).data().toInt();
+ if (!hr)
+ continue;
+ sec = dataModel->index(i, hDataColumn).data().toInt();
+ QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr));
+ poly.append(point);
+ }
+ setPolygon(poly);
+
+ if (texts.count())
+ texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
+}
+
+void DiveGFLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ painter->save();
+ painter->setPen(pen());
+ painter->drawPolyline(polygon());
+ painter->restore();
+}
+
+void DiveGFLineItem::settingsChanged()
+{
+ setVisible(prefs.percentagegraph);
+}
+
+DiveTemperatureItem::DiveTemperatureItem()
+{
+ QPen pen;
+ pen.setBrush(QBrush(getColor(::TEMP_PLOT)));
+ pen.setCosmetic(true);
+ pen.setWidth(2);
+ setPen(pen);
+}
+
+void DiveTemperatureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ int last = -300, last_printed_temp = 0, sec = 0, last_valid_temp = 0;
+ // We don't have enougth data to calculate things, quit.
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ qDeleteAll(texts);
+ texts.clear();
+ // Ignore empty values. things do not look good with '0' as temperature in kelvin...
+ QPolygonF poly;
+ for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) {
+ int mkelvin = dataModel->index(i, vDataColumn).data().toInt();
+ if (!mkelvin)
+ continue;
+ last_valid_temp = mkelvin;
+ sec = dataModel->index(i, hDataColumn).data().toInt();
+ QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(mkelvin));
+ poly.append(point);
+
+ /* don't print a temperature
+ * if it's been less than 5min and less than a 2K change OR
+ * if it's been less than 2min OR if the change from the
+ * last print is less than .4K (and therefore less than 1F) */
+ if (((sec < last + 300) && (abs(mkelvin - last_printed_temp) < 2000)) ||
+ (sec < last + 120) ||
+ (abs(mkelvin - last_printed_temp) < 400))
+ continue;
+ last = sec;
+ if (mkelvin > 200000)
+ createTextItem(sec, mkelvin);
+ last_printed_temp = mkelvin;
+ }
+ setPolygon(poly);
+
+ /* it would be nice to print the end temperature, if it's
+ * different or if the last temperature print has been more
+ * than a quarter of the dive back */
+ if (last_valid_temp > 200000 &&
+ ((abs(last_valid_temp - last_printed_temp) > 500) || ((double)last / (double)sec < 0.75))) {
+ createTextItem(sec, last_valid_temp);
+ }
+ if (texts.count())
+ texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
+}
+
+void DiveTemperatureItem::createTextItem(int sec, int mkelvin)
+{
+ double deg;
+ const char *unit;
+ deg = get_temp_units(mkelvin, &unit);
+
+ DiveTextItem *text = new DiveTextItem(this);
+ text->setAlignment(Qt::AlignRight | Qt::AlignBottom);
+ text->setBrush(getColor(TEMP_TEXT));
+ text->setPos(QPointF(hAxis->posAtValue(sec), vAxis->posAtValue(mkelvin)));
+ text->setScale(0.8); // need to call this BEFORE setText()
+ text->setText(QString("%1%2").arg(deg, 0, 'f', 1).arg(unit));
+ texts.append(text);
+}
+
+void DiveTemperatureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ painter->save();
+ painter->setPen(pen());
+ painter->drawPolyline(polygon());
+ painter->restore();
+}
+
+DiveMeanDepthItem::DiveMeanDepthItem()
+{
+ QPen pen;
+ pen.setBrush(QBrush(getColor(::HR_AXIS)));
+ pen.setCosmetic(true);
+ pen.setWidth(2);
+ setPen(pen);
+ settingsChanged();
+}
+
+void DiveMeanDepthItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ double meandepthvalue = 0.0;
+ // We don't have enougth data to calculate things, quit.
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ QPolygonF poly;
+ plot_data *entry = dataModel->data().entry;
+ for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++, entry++) {
+ // Ignore empty values
+ if (entry->running_sum == 0 || entry->sec == 0)
+ continue;
+
+ meandepthvalue = entry->running_sum / entry->sec;
+ QPointF point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(meandepthvalue));
+ poly.append(point);
+ }
+ lastRunningSum = meandepthvalue;
+ setPolygon(poly);
+ createTextItem();
+}
+
+
+void DiveMeanDepthItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ painter->save();
+ painter->setPen(pen());
+ painter->drawPolyline(polygon());
+ painter->restore();
+}
+
+void DiveMeanDepthItem::settingsChanged()
+{
+ setVisible(prefs.show_average_depth);
+}
+
+void DiveMeanDepthItem::createTextItem() {
+ plot_data *entry = dataModel->data().entry;
+ int sec = entry[dataModel->rowCount()-1].sec;
+ qDeleteAll(texts);
+ texts.clear();
+ int decimals;
+ const char *unitText;
+ double d = get_depth_units(lastRunningSum, &decimals, &unitText);
+ DiveTextItem *text = new DiveTextItem(this);
+ text->setAlignment(Qt::AlignRight | Qt::AlignTop);
+ text->setBrush(getColor(TEMP_TEXT));
+ text->setPos(QPointF(hAxis->posAtValue(sec) + 1, vAxis->posAtValue(lastRunningSum)));
+ text->setScale(0.8); // need to call this BEFORE setText()
+ text->setText(QString("%1%2").arg(d, 0, 'f', 1).arg(unitText));
+ texts.append(text);
+}
+
+void DiveGasPressureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ // We don't have enougth data to calculate things, quit.
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+ int last_index = -1;
+ int o2mbar;
+ QPolygonF boundingPoly, o2Poly; // This is the "Whole Item", but a pressure can be divided in N Polygons.
+ polygons.clear();
+ if (displayed_dive.dc.divemode == CCR)
+ polygons.append(o2Poly);
+
+ for (int i = 0, count = dataModel->rowCount(); i < count; i++) {
+ o2mbar = 0;
+ plot_data *entry = dataModel->data().entry + i;
+ int mbar = GET_PRESSURE(entry);
+ if (displayed_dive.dc.divemode == CCR)
+ o2mbar = GET_O2CYLINDER_PRESSURE(entry);
+
+ if (entry->cylinderindex != last_index) {
+ polygons.append(QPolygonF()); // this is the polygon that will be actually drawn on screen.
+ last_index = entry->cylinderindex;
+ }
+ if (!mbar) {
+ continue;
+ }
+ if (o2mbar) {
+ QPointF o2point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(o2mbar));
+ boundingPoly.push_back(o2point);
+ polygons.first().push_back(o2point);
+ }
+
+ QPointF point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(mbar));
+ boundingPoly.push_back(point); // The BoundingRect
+ polygons.last().push_back(point); // The polygon thta will be plotted.
+ }
+ setPolygon(boundingPoly);
+ qDeleteAll(texts);
+ texts.clear();
+ int mbar, cyl;
+ int seen_cyl[MAX_CYLINDERS] = { false, };
+ int last_pressure[MAX_CYLINDERS] = { 0, };
+ int last_time[MAX_CYLINDERS] = { 0, };
+ struct plot_data *entry;
+
+ cyl = -1;
+ o2mbar = 0;
+
+ double print_y_offset[8][2] = { { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 } ,{ 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 } };
+ // CCR dives: These are offset values used to print the gas lables and pressures on a CCR dive profile at
+ // appropriate Y-coordinates: One doublet of values for each of 8 cylinders.
+ // Order of offsets within a doublet: gas lable offset; gas pressure offset.
+ // The array is initialised with default values that apply to non-CCR dives.
+
+ bool offsets_initialised = false;
+ int o2cyl = -1, dilcyl = -1;
+ QFlags<Qt::AlignmentFlag> alignVar= Qt::AlignTop, align_dil = Qt::AlignBottom, align_o2 = Qt::AlignTop;
+ double axisRange = (vAxis->maximum() - vAxis->minimum())/1000; // Convert axis pressure range to bar
+ double axisLog = log10(log10(axisRange));
+ for (int i = 0, count = dataModel->rowCount(); i < count; i++) {
+ entry = dataModel->data().entry + i;
+ mbar = GET_PRESSURE(entry);
+ if (displayed_dive.dc.divemode == CCR && displayed_dive.oxygen_cylinder_index >= 0)
+ o2mbar = GET_O2CYLINDER_PRESSURE(entry);
+
+ if (o2mbar) { // If there is an o2mbar value then this is a CCR dive. Then do:
+ // The first time an o2 value is detected, see if the oxygen cyl pressure graph starts above or below the dil graph
+ if (!offsets_initialised) { // Initialise the parameters for placing the text correctly near the graph line:
+ o2cyl = displayed_dive.oxygen_cylinder_index;
+ dilcyl = displayed_dive.diluent_cylinder_index;
+ if ((o2mbar > mbar)) { // If above, write o2 start cyl pressure above graph and diluent pressure below graph:
+ print_y_offset[o2cyl][0] = -7 * axisLog; // y offset for oxygen gas lable (above); pressure offsets=-0.5, already initialised
+ print_y_offset[dilcyl][0] = 5 * axisLog; // y offset for diluent gas lable (below)
+ } else { // ... else write o2 start cyl pressure below graph:
+ print_y_offset[o2cyl][0] = 5 * axisLog; // o2 lable & pressure below graph; pressure offsets=-0.5, already initialised
+ print_y_offset[dilcyl][0] = -7.8 * axisLog; // and diluent lable above graph.
+ align_dil = Qt::AlignTop;
+ align_o2 = Qt::AlignBottom;
+ }
+ offsets_initialised = true;
+ }
+
+ if (!seen_cyl[displayed_dive.oxygen_cylinder_index]) { //For o2, on the left of profile, write lable and pressure
+ plotPressureValue(o2mbar, entry->sec, align_o2, print_y_offset[o2cyl][1]);
+ plotGasValue(o2mbar, entry->sec, displayed_dive.cylinder[displayed_dive.oxygen_cylinder_index].gasmix, align_o2, print_y_offset[o2cyl][0]);
+ seen_cyl[displayed_dive.oxygen_cylinder_index] = true;
+ }
+ last_pressure[displayed_dive.oxygen_cylinder_index] = o2mbar;
+ last_time[displayed_dive.oxygen_cylinder_index] = entry->sec;
+ alignVar = align_dil;
+ }
+
+ if (!mbar)
+ continue;
+
+ if (cyl != entry->cylinderindex) { // Pressure value near the left hand edge of the profile - other cylinders:
+ cyl = entry->cylinderindex; // For each other cylinder, write the gas lable and pressure
+ if (!seen_cyl[cyl]) {
+ plotPressureValue(mbar, entry->sec, alignVar, print_y_offset[cyl][1]);
+ plotGasValue(mbar, entry->sec, displayed_dive.cylinder[cyl].gasmix, align_dil, print_y_offset[cyl][0]);
+ seen_cyl[cyl] = true;
+ }
+ }
+ last_pressure[cyl] = mbar;
+ last_time[cyl] = entry->sec;
+ }
+
+ for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { // For each cylinder, on right hand side of profile, write cylinder pressure
+ alignVar = ((o2cyl >= 0) && (cyl == displayed_dive.oxygen_cylinder_index)) ? align_o2 : align_dil;
+ if (last_time[cyl]) {
+ plotPressureValue(last_pressure[cyl], last_time[cyl], (alignVar | Qt::AlignLeft), print_y_offset[cyl][1]);
+ }
+ }
+}
+
+void DiveGasPressureItem::plotPressureValue(int mbar, int sec, QFlags<Qt::AlignmentFlag> align, double pressure_offset)
+{
+ const char *unit;
+ int pressure = get_pressure_units(mbar, &unit);
+ DiveTextItem *text = new DiveTextItem(this);
+ text->setPos(hAxis->posAtValue(sec), vAxis->posAtValue(mbar) + pressure_offset );
+ text->setText(QString("%1 %2").arg(pressure).arg(unit));
+ text->setAlignment(align);
+ text->setBrush(getColor(PRESSURE_TEXT));
+ texts.push_back(text);
+}
+
+void DiveGasPressureItem::plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags<Qt::AlignmentFlag> align, double gasname_offset)
+{
+ QString gas = get_gas_string(gasmix);
+ DiveTextItem *text = new DiveTextItem(this);
+ text->setPos(hAxis->posAtValue(sec), vAxis->posAtValue(mbar) + gasname_offset );
+ text->setText(gas);
+ text->setAlignment(align);
+ text->setBrush(getColor(PRESSURE_TEXT));
+ texts.push_back(text);
+}
+
+void DiveGasPressureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ QPen pen;
+ pen.setCosmetic(true);
+ pen.setWidth(2);
+ painter->save();
+ struct plot_data *entry;
+ Q_FOREACH (const QPolygonF &poly, polygons) {
+ entry = dataModel->data().entry;
+ for (int i = 1, count = poly.count(); i < count; i++, entry++) {
+ if (entry->sac)
+ pen.setBrush(getSacColor(entry->sac, displayed_dive.sac));
+ else
+ pen.setBrush(MED_GRAY_HIGH_TRANS);
+ painter->setPen(pen);
+ painter->drawLine(poly[i - 1], poly[i]);
+ }
+ }
+ painter->restore();
+}
+
+DiveCalculatedCeiling::DiveCalculatedCeiling() : is3mIncrement(false)
+{
+ settingsChanged();
+}
+
+void DiveCalculatedCeiling::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ if (MainWindow::instance()->information())
+ connect(MainWindow::instance()->information(), SIGNAL(dateTimeChanged()), this, SLOT(recalc()), Qt::UniqueConnection);
+
+ // We don't have enougth data to calculate things, quit.
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+ AbstractProfilePolygonItem::modelDataChanged(topLeft, bottomRight);
+ // Add 2 points to close the polygon.
+ QPolygonF poly = polygon();
+ if (poly.isEmpty())
+ return;
+ QPointF p1 = poly.first();
+ QPointF p2 = poly.last();
+
+ poly.prepend(QPointF(p1.x(), vAxis->posAtValue(0)));
+ poly.append(QPointF(p2.x(), vAxis->posAtValue(0)));
+ setPolygon(poly);
+
+ QLinearGradient pat(0, polygon().boundingRect().top(), 0, polygon().boundingRect().bottom());
+ pat.setColorAt(0, getColor(CALC_CEILING_SHALLOW));
+ pat.setColorAt(1, getColor(CALC_CEILING_DEEP));
+ setPen(QPen(QBrush(Qt::NoBrush), 0));
+ setBrush(pat);
+}
+
+void DiveCalculatedCeiling::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ QGraphicsPolygonItem::paint(painter, option, widget);
+}
+
+DiveCalculatedTissue::DiveCalculatedTissue()
+{
+ settingsChanged();
+}
+
+void DiveCalculatedTissue::settingsChanged()
+{
+ setVisible(prefs.calcalltissues && prefs.calcceiling);
+}
+
+void DiveReportedCeiling::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ QPolygonF p;
+ p.append(QPointF(hAxis->posAtValue(0), vAxis->posAtValue(0)));
+ plot_data *entry = dataModel->data().entry;
+ for (int i = 0, count = dataModel->rowCount(); i < count; i++, entry++) {
+ if (entry->in_deco && entry->stopdepth) {
+ p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(qMin(entry->stopdepth, entry->depth))));
+ } else {
+ p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(0)));
+ }
+ }
+ setPolygon(p);
+ QLinearGradient pat(0, p.boundingRect().top(), 0, p.boundingRect().bottom());
+ // does the user want the ceiling in "surface color" or in red?
+ if (prefs.redceiling) {
+ pat.setColorAt(0, getColor(CEILING_SHALLOW));
+ pat.setColorAt(1, getColor(CEILING_DEEP));
+ } else {
+ pat.setColorAt(0, getColor(BACKGROUND_TRANS));
+ pat.setColorAt(1, getColor(BACKGROUND_TRANS));
+ }
+ setPen(QPen(QBrush(Qt::NoBrush), 0));
+ setBrush(pat);
+}
+
+void DiveCalculatedCeiling::recalc()
+{
+ dataModel->calculateDecompression();
+}
+
+void DiveCalculatedCeiling::settingsChanged()
+{
+ if (dataModel && is3mIncrement != prefs.calcceiling3m) {
+ // recalculate that part.
+ recalc();
+ }
+ is3mIncrement = prefs.calcceiling3m;
+ setVisible(prefs.calcceiling);
+}
+
+void DiveReportedCeiling::settingsChanged()
+{
+ setVisible(prefs.dcceiling);
+}
+
+void DiveReportedCeiling::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ if (polygon().isEmpty())
+ return;
+ QGraphicsPolygonItem::paint(painter, option, widget);
+}
+
+void PartialPressureGasItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ //AbstractProfilePolygonItem::modelDataChanged();
+ if (!shouldCalculateStuff(topLeft, bottomRight))
+ return;
+
+ plot_data *entry = dataModel->data().entry;
+ QPolygonF poly;
+ QPolygonF alertpoly;
+ alertPolygons.clear();
+ QSettings s;
+ s.beginGroup("TecDetails");
+ double threshold = 0.0;
+ if (thresholdPtr)
+ threshold = *thresholdPtr;
+ bool inAlertFragment = false;
+ for (int i = 0; i < dataModel->rowCount(); i++, entry++) {
+ double value = dataModel->index(i, vDataColumn).data().toDouble();
+ int time = dataModel->index(i, hDataColumn).data().toInt();
+ QPointF point(hAxis->posAtValue(time), vAxis->posAtValue(value));
+ poly.push_back(point);
+ if (value >= threshold) {
+ if (inAlertFragment) {
+ alertPolygons.back().push_back(point);
+ } else {
+ alertpoly.clear();
+ alertpoly.push_back(point);
+ alertPolygons.append(alertpoly);
+ inAlertFragment = true;
+ }
+ } else {
+ inAlertFragment = false;
+ }
+ }
+ setPolygon(poly);
+ /*
+ createPPLegend(trUtf8("pN" UTF8_SUBSCRIPT_2),getColor(PN2), legendPos);
+ */
+}
+
+void PartialPressureGasItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ const qreal pWidth = 0.0;
+ painter->save();
+ painter->setPen(QPen(normalColor, pWidth));
+ painter->drawPolyline(polygon());
+
+ QPolygonF poly;
+ painter->setPen(QPen(alertColor, pWidth));
+ Q_FOREACH (const QPolygonF &poly, alertPolygons)
+ painter->drawPolyline(poly);
+ painter->restore();
+}
+
+void PartialPressureGasItem::setThreshouldSettingsKey(double *prefPointer)
+{
+ thresholdPtr = prefPointer;
+}
+
+PartialPressureGasItem::PartialPressureGasItem() :
+ thresholdPtr(NULL)
+{
+}
+
+void PartialPressureGasItem::settingsChanged()
+{
+ QSettings s;
+ s.beginGroup("TecDetails");
+ setVisible(s.value(visibilityKey).toBool());
+}
+
+void PartialPressureGasItem::setVisibilitySettingsKey(const QString &key)
+{
+ visibilityKey = key;
+}
+
+void PartialPressureGasItem::setColors(const QColor &normal, const QColor &alert)
+{
+ normalColor = normal;
+ alertColor = alert;
+}
diff --git a/profile-widget/diveprofileitem.h b/profile-widget/diveprofileitem.h
new file mode 100644
index 000000000..0bba7f7a3
--- /dev/null
+++ b/profile-widget/diveprofileitem.h
@@ -0,0 +1,225 @@
+#ifndef DIVEPROFILEITEM_H
+#define DIVEPROFILEITEM_H
+
+#include <QObject>
+#include <QGraphicsPolygonItem>
+#include <QModelIndex>
+
+#include "divelineitem.h"
+
+/* This is the Profile Item, it should be used for quite a lot of things
+ on the profile view. The usage should be pretty simple:
+
+ DiveProfileItem *profile = new DiveProfileItem();
+ profile->setVerticalAxis( profileYAxis );
+ profile->setHorizontalAxis( timeAxis );
+ profile->setModel( DiveDataModel );
+ profile->setHorizontalDataColumn( DiveDataModel::TIME );
+ profile->setVerticalDataColumn( DiveDataModel::DEPTH );
+ scene()->addItem(profile);
+
+ This is a generically item and should be used as a base for others, I think...
+*/
+
+class DivePlotDataModel;
+class DiveTextItem;
+class DiveCartesianAxis;
+class QAbstractTableModel;
+struct plot_data;
+
+class AbstractProfilePolygonItem : public QObject, public QGraphicsPolygonItem {
+ Q_OBJECT
+ Q_PROPERTY(QPointF pos WRITE setPos READ pos)
+ Q_PROPERTY(qreal x WRITE setX READ x)
+ Q_PROPERTY(qreal y WRITE setY READ y)
+public:
+ AbstractProfilePolygonItem();
+ void setVerticalAxis(DiveCartesianAxis *vertical);
+ void setHorizontalAxis(DiveCartesianAxis *horizontal);
+ void setModel(DivePlotDataModel *model);
+ void setHorizontalDataColumn(int column);
+ void setVerticalDataColumn(int column);
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) = 0;
+ virtual void clear()
+ {
+ }
+public
+slots:
+ virtual void settingsChanged();
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+ virtual void modelDataRemoved(const QModelIndex &parent, int from, int to);
+
+protected:
+ /* when the model emits a 'datachanged' signal, this method below should be used to check if the
+ * modified data affects this particular item ( for example, when setting the '3m increment'
+ * the data for Ceiling and tissues will be changed, and only those. so, the topLeft will be the CEILING
+ * column and the bottomRight will have the TISSUE_16 column. this method takes the vDataColumn and hDataColumn
+ * into consideration when returning 'true' for "yes, continue the calculation', and 'false' for
+ * 'do not recalculate, we already have the right data.
+ */
+ bool shouldCalculateStuff(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+
+ DiveCartesianAxis *hAxis;
+ DiveCartesianAxis *vAxis;
+ DivePlotDataModel *dataModel;
+ int hDataColumn;
+ int vDataColumn;
+ QList<DiveTextItem *> texts;
+};
+
+class DiveProfileItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+
+public:
+ DiveProfileItem();
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+ virtual void settingsChanged();
+ void plot_depth_sample(struct plot_data *entry, QFlags<Qt::AlignmentFlag> flags, const QColor &color);
+ int maxCeiling(int row);
+
+private:
+ unsigned int show_reported_ceiling;
+ unsigned int reported_ceiling_in_red;
+ QColor profileColor;
+};
+
+class DiveMeanDepthItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+public:
+ DiveMeanDepthItem();
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+ virtual void settingsChanged();
+
+private:
+ void createTextItem();
+ double lastRunningSum;
+ QString visibilityKey;
+};
+
+class DiveTemperatureItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+public:
+ DiveTemperatureItem();
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+
+private:
+ void createTextItem(int seconds, int mkelvin);
+};
+
+class DiveHeartrateItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+public:
+ DiveHeartrateItem();
+ virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+ virtual void settingsChanged();
+
+private:
+ void createTextItem(int seconds, int hr);
+ QString visibilityKey;
+};
+
+class DivePercentageItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+public:
+ DivePercentageItem(int i);
+ virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+ virtual void settingsChanged();
+
+private:
+ QString visibilityKey;
+};
+
+class DiveAmbPressureItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+public:
+ DiveAmbPressureItem();
+ virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+ virtual void settingsChanged();
+
+private:
+ QString visibilityKey;
+};
+
+class DiveGFLineItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+public:
+ DiveGFLineItem();
+ virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+ virtual void settingsChanged();
+
+private:
+ QString visibilityKey;
+};
+
+class DiveGasPressureItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+
+public:
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+
+private:
+ void plotPressureValue(int mbar, int sec, QFlags<Qt::AlignmentFlag> align, double offset);
+ void plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags<Qt::AlignmentFlag> align, double offset);
+ QVector<QPolygonF> polygons;
+};
+
+class DiveCalculatedCeiling : public AbstractProfilePolygonItem {
+ Q_OBJECT
+
+public:
+ DiveCalculatedCeiling();
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+ virtual void settingsChanged();
+
+public
+slots:
+ void recalc();
+
+private:
+ bool is3mIncrement;
+};
+
+class DiveReportedCeiling : public AbstractProfilePolygonItem {
+ Q_OBJECT
+
+public:
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+ virtual void settingsChanged();
+};
+
+class DiveCalculatedTissue : public DiveCalculatedCeiling {
+ Q_OBJECT
+public:
+ DiveCalculatedTissue();
+ virtual void settingsChanged();
+};
+
+class PartialPressureGasItem : public AbstractProfilePolygonItem {
+ Q_OBJECT
+public:
+ PartialPressureGasItem();
+ virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+ virtual void settingsChanged();
+ void setThreshouldSettingsKey(double *prefPointer);
+ void setVisibilitySettingsKey(const QString &setVisibilitySettingsKey);
+ void setColors(const QColor &normalColor, const QColor &alertColor);
+
+private:
+ QVector<QPolygonF> alertPolygons;
+ double *thresholdPtr;
+ QString visibilityKey;
+ QColor normalColor;
+ QColor alertColor;
+};
+#endif // DIVEPROFILEITEM_H
diff --git a/profile-widget/diverectitem.cpp b/profile-widget/diverectitem.cpp
new file mode 100644
index 000000000..8cb60c3f5
--- /dev/null
+++ b/profile-widget/diverectitem.cpp
@@ -0,0 +1,5 @@
+#include "diverectitem.h"
+
+DiveRectItem::DiveRectItem(QObject *parent, QGraphicsItem *parentItem) : QObject(parent), QGraphicsRectItem(parentItem)
+{
+}
diff --git a/profile-widget/diverectitem.h b/profile-widget/diverectitem.h
new file mode 100644
index 000000000..e616cf591
--- /dev/null
+++ b/profile-widget/diverectitem.h
@@ -0,0 +1,17 @@
+#ifndef DIVERECTITEM_H
+#define DIVERECTITEM_H
+
+#include <QObject>
+#include <QGraphicsRectItem>
+
+class DiveRectItem : public QObject, public QGraphicsRectItem {
+ Q_OBJECT
+ Q_PROPERTY(QRectF rect WRITE setRect READ rect)
+ Q_PROPERTY(QPointF pos WRITE setPos READ pos)
+ Q_PROPERTY(qreal x WRITE setX READ x)
+ Q_PROPERTY(qreal y WRITE setY READ y)
+public:
+ DiveRectItem(QObject *parent = 0, QGraphicsItem *parentItem = 0);
+};
+
+#endif // DIVERECTITEM_H
diff --git a/profile-widget/divetextitem.cpp b/profile-widget/divetextitem.cpp
new file mode 100644
index 000000000..3bf00d68f
--- /dev/null
+++ b/profile-widget/divetextitem.cpp
@@ -0,0 +1,113 @@
+#include "divetextitem.h"
+#include "mainwindow.h"
+#include "profilewidget2.h"
+#include "subsurface-core/color.h"
+
+#include <QBrush>
+
+DiveTextItem::DiveTextItem(QGraphicsItem *parent) : QGraphicsItemGroup(parent),
+ internalAlignFlags(Qt::AlignHCenter | Qt::AlignVCenter),
+ textBackgroundItem(new QGraphicsPathItem(this)),
+ textItem(new QGraphicsPathItem(this)),
+ printScale(1.0),
+ scale(1.0),
+ connected(false)
+{
+ setFlag(ItemIgnoresTransformations);
+ textBackgroundItem->setBrush(QBrush(getColor(TEXT_BACKGROUND)));
+ textBackgroundItem->setPen(Qt::NoPen);
+ textItem->setPen(Qt::NoPen);
+}
+
+void DiveTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ updateText();
+ QGraphicsItemGroup::paint(painter, option, widget);
+}
+
+void DiveTextItem::fontPrintScaleUpdate(double scale)
+{
+ printScale = scale;
+}
+
+void DiveTextItem::setAlignment(int alignFlags)
+{
+ if (alignFlags != internalAlignFlags) {
+ internalAlignFlags = alignFlags;
+ }
+}
+
+void DiveTextItem::setBrush(const QBrush &b)
+{
+ textItem->setBrush(b);
+}
+
+void DiveTextItem::setScale(double newscale)
+{
+ if (scale != newscale) {
+ scale = newscale;
+ }
+}
+
+void DiveTextItem::setText(const QString &t)
+{
+ if (internalText != t) {
+ if (!connected) {
+ if (scene()) {
+ // by now we should be on a scene. grab the profile widget from it and setup our printScale
+ // and connect to the signal that makes sure we keep track if that changes
+ ProfileWidget2 *profile = qobject_cast<ProfileWidget2 *>(scene()->views().first());
+ connect(profile, SIGNAL(fontPrintScaleChanged(double)), this, SLOT(fontPrintScaleUpdate(double)), Qt::UniqueConnection);
+ fontPrintScaleUpdate(profile->getFontPrintScale());
+ connected = true;
+ } else {
+ qDebug() << "called before scene was set up" << t;
+ }
+ }
+ internalText = t;
+ updateText();
+ }
+}
+
+const QString &DiveTextItem::text()
+{
+ return internalText;
+}
+
+void DiveTextItem::updateText()
+{
+ double size;
+ if (internalText.isEmpty()) {
+ return;
+ }
+
+ QFont fnt(qApp->font());
+ if ((size = fnt.pixelSize()) > 0) {
+ // set in pixels - so the scale factor may not make a difference if it's too close to 1
+ size *= scale * printScale;
+ fnt.setPixelSize(size);
+ } else {
+ size = fnt.pointSizeF();
+ size *= scale * printScale;
+ fnt.setPointSizeF(size);
+ }
+ QFontMetrics fm(fnt);
+
+ QPainterPath textPath;
+ qreal xPos = 0, yPos = 0;
+
+ QRectF rect = fm.boundingRect(internalText);
+ yPos = (internalAlignFlags & Qt::AlignTop) ? 0 :
+ (internalAlignFlags & Qt::AlignBottom) ? +rect.height() :
+ /*(internalAlignFlags & Qt::AlignVCenter ? */ +rect.height() / 4;
+
+ xPos = (internalAlignFlags & Qt::AlignLeft) ? -rect.width() :
+ (internalAlignFlags & Qt::AlignHCenter) ? -rect.width() / 2 :
+ /* (internalAlignFlags & Qt::AlignRight) */ 0;
+
+ textPath.addText(xPos, yPos, fnt, internalText);
+ QPainterPathStroker stroker;
+ stroker.setWidth(3);
+ textBackgroundItem->setPath(stroker.createStroke(textPath));
+ textItem->setPath(textPath);
+}
diff --git a/profile-widget/divetextitem.h b/profile-widget/divetextitem.h
new file mode 100644
index 000000000..be0adf292
--- /dev/null
+++ b/profile-widget/divetextitem.h
@@ -0,0 +1,38 @@
+#ifndef DIVETEXTITEM_H
+#define DIVETEXTITEM_H
+
+#include <QObject>
+#include <QGraphicsItemGroup>
+
+class QBrush;
+
+/* A Line Item that has animated-properties. */
+class DiveTextItem : public QObject, public QGraphicsItemGroup {
+ Q_OBJECT
+ Q_PROPERTY(QPointF pos READ pos WRITE setPos)
+ Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)
+public:
+ DiveTextItem(QGraphicsItem *parent = 0);
+ void setText(const QString &text);
+ void setAlignment(int alignFlags);
+ void setScale(double newscale);
+ void setBrush(const QBrush &brush);
+ const QString &text();
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+
+private
+slots:
+ void fontPrintScaleUpdate(double scale);
+
+private:
+ void updateText();
+ int internalAlignFlags;
+ QGraphicsPathItem *textBackgroundItem;
+ QGraphicsPathItem *textItem;
+ QString internalText;
+ double printScale;
+ double scale;
+ bool connected;
+};
+
+#endif // DIVETEXTITEM_H
diff --git a/profile-widget/divetooltipitem.cpp b/profile-widget/divetooltipitem.cpp
new file mode 100644
index 000000000..d4818422b
--- /dev/null
+++ b/profile-widget/divetooltipitem.cpp
@@ -0,0 +1,285 @@
+#include "divetooltipitem.h"
+#include "divecartesianaxis.h"
+#include "dive.h"
+#include "profile.h"
+#include "membuffer.h"
+#include "metrics.h"
+#include <QPropertyAnimation>
+#include <QSettings>
+#include <QGraphicsView>
+#include <QStyleOptionGraphicsItem>
+
+#define PORT_IN_PROGRESS 1
+#ifdef PORT_IN_PROGRESS
+#include "display.h"
+#endif
+
+void ToolTipItem::addToolTip(const QString &toolTip, const QIcon &icon, const QPixmap& pixmap)
+{
+ const IconMetrics& iconMetrics = defaultIconMetrics();
+
+ QGraphicsPixmapItem *iconItem = 0;
+ double yValue = title->boundingRect().height() + iconMetrics.spacing;
+ Q_FOREACH (ToolTip t, toolTips) {
+ yValue += t.second->boundingRect().height();
+ }
+ if (entryToolTip.second) {
+ yValue += entryToolTip.second->boundingRect().height();
+ }
+ iconItem = new QGraphicsPixmapItem(this);
+ if (!icon.isNull()) {
+ iconItem->setPixmap(icon.pixmap(iconMetrics.sz_small, iconMetrics.sz_small));
+ } else if (!pixmap.isNull()) {
+ iconItem->setPixmap(pixmap);
+ }
+ iconItem->setPos(iconMetrics.spacing, yValue);
+
+ QGraphicsSimpleTextItem *textItem = new QGraphicsSimpleTextItem(toolTip, this);
+ textItem->setPos(iconMetrics.spacing + iconMetrics.sz_small + iconMetrics.spacing, yValue);
+ textItem->setBrush(QBrush(Qt::white));
+ textItem->setFlag(ItemIgnoresTransformations);
+ toolTips.push_back(qMakePair(iconItem, textItem));
+}
+
+void ToolTipItem::clear()
+{
+ Q_FOREACH (ToolTip t, toolTips) {
+ delete t.first;
+ delete t.second;
+ }
+ toolTips.clear();
+}
+
+void ToolTipItem::setRect(const QRectF &r)
+{
+ if( r == rect() ) {
+ return;
+ }
+
+ QGraphicsRectItem::setRect(r);
+ updateTitlePosition();
+}
+
+void ToolTipItem::collapse()
+{
+ int dim = defaultIconMetrics().sz_small;
+
+ if (prefs.animation_speed) {
+ QPropertyAnimation *animation = new QPropertyAnimation(this, "rect");
+ animation->setDuration(100);
+ animation->setStartValue(nextRectangle);
+ animation->setEndValue(QRect(0, 0, dim, dim));
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else {
+ setRect(nextRectangle);
+ }
+ clear();
+
+ status = COLLAPSED;
+}
+
+void ToolTipItem::expand()
+{
+ if (!title)
+ return;
+
+ const IconMetrics& iconMetrics = defaultIconMetrics();
+
+ double width = 0, height = title->boundingRect().height() + iconMetrics.spacing;
+ Q_FOREACH (const ToolTip& t, toolTips) {
+ QRectF sRect = t.second->boundingRect();
+ if (sRect.width() > width)
+ width = sRect.width();
+ height += sRect.height();
+ }
+
+ if (entryToolTip.first) {
+ QRectF sRect = entryToolTip.second->boundingRect();
+ if (sRect.width() > width)
+ width = sRect.width();
+ height += sRect.height();
+ }
+
+ /* Left padding, Icon Size, space, right padding */
+ width += iconMetrics.spacing + iconMetrics.sz_small + iconMetrics.spacing + iconMetrics.spacing;
+
+ if (width < title->boundingRect().width() + iconMetrics.spacing * 2)
+ width = title->boundingRect().width() + iconMetrics.spacing * 2;
+
+ if (height < iconMetrics.sz_small)
+ height = iconMetrics.sz_small;
+
+ nextRectangle.setWidth(width);
+ nextRectangle.setHeight(height);
+
+ if (nextRectangle != rect()) {
+ if (prefs.animation_speed) {
+ QPropertyAnimation *animation = new QPropertyAnimation(this, "rect", this);
+ animation->setDuration(prefs.animation_speed);
+ animation->setStartValue(rect());
+ animation->setEndValue(nextRectangle);
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else {
+ setRect(nextRectangle);
+ }
+ }
+
+ status = EXPANDED;
+}
+
+ToolTipItem::ToolTipItem(QGraphicsItem *parent) : QGraphicsRectItem(parent),
+ title(new QGraphicsSimpleTextItem(tr("Information"), this)),
+ status(COLLAPSED),
+ timeAxis(0),
+ lastTime(-1)
+{
+ memset(&pInfo, 0, sizeof(pInfo));
+ entryToolTip.first = NULL;
+ entryToolTip.second = NULL;
+ setFlags(ItemIgnoresTransformations | ItemIsMovable | ItemClipsChildrenToShape);
+
+ QColor c = QColor(Qt::black);
+ c.setAlpha(155);
+ setBrush(c);
+
+ setZValue(99);
+
+ addToolTip(QString(), QIcon(), QPixmap(16,60));
+ entryToolTip = toolTips.first();
+ toolTips.clear();
+
+ title->setFlag(ItemIgnoresTransformations);
+ title->setPen(QPen(Qt::white, 1));
+ title->setBrush(Qt::white);
+
+ setPen(QPen(Qt::white, 2));
+ refreshTime.start();
+}
+
+ToolTipItem::~ToolTipItem()
+{
+ clear();
+}
+
+void ToolTipItem::updateTitlePosition()
+{
+ const IconMetrics& iconMetrics = defaultIconMetrics();
+ if (rect().width() < title->boundingRect().width() + iconMetrics.spacing * 4) {
+ QRectF newRect = rect();
+ newRect.setWidth(title->boundingRect().width() + iconMetrics.spacing * 4);
+ newRect.setHeight((newRect.height() && isExpanded()) ? newRect.height() : iconMetrics.sz_small);
+ setRect(newRect);
+ }
+
+ title->setPos(rect().width() / 2 - title->boundingRect().width() / 2 - 1, 0);
+}
+
+bool ToolTipItem::isExpanded() const
+{
+ return status == EXPANDED;
+}
+
+void ToolTipItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ persistPos();
+ QGraphicsRectItem::mouseReleaseEvent(event);
+ Q_FOREACH (QGraphicsItem *item, oldSelection) {
+ item->setSelected(true);
+ }
+}
+
+void ToolTipItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(widget);
+ painter->save();
+ painter->setClipRect(option->rect);
+ painter->setPen(pen());
+ painter->setBrush(brush());
+ painter->drawRoundedRect(rect(), 10, 10, Qt::AbsoluteSize);
+ painter->restore();
+}
+
+void ToolTipItem::persistPos()
+{
+ QSettings s;
+ s.beginGroup("ProfileMap");
+ s.setValue("tooltip_position", pos());
+ s.endGroup();
+}
+
+void ToolTipItem::readPos()
+{
+ QSettings s;
+ s.beginGroup("ProfileMap");
+ QPointF value = s.value("tooltip_position").toPoint();
+ if (!scene()->sceneRect().contains(value)) {
+ value = QPointF(0, 0);
+ }
+ setPos(value);
+}
+
+void ToolTipItem::setPlotInfo(const plot_info &plot)
+{
+ pInfo = plot;
+}
+
+void ToolTipItem::setTimeAxis(DiveCartesianAxis *axis)
+{
+ timeAxis = axis;
+}
+
+void ToolTipItem::refresh(const QPointF &pos)
+{
+ struct plot_data *entry;
+ static QPixmap tissues(16,60);
+ static QPainter painter(&tissues);
+ static struct membuffer mb = { 0 };
+
+ if(refreshTime.elapsed() < 40)
+ return;
+ refreshTime.start();
+
+ int time = timeAxis->valueAt(pos);
+ if (time == lastTime)
+ return;
+
+ lastTime = time;
+ clear();
+
+ mb.len = 0;
+ entry = get_plot_details_new(&pInfo, time, &mb);
+ if (entry) {
+ tissues.fill();
+ painter.setPen(QColor(0, 0, 0, 0));
+ painter.setBrush(QColor(LIMENADE1));
+ painter.drawRect(0, 10 + (100 - AMB_PERCENTAGE) / 2, 16, AMB_PERCENTAGE / 2);
+ painter.setBrush(QColor(SPRINGWOOD1));
+ painter.drawRect(0, 10, 16, (100 - AMB_PERCENTAGE) / 2);
+ painter.setBrush(QColor(Qt::red));
+ painter.drawRect(0,0,16,10);
+ painter.setPen(QColor(0, 0, 0, 255));
+ painter.drawLine(0, 60 - entry->gfline / 2, 16, 60 - entry->gfline / 2);
+ painter.drawLine(0, 60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure / 2,
+ 16, 60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure /2);
+ painter.setPen(QColor(0, 0, 0, 127));
+ for (int i=0; i<16; i++) {
+ painter.drawLine(i, 60, i, 60 - entry->percentages[i] / 2);
+ }
+ entryToolTip.first->setPixmap(tissues);
+ entryToolTip.second->setText(QString::fromUtf8(mb.buffer, mb.len));
+ }
+
+ Q_FOREACH (QGraphicsItem *item, scene()->items(pos, Qt::IntersectsItemBoundingRect
+ ,Qt::DescendingOrder, scene()->views().first()->transform())) {
+ if (!item->toolTip().isEmpty())
+ addToolTip(item->toolTip());
+ }
+ expand();
+}
+
+void ToolTipItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ oldSelection = scene()->selectedItems();
+ scene()->clearSelection();
+ QGraphicsItem::mousePressEvent(event);
+}
diff --git a/profile-widget/divetooltipitem.h b/profile-widget/divetooltipitem.h
new file mode 100644
index 000000000..4fa7ec2d7
--- /dev/null
+++ b/profile-widget/divetooltipitem.h
@@ -0,0 +1,67 @@
+#ifndef DIVETOOLTIPITEM_H
+#define DIVETOOLTIPITEM_H
+
+#include <QGraphicsRectItem>
+#include <QVector>
+#include <QPair>
+#include <QRectF>
+#include <QIcon>
+#include <QTime>
+#include "display.h"
+
+class DiveCartesianAxis;
+class QGraphicsLineItem;
+class QGraphicsSimpleTextItem;
+class QGraphicsPixmapItem;
+struct graphics_context;
+
+/* To use a tooltip, simply ->setToolTip on the QGraphicsItem that you want
+ * or, if it's a "global" tooltip, set it on the mouseMoveEvent of the ProfileGraphicsView.
+ */
+class ToolTipItem : public QObject, public QGraphicsRectItem {
+ Q_OBJECT
+ void updateTitlePosition();
+ Q_PROPERTY(QRectF rect READ rect WRITE setRect)
+
+public:
+ enum Status {
+ COLLAPSED,
+ EXPANDED
+ };
+
+ explicit ToolTipItem(QGraphicsItem *parent = 0);
+ virtual ~ToolTipItem();
+
+ void collapse();
+ void expand();
+ void clear();
+ void addToolTip(const QString &toolTip, const QIcon &icon = QIcon(), const QPixmap &pixmap = QPixmap());
+ void refresh(const QPointF &pos);
+ bool isExpanded() const;
+ void persistPos();
+ void readPos();
+ void mousePressEvent(QGraphicsSceneMouseEvent *event);
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
+ void setTimeAxis(DiveCartesianAxis *axis);
+ void setPlotInfo(const plot_info &plot);
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+public
+slots:
+ void setRect(const QRectF &rect);
+
+private:
+ typedef QPair<QGraphicsPixmapItem *, QGraphicsSimpleTextItem *> ToolTip;
+ QVector<ToolTip> toolTips;
+ ToolTip entryToolTip;
+ QGraphicsSimpleTextItem *title;
+ Status status;
+ QRectF rectangle;
+ QRectF nextRectangle;
+ DiveCartesianAxis *timeAxis;
+ plot_info pInfo;
+ int lastTime;
+ QTime refreshTime;
+ QList<QGraphicsItem*> oldSelection;
+};
+
+#endif // DIVETOOLTIPITEM_H
diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp
new file mode 100644
index 000000000..653818556
--- /dev/null
+++ b/profile-widget/profilewidget2.cpp
@@ -0,0 +1,1842 @@
+#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/preferencesdialog.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()),
+ decoModelParameters(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(decoModelParameters);
+ 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 deco model parameters at the top in the center
+ decoModelParameters->setY(0);
+ decoModelParameters->setX(50);
+ decoModelParameters->setBrush(getColor(PRESSURE_TEXT));
+ decoModelParameters->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);
+ if (prefs.deco_mode == VPMB)
+ decoModelParameters->setText(QString("VPM-B +%1").arg(prefs.conservatism_level));
+ else
+ decoModelParameters->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;
+ }
+ if (prefs.deco_mode == VPMB)
+ decoModelParameters->setText(QString("VPM-B +%1").arg(prefs.conservatism_level));
+ else
+ decoModelParameters->setText(QString("GF %1/%2").arg(prefs.gflow).arg(prefs.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);
+ decoModelParameters->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);
+ decoModelParameters->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);
+ decoModelParameters->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);
+ decoModelParameters->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);
+ }
+}
diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h
new file mode 100644
index 000000000..ce3fda083
--- /dev/null
+++ b/profile-widget/profilewidget2.h
@@ -0,0 +1,211 @@
+#ifndef PROFILEWIDGET2_H
+#define PROFILEWIDGET2_H
+
+#include <QGraphicsView>
+
+// /* The idea of this widget is to display and edit the profile.
+// * It has:
+// * 1 - ToolTip / Legend item, displays every information of the current mouse position on it, plus the legends of the maps.
+// * 2 - ToolBox, displays the QActions that are used to do special stuff on the profile ( like activating the plugins. )
+// * 3 - Cartesian Axis for depth ( y )
+// * 4 - Cartesian Axis for Gases ( y )
+// * 5 - Cartesian Axis for Time ( x )
+// *
+// * It needs to be dynamic, things should *flow* on it, not just appear / disappear.
+// */
+#include "divelineitem.h"
+#include "diveprofileitem.h"
+#include "display.h"
+
+class RulerItem2;
+struct dive;
+struct plot_info;
+class ToolTipItem;
+class DiveMeanDepth;
+class DiveReportedCeiling;
+class DiveTextItem;
+class TemperatureAxis;
+class DiveEventItem;
+class DivePlotDataModel;
+class DivePixmapItem;
+class DiveRectItem;
+class DepthAxis;
+class DiveCartesianAxis;
+class DiveProfileItem;
+class TimeAxis;
+class DiveTemperatureItem;
+class DiveHeartrateItem;
+class PercentageItem;
+class DiveGasPressureItem;
+class DiveCalculatedCeiling;
+class DiveCalculatedTissue;
+class PartialPressureGasItem;
+class PartialGasPressureAxis;
+class AbstractProfilePolygonItem;
+class TankItem;
+class DiveHandler;
+class QGraphicsSimpleTextItem;
+class QModelIndex;
+class DivePictureItem;
+
+class ProfileWidget2 : public QGraphicsView {
+ Q_OBJECT
+public:
+ enum State {
+ EMPTY,
+ PROFILE,
+ EDIT,
+ ADD,
+ PLAN,
+ INVALID
+ };
+ enum Items {
+ BACKGROUND,
+ PROFILE_Y_AXIS,
+ GAS_Y_AXIS,
+ TIME_AXIS,
+ DEPTH_CONTROLLER,
+ TIME_CONTROLLER,
+ COLUMNS
+ };
+
+ ProfileWidget2(QWidget *parent = 0);
+ void resetZoom();
+ void plotDive(struct dive *d = 0, bool force = false);
+ virtual bool eventFilter(QObject *, QEvent *);
+ void setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, DivePlotDataModel *model, int vData, int hData, int zValue);
+ void setPrintMode(bool mode, bool grayscale = false);
+ bool getPrintMode();
+ bool isPointOutOfBoundaries(const QPointF &point) const;
+ bool isPlanner();
+ bool isAddOrPlanner();
+ double getFontPrintScale();
+ void setFontPrintScale(double scale);
+ void clearHandlers();
+ void recalcCeiling();
+ void setToolTipVisibile(bool visible);
+ State currentState;
+
+signals:
+ void fontPrintScaleChanged(double scale);
+
+public
+slots: // Necessary to call from QAction's signals.
+ void settingsChanged();
+ void setEmptyState();
+ void setProfileState();
+ void setPlanState();
+ void setAddState();
+ void changeGas();
+ void addSetpointChange();
+ void addBookmark();
+ void hideEvents();
+ void unhideEvents();
+ void removeEvent();
+ void editName();
+ void makeFirstDC();
+ void deleteCurrentDC();
+ void pointInserted(const QModelIndex &parent, int start, int end);
+ void pointsRemoved(const QModelIndex &, int start, int end);
+ void plotPictures();
+ void setReplot(bool state);
+ void replot(dive *d = 0);
+
+ /* this is called for every move on the handlers. maybe we can speed up this a bit? */
+ void recreatePlannedDive();
+
+ /* key press handlers */
+ void keyEscAction();
+ void keyDeleteAction();
+ void keyUpAction();
+ void keyDownAction();
+ void keyLeftAction();
+ void keyRightAction();
+
+ void divePlannerHandlerClicked();
+ void divePlannerHandlerReleased();
+
+protected:
+ virtual ~ProfileWidget2();
+ virtual void resizeEvent(QResizeEvent *event);
+ virtual void wheelEvent(QWheelEvent *event);
+ virtual void mouseMoveEvent(QMouseEvent *event);
+ virtual void contextMenuEvent(QContextMenuEvent *event);
+ virtual void mouseDoubleClickEvent(QMouseEvent *event);
+ virtual void mousePressEvent(QMouseEvent *event);
+ virtual void mouseReleaseEvent(QMouseEvent *event);
+
+private: /*methods*/
+ void fixBackgroundPos();
+ void scrollViewTo(const QPoint &pos);
+ void setupSceneAndFlags();
+ void setupItemSizes();
+ void addItemsToScene();
+ void setupItemOnScene();
+ void disconnectTemporaryConnections();
+ struct plot_data *getEntryFromPos(QPointF pos);
+
+private:
+ DivePlotDataModel *dataModel;
+ int zoomLevel;
+ qreal zoomFactor;
+ DivePixmapItem *background;
+ QString backgroundFile;
+ ToolTipItem *toolTipItem;
+ bool isPlotZoomed;
+ bool replotEnabled;
+ // All those here should probably be merged into one structure,
+ // So it's esyer to replicate for more dives later.
+ // In the meantime, keep it here.
+ struct plot_info plotInfo;
+ DepthAxis *profileYAxis;
+ PartialGasPressureAxis *gasYAxis;
+ TemperatureAxis *temperatureAxis;
+ TimeAxis *timeAxis;
+ DiveProfileItem *diveProfileItem;
+ DiveTemperatureItem *temperatureItem;
+ DiveMeanDepthItem *meanDepthItem;
+ DiveCartesianAxis *cylinderPressureAxis;
+ DiveGasPressureItem *gasPressureItem;
+ QList<DiveEventItem *> eventItems;
+ DiveTextItem *diveComputerText;
+ DiveCalculatedCeiling *diveCeiling;
+ DiveTextItem *decoModelParameters;
+ QList<DiveCalculatedTissue *> allTissues;
+ DiveReportedCeiling *reportedCeiling;
+ PartialPressureGasItem *pn2GasItem;
+ PartialPressureGasItem *pheGasItem;
+ PartialPressureGasItem *po2GasItem;
+ PartialPressureGasItem *o2SetpointGasItem;
+ PartialPressureGasItem *ccrsensor1GasItem;
+ PartialPressureGasItem *ccrsensor2GasItem;
+ PartialPressureGasItem *ccrsensor3GasItem;
+ DiveCartesianAxis *heartBeatAxis;
+ DiveHeartrateItem *heartBeatItem;
+ DiveCartesianAxis *percentageAxis;
+ QList<DivePercentageItem *> allPercentages;
+ DiveAmbPressureItem *ambPressureItem;
+ DiveGFLineItem *gflineItem;
+ DiveLineItem *mouseFollowerVertical;
+ DiveLineItem *mouseFollowerHorizontal;
+ RulerItem2 *rulerItem;
+ TankItem *tankItem;
+ bool isGrayscale;
+ bool printMode;
+
+ //specifics for ADD and PLAN
+ QList<DiveHandler *> handles;
+ QList<QGraphicsSimpleTextItem *> gases;
+ QList<DivePictureItem *> pictures;
+ void repositionDiveHandlers();
+ int fixHandlerIndex(DiveHandler *activeHandler);
+ friend class DiveHandler;
+ QHash<Qt::Key, QAction *> actionsForKeys;
+ bool shouldCalculateMaxTime;
+ bool shouldCalculateMaxDepth;
+ int maxtime;
+ int maxdepth;
+ double fontPrintScale;
+};
+
+#endif // PROFILEWIDGET2_H
diff --git a/profile-widget/ruleritem.cpp b/profile-widget/ruleritem.cpp
new file mode 100644
index 000000000..a5a61c0fe
--- /dev/null
+++ b/profile-widget/ruleritem.cpp
@@ -0,0 +1,179 @@
+#include "ruleritem.h"
+#include "preferences/preferencesdialog.h"
+#include "mainwindow.h"
+#include "profilewidget2.h"
+#include "display.h"
+
+#include <qgraphicssceneevent.h>
+
+#include "profile.h"
+
+RulerNodeItem2::RulerNodeItem2() :
+ entry(NULL),
+ ruler(NULL),
+ timeAxis(NULL),
+ depthAxis(NULL)
+{
+ memset(&pInfo, 0, sizeof(pInfo));
+ setRect(-8, -8, 16, 16);
+ setBrush(QColor(0xff, 0, 0, 127));
+ setPen(QColor(Qt::red));
+ setFlag(ItemIsMovable);
+ setFlag(ItemSendsGeometryChanges);
+ setFlag(ItemIgnoresTransformations);
+}
+
+void RulerNodeItem2::setPlotInfo(plot_info &info)
+{
+ pInfo = info;
+ entry = pInfo.entry;
+}
+
+void RulerNodeItem2::setRuler(RulerItem2 *r)
+{
+ ruler = r;
+}
+
+void RulerNodeItem2::recalculate()
+{
+ struct plot_data *data = pInfo.entry + (pInfo.nr - 1);
+ uint16_t count = 0;
+ if (x() < 0) {
+ setPos(0, y());
+ } else if (x() > timeAxis->posAtValue(data->sec)) {
+ setPos(timeAxis->posAtValue(data->sec), depthAxis->posAtValue(data->depth));
+ } else {
+ data = pInfo.entry;
+ count = 0;
+ while (timeAxis->posAtValue(data->sec) < x() && count < pInfo.nr) {
+ data = pInfo.entry + count;
+ count++;
+ }
+ setPos(timeAxis->posAtValue(data->sec), depthAxis->posAtValue(data->depth));
+ entry = data;
+ }
+}
+
+void RulerNodeItem2::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ qreal x = event->scenePos().x();
+ if (x < 0.0)
+ x = 0.0;
+ setPos(x, event->scenePos().y());
+ recalculate();
+ ruler->recalculate();
+}
+
+RulerItem2::RulerItem2() : source(new RulerNodeItem2()),
+ dest(new RulerNodeItem2()),
+ timeAxis(NULL),
+ depthAxis(NULL),
+ textItemBack(new QGraphicsRectItem(this)),
+ textItem(new QGraphicsSimpleTextItem(this))
+{
+ memset(&pInfo, 0, sizeof(pInfo));
+ source->setRuler(this);
+ dest->setRuler(this);
+ textItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
+ textItemBack->setBrush(QColor(0xff, 0xff, 0xff, 190));
+ textItemBack->setPen(QColor(Qt::white));
+ textItemBack->setFlag(QGraphicsItem::ItemIgnoresTransformations);
+ setPen(QPen(QColor(Qt::black), 0.0));
+ connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged()));
+}
+
+void RulerItem2::settingsChanged()
+{
+ ProfileWidget2 *profWidget = NULL;
+ if (scene() && scene()->views().count())
+ profWidget = qobject_cast<ProfileWidget2 *>(scene()->views().first());
+
+ if (profWidget && profWidget->currentState == ProfileWidget2::PROFILE)
+ setVisible(prefs.rulergraph);
+ else
+ setVisible(false);
+}
+
+void RulerItem2::recalculate()
+{
+ char buffer[500];
+ QPointF tmp;
+ QFont font;
+ QFontMetrics fm(font);
+
+ if (timeAxis == NULL || depthAxis == NULL || pInfo.nr == 0)
+ return;
+
+ prepareGeometryChange();
+ startPoint = mapFromItem(source, 0, 0);
+ endPoint = mapFromItem(dest, 0, 0);
+
+ if (startPoint.x() > endPoint.x()) {
+ tmp = endPoint;
+ endPoint = startPoint;
+ startPoint = tmp;
+ }
+ QLineF line(startPoint, endPoint);
+ setLine(line);
+ compare_samples(source->entry, dest->entry, buffer, 500, 1);
+ text = QString(buffer);
+
+ // draw text
+ QGraphicsView *view = scene()->views().first();
+ QPoint begin = view->mapFromScene(mapToScene(startPoint));
+ textItem->setText(text);
+ qreal tgtX = startPoint.x();
+ const qreal diff = begin.x() + textItem->boundingRect().width();
+ // clamp so that the text doesn't go out of the screen to the right
+ if (diff > view->width()) {
+ begin.setX(begin.x() - (diff - view->width()));
+ tgtX = mapFromScene(view->mapToScene(begin)).x();
+ }
+ // always show the text bellow the lowest of the start and end points
+ qreal tgtY = (startPoint.y() >= endPoint.y()) ? startPoint.y() : endPoint.y();
+ // this isn't exactly optimal, since we want to scale the 1.0, 4.0 distances as well
+ textItem->setPos(tgtX - 1.0, tgtY + 4.0);
+
+ // setup the text background
+ textItemBack->setVisible(startPoint.x() != endPoint.x());
+ textItemBack->setPos(textItem->x(), textItem->y());
+ textItemBack->setRect(0, 0, textItem->boundingRect().width(), textItem->boundingRect().height());
+}
+
+RulerNodeItem2 *RulerItem2::sourceNode() const
+{
+ return source;
+}
+
+RulerNodeItem2 *RulerItem2::destNode() const
+{
+ return dest;
+}
+
+void RulerItem2::setPlotInfo(plot_info info)
+{
+ pInfo = info;
+ dest->setPlotInfo(info);
+ source->setPlotInfo(info);
+ dest->recalculate();
+ source->recalculate();
+ recalculate();
+}
+
+void RulerItem2::setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth)
+{
+ timeAxis = time;
+ depthAxis = depth;
+ dest->depthAxis = depth;
+ dest->timeAxis = time;
+ source->depthAxis = depth;
+ source->timeAxis = time;
+ recalculate();
+}
+
+void RulerItem2::setVisible(bool visible)
+{
+ QGraphicsLineItem::setVisible(visible);
+ source->setVisible(visible);
+ dest->setVisible(visible);
+}
diff --git a/profile-widget/ruleritem.h b/profile-widget/ruleritem.h
new file mode 100644
index 000000000..4fad0451c
--- /dev/null
+++ b/profile-widget/ruleritem.h
@@ -0,0 +1,59 @@
+#ifndef RULERITEM_H
+#define RULERITEM_H
+
+#include <QObject>
+#include <QGraphicsEllipseItem>
+#include <QGraphicsObject>
+#include "divecartesianaxis.h"
+#include "display.h"
+
+struct plot_data;
+class RulerItem2;
+
+class RulerNodeItem2 : public QObject, public QGraphicsEllipseItem {
+ Q_OBJECT
+ friend class RulerItem2;
+
+public:
+ explicit RulerNodeItem2();
+ void setRuler(RulerItem2 *r);
+ void setPlotInfo(struct plot_info &info);
+ void recalculate();
+
+protected:
+ virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
+private:
+ struct plot_info pInfo;
+ struct plot_data *entry;
+ RulerItem2 *ruler;
+ DiveCartesianAxis *timeAxis;
+ DiveCartesianAxis *depthAxis;
+};
+
+class RulerItem2 : public QObject, public QGraphicsLineItem {
+ Q_OBJECT
+public:
+ explicit RulerItem2();
+ void recalculate();
+
+ void setPlotInfo(struct plot_info pInfo);
+ RulerNodeItem2 *sourceNode() const;
+ RulerNodeItem2 *destNode() const;
+ void setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth);
+ void setVisible(bool visible);
+
+public
+slots:
+ void settingsChanged();
+
+private:
+ struct plot_info pInfo;
+ QPointF startPoint, endPoint;
+ RulerNodeItem2 *source, *dest;
+ QString text;
+ DiveCartesianAxis *timeAxis;
+ DiveCartesianAxis *depthAxis;
+ QGraphicsRectItem *textItemBack;
+ QGraphicsSimpleTextItem *textItem;
+};
+#endif
diff --git a/profile-widget/tankitem.cpp b/profile-widget/tankitem.cpp
new file mode 100644
index 000000000..c0e75a371
--- /dev/null
+++ b/profile-widget/tankitem.cpp
@@ -0,0 +1,120 @@
+#include "tankitem.h"
+#include "diveplotdatamodel.h"
+#include "divetextitem.h"
+#include "profile.h"
+#include <QPen>
+
+TankItem::TankItem(QObject *parent) :
+ QGraphicsRectItem(),
+ dataModel(0),
+ pInfoEntry(0),
+ pInfoNr(0)
+{
+ height = 3;
+ QColor red(PERSIANRED1);
+ QColor blue(AIR_BLUE);
+ QColor yellow(NITROX_YELLOW);
+ QColor green(NITROX_GREEN);
+ QLinearGradient nitroxGradient(QPointF(0, 0), QPointF(0, height));
+ nitroxGradient.setColorAt(0.0, green);
+ nitroxGradient.setColorAt(0.49, green);
+ nitroxGradient.setColorAt(0.5, yellow);
+ nitroxGradient.setColorAt(1.0, yellow);
+ nitrox = nitroxGradient;
+ oxygen = green;
+ QLinearGradient trimixGradient(QPointF(0, 0), QPointF(0, height));
+ trimixGradient.setColorAt(0.0, green);
+ trimixGradient.setColorAt(0.49, green);
+ trimixGradient.setColorAt(0.5, red);
+ trimixGradient.setColorAt(1.0, red);
+ trimix = trimixGradient;
+ air = blue;
+ memset(&diveCylinderStore, 0, sizeof(diveCylinderStore));
+}
+
+TankItem::~TankItem()
+{
+ // Should this be clear_dive(diveCylinderStore)?
+ for (int i = 0; i < MAX_CYLINDERS; i++)
+ free((void *)diveCylinderStore.cylinder[i].type.description);
+}
+
+void TankItem::setData(DivePlotDataModel *model, struct plot_info *plotInfo, struct dive *d)
+{
+ free(pInfoEntry);
+ // the plotInfo and dive structures passed in could become invalid before we stop using them,
+ // so copy the data that we need
+ int size = plotInfo->nr * sizeof(plotInfo->entry[0]);
+ pInfoEntry = (struct plot_data *)malloc(size);
+ pInfoNr = plotInfo->nr;
+ memcpy(pInfoEntry, plotInfo->entry, size);
+ copy_cylinders(d, &diveCylinderStore, false);
+ dataModel = model;
+ connect(dataModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(modelDataChanged(QModelIndex, QModelIndex)), Qt::UniqueConnection);
+ modelDataChanged();
+}
+
+void TankItem::createBar(qreal x, qreal w, struct gasmix *gas)
+{
+ // pick the right gradient, size, position and text
+ QGraphicsRectItem *rect = new QGraphicsRectItem(x, 0, w, height, this);
+ if (gasmix_is_air(gas))
+ rect->setBrush(air);
+ else if (gas->he.permille)
+ rect->setBrush(trimix);
+ else if (gas->o2.permille == 1000)
+ rect->setBrush(oxygen);
+ else
+ rect->setBrush(nitrox);
+ rect->setPen(QPen(QBrush(), 0.0)); // get rid of the thick line around the rectangle
+ rects.push_back(rect);
+ DiveTextItem *label = new DiveTextItem(rect);
+ label->setText(gasname(gas));
+ label->setBrush(Qt::black);
+ label->setPos(x + 1, 0);
+ label->setAlignment(Qt::AlignBottom | Qt::AlignRight);
+ label->setZValue(101);
+}
+
+void TankItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ // We don't have enougth data to calculate things, quit.
+
+ if (!dataModel || !pInfoEntry || !pInfoNr)
+ return;
+
+ // remove the old rectangles
+ foreach (QGraphicsRectItem *r, rects) {
+ delete(r);
+ }
+ rects.clear();
+
+ // walk the list and figure out which tanks go where
+ struct plot_data *entry = pInfoEntry;
+ int cylIdx = entry->cylinderindex;
+ int i = -1;
+ int startTime = 0;
+ struct gasmix *gas = &diveCylinderStore.cylinder[cylIdx].gasmix;
+ qreal width, left;
+ while (++i < pInfoNr) {
+ entry = &pInfoEntry[i];
+ if (entry->cylinderindex == cylIdx)
+ continue;
+ width = hAxis->posAtValue(entry->sec) - hAxis->posAtValue(startTime);
+ left = hAxis->posAtValue(startTime);
+ createBar(left, width, gas);
+ cylIdx = entry->cylinderindex;
+ gas = &diveCylinderStore.cylinder[cylIdx].gasmix;
+ startTime = entry->sec;
+ }
+ width = hAxis->posAtValue(entry->sec) - hAxis->posAtValue(startTime);
+ left = hAxis->posAtValue(startTime);
+ createBar(left, width, gas);
+}
+
+void TankItem::setHorizontalAxis(DiveCartesianAxis *horizontal)
+{
+ hAxis = horizontal;
+ connect(hAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged()));
+ modelDataChanged();
+}
diff --git a/profile-widget/tankitem.h b/profile-widget/tankitem.h
new file mode 100644
index 000000000..fd685fc82
--- /dev/null
+++ b/profile-widget/tankitem.h
@@ -0,0 +1,39 @@
+#ifndef TANKITEM_H
+#define TANKITEM_H
+
+#include <QGraphicsItem>
+#include <QModelIndex>
+#include <QBrush>
+#include "divelineitem.h"
+#include "divecartesianaxis.h"
+#include "dive.h"
+
+class TankItem : public QObject, public QGraphicsRectItem
+{
+ Q_OBJECT
+
+public:
+ explicit TankItem(QObject *parent = 0);
+ ~TankItem();
+ void setHorizontalAxis(DiveCartesianAxis *horizontal);
+ void setData(DivePlotDataModel *model, struct plot_info *plotInfo, struct dive *d);
+
+signals:
+
+public slots:
+ virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex());
+
+private:
+ void createBar(qreal x, qreal w, struct gasmix *gas);
+ DivePlotDataModel *dataModel;
+ DiveCartesianAxis *hAxis;
+ int hDataColumn;
+ struct dive diveCylinderStore;
+ struct plot_data *pInfoEntry;
+ int pInfoNr;
+ qreal height;
+ QBrush air, nitrox, oxygen, trimix;
+ QList<QGraphicsRectItem *> rects;
+};
+
+#endif // TANKITEM_H