From 1d6683f3e07d9a73af5fab702bc3a551ec7dabc9 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Thu, 3 Sep 2015 15:56:37 -0300 Subject: Move Profile widget out of desktop-widgets The reason for that is, even if profile widget is made with qpainter and for that reason it should be a desktop widget, it's being used on the mobile version because of a lack of QML plotting library that is fast and reliable. We discovered that it was faster just to encapsulate our Profile in a QML class and call it directly. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- CMakeLists.txt | 3 +- desktop-widgets/CMakeLists.txt | 19 - desktop-widgets/diveplanner.cpp | 6 +- desktop-widgets/maintab.cpp | 2 +- desktop-widgets/mainwindow.cpp | 2 +- desktop-widgets/modeldelegates.cpp | 2 +- desktop-widgets/printer.cpp | 1 + desktop-widgets/printer.h | 1 - desktop-widgets/profile/animationfunctions.cpp | 75 - desktop-widgets/profile/animationfunctions.h | 18 - desktop-widgets/profile/divecartesianaxis.cpp | 459 ------ desktop-widgets/profile/divecartesianaxis.h | 122 -- desktop-widgets/profile/diveeventitem.cpp | 172 --- desktop-widgets/profile/diveeventitem.h | 34 - desktop-widgets/profile/divelineitem.cpp | 5 - desktop-widgets/profile/divelineitem.h | 15 - desktop-widgets/profile/divepixmapitem.cpp | 130 -- desktop-widgets/profile/divepixmapitem.h | 57 - desktop-widgets/profile/diveprofileitem.cpp | 979 ------------- desktop-widgets/profile/diveprofileitem.h | 225 --- desktop-widgets/profile/diverectitem.cpp | 5 - desktop-widgets/profile/diverectitem.h | 17 - desktop-widgets/profile/divetextitem.cpp | 113 -- desktop-widgets/profile/divetextitem.h | 38 - desktop-widgets/profile/divetooltipitem.cpp | 285 ---- desktop-widgets/profile/divetooltipitem.h | 67 - desktop-widgets/profile/profilewidget2.cpp | 1836 ------------------------ desktop-widgets/profile/profilewidget2.h | 211 --- desktop-widgets/profile/ruleritem.cpp | 179 --- desktop-widgets/profile/ruleritem.h | 59 - desktop-widgets/profile/tankitem.cpp | 120 -- desktop-widgets/profile/tankitem.h | 39 - desktop-widgets/simplewidgets.cpp | 2 +- desktop-widgets/socialnetworks.cpp | 2 +- profile-widget/CMakeLists.txt | 19 + profile-widget/animationfunctions.cpp | 75 + profile-widget/animationfunctions.h | 18 + profile-widget/divecartesianaxis.cpp | 459 ++++++ profile-widget/divecartesianaxis.h | 122 ++ profile-widget/diveeventitem.cpp | 172 +++ profile-widget/diveeventitem.h | 34 + profile-widget/divelineitem.cpp | 5 + profile-widget/divelineitem.h | 15 + profile-widget/divepixmapitem.cpp | 130 ++ profile-widget/divepixmapitem.h | 57 + profile-widget/diveprofileitem.cpp | 979 +++++++++++++ profile-widget/diveprofileitem.h | 225 +++ profile-widget/diverectitem.cpp | 5 + profile-widget/diverectitem.h | 17 + profile-widget/divetextitem.cpp | 113 ++ profile-widget/divetextitem.h | 38 + profile-widget/divetooltipitem.cpp | 285 ++++ profile-widget/divetooltipitem.h | 67 + profile-widget/profilewidget2.cpp | 1836 ++++++++++++++++++++++++ profile-widget/profilewidget2.h | 211 +++ profile-widget/ruleritem.cpp | 179 +++ profile-widget/ruleritem.h | 59 + profile-widget/tankitem.cpp | 120 ++ profile-widget/tankitem.h | 39 + qt-models/CMakeLists.txt | 1 + 60 files changed, 5291 insertions(+), 5289 deletions(-) delete mode 100644 desktop-widgets/profile/animationfunctions.cpp delete mode 100644 desktop-widgets/profile/animationfunctions.h delete mode 100644 desktop-widgets/profile/divecartesianaxis.cpp delete mode 100644 desktop-widgets/profile/divecartesianaxis.h delete mode 100644 desktop-widgets/profile/diveeventitem.cpp delete mode 100644 desktop-widgets/profile/diveeventitem.h delete mode 100644 desktop-widgets/profile/divelineitem.cpp delete mode 100644 desktop-widgets/profile/divelineitem.h delete mode 100644 desktop-widgets/profile/divepixmapitem.cpp delete mode 100644 desktop-widgets/profile/divepixmapitem.h delete mode 100644 desktop-widgets/profile/diveprofileitem.cpp delete mode 100644 desktop-widgets/profile/diveprofileitem.h delete mode 100644 desktop-widgets/profile/diverectitem.cpp delete mode 100644 desktop-widgets/profile/diverectitem.h delete mode 100644 desktop-widgets/profile/divetextitem.cpp delete mode 100644 desktop-widgets/profile/divetextitem.h delete mode 100644 desktop-widgets/profile/divetooltipitem.cpp delete mode 100644 desktop-widgets/profile/divetooltipitem.h delete mode 100644 desktop-widgets/profile/profilewidget2.cpp delete mode 100644 desktop-widgets/profile/profilewidget2.h delete mode 100644 desktop-widgets/profile/ruleritem.cpp delete mode 100644 desktop-widgets/profile/ruleritem.h delete mode 100644 desktop-widgets/profile/tankitem.cpp delete mode 100644 desktop-widgets/profile/tankitem.h create mode 100644 profile-widget/CMakeLists.txt create mode 100644 profile-widget/animationfunctions.cpp create mode 100644 profile-widget/animationfunctions.h create mode 100644 profile-widget/divecartesianaxis.cpp create mode 100644 profile-widget/divecartesianaxis.h create mode 100644 profile-widget/diveeventitem.cpp create mode 100644 profile-widget/diveeventitem.h create mode 100644 profile-widget/divelineitem.cpp create mode 100644 profile-widget/divelineitem.h create mode 100644 profile-widget/divepixmapitem.cpp create mode 100644 profile-widget/divepixmapitem.h create mode 100644 profile-widget/diveprofileitem.cpp create mode 100644 profile-widget/diveprofileitem.h create mode 100644 profile-widget/diverectitem.cpp create mode 100644 profile-widget/diverectitem.h create mode 100644 profile-widget/divetextitem.cpp create mode 100644 profile-widget/divetextitem.h create mode 100644 profile-widget/divetooltipitem.cpp create mode 100644 profile-widget/divetooltipitem.h create mode 100644 profile-widget/profilewidget2.cpp create mode 100644 profile-widget/profilewidget2.h create mode 100644 profile-widget/ruleritem.cpp create mode 100644 profile-widget/ruleritem.h create mode 100644 profile-widget/tankitem.cpp create mode 100644 profile-widget/tankitem.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cdee60858..b5b0195a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -313,6 +313,8 @@ endif() add_subdirectory(translations) add_subdirectory(subsurface-core) add_subdirectory(qt-models) +add_subdirectory(profile-widget) +add_subdirectory(desktop-widgets) if(FBSUPPORT) add_definitions(-DFBSUPPORT) @@ -338,7 +340,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT ANDROID) set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} -lpthread) endif() -add_subdirectory(desktop-widgets) # create the executables if(SUBSURFACE_MOBILE) diff --git a/desktop-widgets/CMakeLists.txt b/desktop-widgets/CMakeLists.txt index 2c373b83f..635a8b68e 100644 --- a/desktop-widgets/CMakeLists.txt +++ b/desktop-widgets/CMakeLists.txt @@ -75,23 +75,6 @@ endif() source_group("Subsurface Interface" FILES ${SUBSURFACE_INTERFACE}) -# the profile widget -set(SUBSURFACE_PROFILE_LIB_SRCS - profile/profilewidget2.cpp - profile/diverectitem.cpp - profile/divepixmapitem.cpp - profile/divelineitem.cpp - profile/divetextitem.cpp - profile/animationfunctions.cpp - profile/divecartesianaxis.cpp - profile/diveprofileitem.cpp - profile/diveeventitem.cpp - profile/divetooltipitem.cpp - profile/ruleritem.cpp - profile/tankitem.cpp -) -source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS}) - # the yearly statistics widget. set(SUBSURFACE_STATISTICS_LIB_SRCS statistics/statisticswidget.cpp @@ -101,8 +84,6 @@ set(SUBSURFACE_STATISTICS_LIB_SRCS ) source_group("Subsurface Statistics" FILES ${SUBSURFACE_STATISTICS_LIB_SRCS}) -add_library(subsurface_profile STATIC ${SUBSURFACE_PROFILE_LIB_SRCS}) -target_link_libraries(subsurface_profile ${QT_LIBRARIES}) add_library(subsurface_statistics STATIC ${SUBSURFACE_STATISTICS_LIB_SRCS}) target_link_libraries(subsurface_statistics ${QT_LIBRARIES}) add_library(subsurface_generated_ui STATIC ${SUBSURFACE_UI_HDRS}) diff --git a/desktop-widgets/diveplanner.cpp b/desktop-widgets/diveplanner.cpp index b4413d11a..86f03b1d2 100644 --- a/desktop-widgets/diveplanner.cpp +++ b/desktop-widgets/diveplanner.cpp @@ -5,7 +5,7 @@ #include "helpers.h" #include "cylindermodel.h" #include "models.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "diveplannermodel.h" #include @@ -328,11 +328,11 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f) modeMapper->setMapping(ui.recreational_deco, int(RECREATIONAL)); modeMapper->setMapping(ui.buehlmann_deco, int(BUEHLMANN)); modeMapper->setMapping(ui.vpmb_deco, int(VPMB)); - + connect(ui.recreational_deco, SIGNAL(clicked()), modeMapper, SLOT(map())); connect(ui.buehlmann_deco, SIGNAL(clicked()), modeMapper, SLOT(map())); connect(ui.vpmb_deco, SIGNAL(clicked()), modeMapper, SLOT(map())); - + connect(ui.lastStop, SIGNAL(toggled(bool)), plannerModel, SLOT(setLastStop6m(bool))); connect(ui.verbatim_plan, SIGNAL(toggled(bool)), plannerModel, SLOT(setVerbatim(bool))); connect(ui.display_duration, SIGNAL(toggled(bool)), plannerModel, SLOT(setDisplayDuration(bool))); diff --git a/desktop-widgets/maintab.cpp b/desktop-widgets/maintab.cpp index 0afb7b4c0..4d4cd3a5e 100644 --- a/desktop-widgets/maintab.cpp +++ b/desktop-widgets/maintab.cpp @@ -13,7 +13,7 @@ #include "diveplannermodel.h" #include "divelistview.h" #include "display.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "diveplanner.h" #include "divesitehelpers.h" #include "cylindermodel.h" diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index e1e0d81a2..43b433d62 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -21,7 +21,7 @@ #include "updatemanager.h" #include "planner.h" #include "filtermodels.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "globe.h" #include "divecomputer.h" #include "maintab.h" diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index 881037a83..1b1a31c0b 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -5,7 +5,7 @@ #include "cylindermodel.h" #include "models.h" #include "starwidget.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "tankinfomodel.h" #include "weigthsysteminfomodel.h" #include "weightmodel.h" diff --git a/desktop-widgets/printer.cpp b/desktop-widgets/printer.cpp index f0197d446..33ee71b55 100644 --- a/desktop-widgets/printer.cpp +++ b/desktop-widgets/printer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "profile-widget/profilewidget2.h" Printer::Printer(QPaintDevice *paintDevice, print_options *printOptions, template_options *templateOptions, PrintMode printMode) { diff --git a/desktop-widgets/printer.h b/desktop-widgets/printer.h index 979cacd6a..e5f16d77d 100644 --- a/desktop-widgets/printer.h +++ b/desktop-widgets/printer.h @@ -6,7 +6,6 @@ #include #include -#include "profile/profilewidget2.h" #include "printoptions.h" #include "templateedit.h" diff --git a/desktop-widgets/profile/animationfunctions.cpp b/desktop-widgets/profile/animationfunctions.cpp deleted file mode 100644 index a19d50c9d..000000000 --- a/desktop-widgets/profile/animationfunctions.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "animationfunctions.h" -#include "pref.h" -#include - -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/desktop-widgets/profile/animationfunctions.h b/desktop-widgets/profile/animationfunctions.h deleted file mode 100644 index 3cfcff563..000000000 --- a/desktop-widgets/profile/animationfunctions.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef ANIMATIONFUNCTIONS_H -#define ANIMATIONFUNCTIONS_H - -#include -#include - -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/desktop-widgets/profile/divecartesianaxis.cpp b/desktop-widgets/profile/divecartesianaxis.cpp deleted file mode 100644 index bf5a5380c..000000000 --- a/desktop-widgets/profile/divecartesianaxis.cpp +++ /dev/null @@ -1,459 +0,0 @@ -#include "divecartesianaxis.h" -#include "divetextitem.h" -#include "helpers.h" -#include "preferences.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 -void emptyList(QList &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/desktop-widgets/profile/divecartesianaxis.h b/desktop-widgets/profile/divecartesianaxis.h deleted file mode 100644 index cc7d0bcf7..000000000 --- a/desktop-widgets/profile/divecartesianaxis.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef DIVECARTESIANAXIS_H -#define DIVECARTESIANAXIS_H - -#include -#include -#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 labels; - QList 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/desktop-widgets/profile/diveeventitem.cpp b/desktop-widgets/profile/diveeventitem.cpp deleted file mode 100644 index 0bbc84267..000000000 --- a/desktop-widgets/profile/diveeventitem.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#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/desktop-widgets/profile/diveeventitem.h b/desktop-widgets/profile/diveeventitem.h deleted file mode 100644 index f358fee6d..000000000 --- a/desktop-widgets/profile/diveeventitem.h +++ /dev/null @@ -1,34 +0,0 @@ -#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/desktop-widgets/profile/divelineitem.cpp b/desktop-widgets/profile/divelineitem.cpp deleted file mode 100644 index f9e288a44..000000000 --- a/desktop-widgets/profile/divelineitem.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "divelineitem.h" - -DiveLineItem::DiveLineItem(QGraphicsItem *parent) : QGraphicsLineItem(parent) -{ -} diff --git a/desktop-widgets/profile/divelineitem.h b/desktop-widgets/profile/divelineitem.h deleted file mode 100644 index ec88e9da5..000000000 --- a/desktop-widgets/profile/divelineitem.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef DIVELINEITEM_H -#define DIVELINEITEM_H - -#include -#include - -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/desktop-widgets/profile/divepixmapitem.cpp b/desktop-widgets/profile/divepixmapitem.cpp deleted file mode 100644 index 581f6f9b4..000000000 --- a/desktop-widgets/profile/divepixmapitem.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "divepixmapitem.h" -#include "animationfunctions.h" -#include "divepicturemodel.h" -#include - -#include -#include -#include - -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/desktop-widgets/profile/divepixmapitem.h b/desktop-widgets/profile/divepixmapitem.h deleted file mode 100644 index 02c1523f7..000000000 --- a/desktop-widgets/profile/divepixmapitem.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef DIVEPIXMAPITEM_H -#define DIVEPIXMAPITEM_H - -#include -#include - -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/desktop-widgets/profile/diveprofileitem.cpp b/desktop-widgets/profile/diveprofileitem.cpp deleted file mode 100644 index 2c814678a..000000000 --- a/desktop-widgets/profile/diveprofileitem.cpp +++ /dev/null @@ -1,979 +0,0 @@ -#include "diveprofileitem.h" -#include "diveplotdatamodel.h" -#include "divecartesianaxis.h" -#include "divetextitem.h" -#include "animationfunctions.h" -#include "dive.h" -#include "profile.h" -#include "preferences.h" -#include "diveplannermodel.h" -#include "helpers.h" -#include "libdivecomputer/parser.h" -#include "mainwindow.h" -#include "maintab.h" -#include "profile/profilewidget2.h" -#include "diveplanner.h" - -#include - -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())); - 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(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 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 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 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 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/desktop-widgets/profile/diveprofileitem.h b/desktop-widgets/profile/diveprofileitem.h deleted file mode 100644 index 0bba7f7a3..000000000 --- a/desktop-widgets/profile/diveprofileitem.h +++ /dev/null @@ -1,225 +0,0 @@ -#ifndef DIVEPROFILEITEM_H -#define DIVEPROFILEITEM_H - -#include -#include -#include - -#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 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 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 align, double offset); - void plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags align, double offset); - QVector 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 alertPolygons; - double *thresholdPtr; - QString visibilityKey; - QColor normalColor; - QColor alertColor; -}; -#endif // DIVEPROFILEITEM_H diff --git a/desktop-widgets/profile/diverectitem.cpp b/desktop-widgets/profile/diverectitem.cpp deleted file mode 100644 index 8cb60c3f5..000000000 --- a/desktop-widgets/profile/diverectitem.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "diverectitem.h" - -DiveRectItem::DiveRectItem(QObject *parent, QGraphicsItem *parentItem) : QObject(parent), QGraphicsRectItem(parentItem) -{ -} diff --git a/desktop-widgets/profile/diverectitem.h b/desktop-widgets/profile/diverectitem.h deleted file mode 100644 index e616cf591..000000000 --- a/desktop-widgets/profile/diverectitem.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef DIVERECTITEM_H -#define DIVERECTITEM_H - -#include -#include - -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/desktop-widgets/profile/divetextitem.cpp b/desktop-widgets/profile/divetextitem.cpp deleted file mode 100644 index 3bf00d68f..000000000 --- a/desktop-widgets/profile/divetextitem.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "divetextitem.h" -#include "mainwindow.h" -#include "profilewidget2.h" -#include "subsurface-core/color.h" - -#include - -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(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/desktop-widgets/profile/divetextitem.h b/desktop-widgets/profile/divetextitem.h deleted file mode 100644 index be0adf292..000000000 --- a/desktop-widgets/profile/divetextitem.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef DIVETEXTITEM_H -#define DIVETEXTITEM_H - -#include -#include - -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/desktop-widgets/profile/divetooltipitem.cpp b/desktop-widgets/profile/divetooltipitem.cpp deleted file mode 100644 index d4818422b..000000000 --- a/desktop-widgets/profile/divetooltipitem.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "divetooltipitem.h" -#include "divecartesianaxis.h" -#include "dive.h" -#include "profile.h" -#include "membuffer.h" -#include "metrics.h" -#include -#include -#include -#include - -#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/desktop-widgets/profile/divetooltipitem.h b/desktop-widgets/profile/divetooltipitem.h deleted file mode 100644 index 4fa7ec2d7..000000000 --- a/desktop-widgets/profile/divetooltipitem.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef DIVETOOLTIPITEM_H -#define DIVETOOLTIPITEM_H - -#include -#include -#include -#include -#include -#include -#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 ToolTip; - QVector toolTips; - ToolTip entryToolTip; - QGraphicsSimpleTextItem *title; - Status status; - QRectF rectangle; - QRectF nextRectangle; - DiveCartesianAxis *timeAxis; - plot_info pInfo; - int lastTime; - QTime refreshTime; - QList oldSelection; -}; - -#endif // DIVETOOLTIPITEM_H diff --git a/desktop-widgets/profile/profilewidget2.cpp b/desktop-widgets/profile/profilewidget2.cpp deleted file mode 100644 index 3ccd1bb6d..000000000 --- a/desktop-widgets/profile/profilewidget2.cpp +++ /dev/null @@ -1,1836 +0,0 @@ -#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 -#include -#include -#include -#include -#include -#include - -#ifndef QT_NO_DEBUG -#include -#endif -#include "mainwindow.h" -#include - -/* This is the global 'Item position' variable. - * it should tell you where to position things up - * on the canvas. - * - * please, please, please, use this instead of - * hard coding the item on the scene with a random - * value. - */ -static struct _ItemPos { - struct _Pos { - QPointF on; - QPointF off; - }; - struct _Axis { - _Pos pos; - QLineF shrinked; - QLineF expanded; - QLineF intermediate; - }; - _Pos background; - _Pos dcLabel; - _Pos tankBar; - _Axis depth; - _Axis partialPressure; - _Axis partialPressureTissue; - _Axis partialPressureWithTankBar; - _Axis percentage; - _Axis percentageWithTankBar; - _Axis time; - _Axis cylinder; - _Axis temperature; - _Axis temperatureAll; - _Axis heartBeat; - _Axis heartBeatWithTankBar; -} itemPos; - -ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent), - currentState(INVALID), - dataModel(new DivePlotDataModel(this)), - zoomLevel(0), - zoomFactor(1.15), - background(new DivePixmapItem()), - backgroundFile(":poster"), - toolTipItem(new ToolTipItem()), - isPlotZoomed(prefs.zoomed_plot),// no! bad use of prefs. 'PreferencesDialog::loadSettings' NOT CALLED yet. - profileYAxis(new DepthAxis()), - gasYAxis(new PartialGasPressureAxis()), - temperatureAxis(new TemperatureAxis()), - timeAxis(new TimeAxis()), - diveProfileItem(new DiveProfileItem()), - temperatureItem(new DiveTemperatureItem()), - meanDepthItem(new DiveMeanDepthItem()), - cylinderPressureAxis(new DiveCartesianAxis()), - gasPressureItem(new DiveGasPressureItem()), - diveComputerText(new DiveTextItem()), - diveCeiling(new DiveCalculatedCeiling()), - gradientFactor(new DiveTextItem()), - reportedCeiling(new DiveReportedCeiling()), - pn2GasItem(new PartialPressureGasItem()), - pheGasItem(new PartialPressureGasItem()), - po2GasItem(new PartialPressureGasItem()), - o2SetpointGasItem(new PartialPressureGasItem()), - ccrsensor1GasItem(new PartialPressureGasItem()), - ccrsensor2GasItem(new PartialPressureGasItem()), - ccrsensor3GasItem(new PartialPressureGasItem()), - heartBeatAxis(new DiveCartesianAxis()), - heartBeatItem(new DiveHeartrateItem()), - percentageAxis(new DiveCartesianAxis()), - ambPressureItem(new DiveAmbPressureItem()), - gflineItem(new DiveGFLineItem()), - mouseFollowerVertical(new DiveLineItem()), - mouseFollowerHorizontal(new DiveLineItem()), - rulerItem(new RulerItem2()), - tankItem(new TankItem()), - isGrayscale(false), - printMode(false), - shouldCalculateMaxTime(true), - shouldCalculateMaxDepth(true), - fontPrintScale(1.0) -{ - // would like to be able to ASSERT here that PreferencesDialog::loadSettings has been called. - isPlotZoomed = prefs.zoomed_plot; // now it seems that 'prefs' has loaded our preferences - - memset(&plotInfo, 0, sizeof(plotInfo)); - - setupSceneAndFlags(); - setupItemSizes(); - setupItemOnScene(); - addItemsToScene(); - scene()->installEventFilter(this); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); - QAction *action = NULL; -#define ADD_ACTION(SHORTCUT, Slot) \ - action = new QAction(this); \ - action->setShortcut(SHORTCUT); \ - action->setShortcutContext(Qt::WindowShortcut); \ - addAction(action); \ - connect(action, SIGNAL(triggered(bool)), this, SLOT(Slot)); \ - actionsForKeys[SHORTCUT] = action; - - ADD_ACTION(Qt::Key_Escape, keyEscAction()); - ADD_ACTION(Qt::Key_Delete, keyDeleteAction()); - ADD_ACTION(Qt::Key_Up, keyUpAction()); - ADD_ACTION(Qt::Key_Down, keyDownAction()); - ADD_ACTION(Qt::Key_Left, keyLeftAction()); - ADD_ACTION(Qt::Key_Right, keyRightAction()); -#undef ADD_ACTION - -#if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE) - QTableView *diveDepthTableView = new QTableView(); - diveDepthTableView->setModel(dataModel); - diveDepthTableView->show(); -#endif -} - - -ProfileWidget2::~ProfileWidget2() -{ - delete background; - delete toolTipItem; - delete profileYAxis; - delete gasYAxis; - delete temperatureAxis; - delete timeAxis; - delete diveProfileItem; - delete temperatureItem; - delete meanDepthItem; - delete cylinderPressureAxis; - delete gasPressureItem; - delete diveComputerText; - delete diveCeiling; - delete reportedCeiling; - delete pn2GasItem; - delete pheGasItem; - delete po2GasItem; - delete o2SetpointGasItem; - delete ccrsensor1GasItem; - delete ccrsensor2GasItem; - delete ccrsensor3GasItem; - delete heartBeatAxis; - delete heartBeatItem; - delete percentageAxis; - delete ambPressureItem; - delete gflineItem; - delete mouseFollowerVertical; - delete mouseFollowerHorizontal; - delete rulerItem; - delete tankItem; -} - -#define SUBSURFACE_OBJ_DATA 1 -#define SUBSURFACE_OBJ_DC_TEXT 0x42 - -void ProfileWidget2::addItemsToScene() -{ - scene()->addItem(background); - scene()->addItem(toolTipItem); - scene()->addItem(profileYAxis); - scene()->addItem(gasYAxis); - scene()->addItem(temperatureAxis); - scene()->addItem(timeAxis); - scene()->addItem(diveProfileItem); - scene()->addItem(cylinderPressureAxis); - scene()->addItem(temperatureItem); - scene()->addItem(meanDepthItem); - scene()->addItem(gasPressureItem); - // I cannot seem to figure out if an object that I find with itemAt() on the scene - // is the object I am looking for - my guess is there's a simple way in Qt to do that - // but nothing I tried worked. - // so instead this adds a special magic key/value pair to the object to mark it - diveComputerText->setData(SUBSURFACE_OBJ_DATA, SUBSURFACE_OBJ_DC_TEXT); - scene()->addItem(diveComputerText); - scene()->addItem(diveCeiling); - scene()->addItem(gradientFactor); - scene()->addItem(reportedCeiling); - scene()->addItem(pn2GasItem); - scene()->addItem(pheGasItem); - scene()->addItem(po2GasItem); - scene()->addItem(o2SetpointGasItem); - scene()->addItem(ccrsensor1GasItem); - scene()->addItem(ccrsensor2GasItem); - scene()->addItem(ccrsensor3GasItem); - scene()->addItem(percentageAxis); - scene()->addItem(heartBeatAxis); - scene()->addItem(heartBeatItem); - scene()->addItem(rulerItem); - scene()->addItem(rulerItem->sourceNode()); - scene()->addItem(rulerItem->destNode()); - scene()->addItem(tankItem); - scene()->addItem(mouseFollowerHorizontal); - scene()->addItem(mouseFollowerVertical); - QPen pen(QColor(Qt::red).lighter()); - pen.setWidth(0); - mouseFollowerHorizontal->setPen(pen); - mouseFollowerVertical->setPen(pen); - Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) { - scene()->addItem(tissue); - } - Q_FOREACH (DivePercentageItem *percentage, allPercentages) { - scene()->addItem(percentage); - } - scene()->addItem(ambPressureItem); - scene()->addItem(gflineItem); -} - -void ProfileWidget2::setupItemOnScene() -{ - background->setZValue(9999); - toolTipItem->setZValue(9998); - toolTipItem->setTimeAxis(timeAxis); - rulerItem->setZValue(9997); - tankItem->setZValue(100); - - profileYAxis->setOrientation(DiveCartesianAxis::TopToBottom); - profileYAxis->setMinimum(0); - profileYAxis->setTickInterval(M_OR_FT(10, 30)); - profileYAxis->setTickSize(0.5); - profileYAxis->setLineSize(96); - - timeAxis->setLineSize(92); - timeAxis->setTickSize(-0.5); - - gasYAxis->setOrientation(DiveCartesianAxis::BottomToTop); - gasYAxis->setTickInterval(1); - gasYAxis->setTickSize(1); - gasYAxis->setMinimum(0); - gasYAxis->setModel(dataModel); - gasYAxis->setFontLabelScale(0.7); - gasYAxis->setLineSize(96); - - heartBeatAxis->setOrientation(DiveCartesianAxis::BottomToTop); - heartBeatAxis->setTickSize(0.2); - heartBeatAxis->setTickInterval(10); - heartBeatAxis->setFontLabelScale(0.7); - heartBeatAxis->setLineSize(96); - - percentageAxis->setOrientation(DiveCartesianAxis::BottomToTop); - percentageAxis->setTickSize(0.2); - percentageAxis->setTickInterval(10); - percentageAxis->setFontLabelScale(0.7); - percentageAxis->setLineSize(96); - - temperatureAxis->setOrientation(DiveCartesianAxis::BottomToTop); - temperatureAxis->setTickSize(2); - temperatureAxis->setTickInterval(300); - - cylinderPressureAxis->setOrientation(DiveCartesianAxis::BottomToTop); - cylinderPressureAxis->setTickSize(2); - cylinderPressureAxis->setTickInterval(30000); - - - diveComputerText->setAlignment(Qt::AlignRight | Qt::AlignTop); - diveComputerText->setBrush(getColor(TIME_TEXT, isGrayscale)); - - rulerItem->setAxis(timeAxis, profileYAxis); - tankItem->setHorizontalAxis(timeAxis); - - // show the gradient factor at the top in the center - gradientFactor->setY(0); - gradientFactor->setX(50); - gradientFactor->setBrush(getColor(PRESSURE_TEXT)); - gradientFactor->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - - setupItem(reportedCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1); - setupItem(diveCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1); - for (int i = 0; i < 16; i++) { - DiveCalculatedTissue *tissueItem = new DiveCalculatedTissue(); - setupItem(tissueItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::TISSUE_1 + i, DivePlotDataModel::TIME, 1 + i); - allTissues.append(tissueItem); - DivePercentageItem *percentageItem = new DivePercentageItem(i); - setupItem(percentageItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::PERCENTAGE_1 + i, DivePlotDataModel::TIME, 1 + i); - allPercentages.append(percentageItem); - } - setupItem(gasPressureItem, timeAxis, cylinderPressureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1); - setupItem(temperatureItem, timeAxis, temperatureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1); - setupItem(heartBeatItem, timeAxis, heartBeatAxis, dataModel, DivePlotDataModel::HEARTBEAT, DivePlotDataModel::TIME, 1); - setupItem(ambPressureItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::AMBPRESSURE, DivePlotDataModel::TIME, 1); - setupItem(gflineItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::GFLINE, DivePlotDataModel::TIME, 1); - setupItem(diveProfileItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::DEPTH, DivePlotDataModel::TIME, 0); - setupItem(meanDepthItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::INSTANT_MEANDEPTH, DivePlotDataModel::TIME, 1); - - -#define CREATE_PP_GAS(ITEM, VERTICAL_COLUMN, COLOR, COLOR_ALERT, THRESHOULD_SETTINGS, VISIBILITY_SETTINGS) \ - setupItem(ITEM, timeAxis, gasYAxis, dataModel, DivePlotDataModel::VERTICAL_COLUMN, DivePlotDataModel::TIME, 0); \ - ITEM->setThreshouldSettingsKey(THRESHOULD_SETTINGS); \ - ITEM->setVisibilitySettingsKey(VISIBILITY_SETTINGS); \ - ITEM->setColors(getColor(COLOR, isGrayscale), getColor(COLOR_ALERT, isGrayscale)); \ - ITEM->settingsChanged(); \ - ITEM->setZValue(99); - - CREATE_PP_GAS(pn2GasItem, PN2, PN2, PN2_ALERT, &prefs.pp_graphs.pn2_threshold, "pn2graph"); - CREATE_PP_GAS(pheGasItem, PHE, PHE, PHE_ALERT, &prefs.pp_graphs.phe_threshold, "phegraph"); - CREATE_PP_GAS(po2GasItem, PO2, PO2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph"); - CREATE_PP_GAS(o2SetpointGasItem, O2SETPOINT, PO2_ALERT, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph"); - CREATE_PP_GAS(ccrsensor1GasItem, CCRSENSOR1, CCRSENSOR1, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); - CREATE_PP_GAS(ccrsensor2GasItem, CCRSENSOR2, CCRSENSOR2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); - CREATE_PP_GAS(ccrsensor3GasItem, CCRSENSOR3, CCRSENSOR3, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); -#undef CREATE_PP_GAS - - temperatureAxis->setTextVisible(false); - temperatureAxis->setLinesVisible(false); - cylinderPressureAxis->setTextVisible(false); - cylinderPressureAxis->setLinesVisible(false); - timeAxis->setLinesVisible(true); - profileYAxis->setLinesVisible(true); - gasYAxis->setZValue(timeAxis->zValue() + 1); - heartBeatAxis->setTextVisible(true); - heartBeatAxis->setLinesVisible(true); - percentageAxis->setTextVisible(true); - percentageAxis->setLinesVisible(true); - - replotEnabled = true; -} - -void ProfileWidget2::replot(struct dive *d) -{ - if (!replotEnabled) - return; - dataModel->clear(); - plotDive(d, true); -} - -void ProfileWidget2::setupItemSizes() -{ - // Scene is *always* (double) 100 / 100. - // Background Config - /* Much probably a better math is needed here. - * good thing is that we only need to change the - * Axis and everything else is auto-adjusted.* - */ - - itemPos.background.on.setX(0); - itemPos.background.on.setY(0); - itemPos.background.off.setX(0); - itemPos.background.off.setY(110); - - //Depth Axis Config - itemPos.depth.pos.on.setX(3); - itemPos.depth.pos.on.setY(3); - itemPos.depth.pos.off.setX(-2); - itemPos.depth.pos.off.setY(3); - itemPos.depth.expanded.setP1(QPointF(0, 0)); - itemPos.depth.expanded.setP2(QPointF(0, 85)); - itemPos.depth.shrinked.setP1(QPointF(0, 0)); - itemPos.depth.shrinked.setP2(QPointF(0, 55)); - itemPos.depth.intermediate.setP1(QPointF(0, 0)); - itemPos.depth.intermediate.setP2(QPointF(0, 65)); - - // Time Axis Config - itemPos.time.pos.on.setX(3); - itemPos.time.pos.on.setY(95); - itemPos.time.pos.off.setX(3); - itemPos.time.pos.off.setY(110); - itemPos.time.expanded.setP1(QPointF(0, 0)); - itemPos.time.expanded.setP2(QPointF(94, 0)); - - // Partial Gas Axis Config - itemPos.partialPressure.pos.on.setX(97); - itemPos.partialPressure.pos.on.setY(75); - itemPos.partialPressure.pos.off.setX(110); - itemPos.partialPressure.pos.off.setY(63); - itemPos.partialPressure.expanded.setP1(QPointF(0, 0)); - itemPos.partialPressure.expanded.setP2(QPointF(0, 19)); - itemPos.partialPressureWithTankBar = itemPos.partialPressure; - itemPos.partialPressureWithTankBar.expanded.setP2(QPointF(0, 17)); - itemPos.partialPressureTissue = itemPos.partialPressure; - itemPos.partialPressureTissue.pos.on.setX(97); - itemPos.partialPressureTissue.pos.on.setY(65); - itemPos.partialPressureTissue.expanded.setP2(QPointF(0, 16)); - - // cylinder axis config - itemPos.cylinder.pos.on.setX(3); - itemPos.cylinder.pos.on.setY(20); - itemPos.cylinder.pos.off.setX(-10); - itemPos.cylinder.pos.off.setY(20); - itemPos.cylinder.expanded.setP1(QPointF(0, 15)); - itemPos.cylinder.expanded.setP2(QPointF(0, 50)); - itemPos.cylinder.shrinked.setP1(QPointF(0, 0)); - itemPos.cylinder.shrinked.setP2(QPointF(0, 20)); - itemPos.cylinder.intermediate.setP1(QPointF(0, 0)); - itemPos.cylinder.intermediate.setP2(QPointF(0, 20)); - - // Temperature axis config - itemPos.temperature.pos.on.setX(3); - itemPos.temperature.pos.on.setY(60); - itemPos.temperatureAll.pos.on.setY(51); - itemPos.temperature.pos.off.setX(-10); - itemPos.temperature.pos.off.setY(40); - itemPos.temperature.expanded.setP1(QPointF(0, 20)); - itemPos.temperature.expanded.setP2(QPointF(0, 33)); - itemPos.temperature.shrinked.setP1(QPointF(0, 2)); - itemPos.temperature.shrinked.setP2(QPointF(0, 12)); - itemPos.temperature.intermediate.setP1(QPointF(0, 2)); - itemPos.temperature.intermediate.setP2(QPointF(0, 12)); - - // Heartbeat axis config - itemPos.heartBeat.pos.on.setX(3); - itemPos.heartBeat.pos.on.setY(82); - itemPos.heartBeat.expanded.setP1(QPointF(0, 0)); - itemPos.heartBeat.expanded.setP2(QPointF(0, 10)); - itemPos.heartBeatWithTankBar = itemPos.heartBeat; - itemPos.heartBeatWithTankBar.expanded.setP2(QPointF(0, 7)); - - // Percentage axis config - itemPos.percentage.pos.on.setX(3); - itemPos.percentage.pos.on.setY(80); - itemPos.percentage.expanded.setP1(QPointF(0, 0)); - itemPos.percentage.expanded.setP2(QPointF(0, 15)); - itemPos.percentageWithTankBar = itemPos.percentage; - itemPos.percentageWithTankBar.expanded.setP2(QPointF(0, 12)); - - itemPos.dcLabel.on.setX(3); - itemPos.dcLabel.on.setY(100); - itemPos.dcLabel.off.setX(-10); - itemPos.dcLabel.off.setY(100); - - itemPos.tankBar.on.setX(0); - itemPos.tankBar.on.setY(91.5); -} - -void ProfileWidget2::setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *hAxis, - DiveCartesianAxis *vAxis, DivePlotDataModel *model, - int vData, int hData, int zValue) -{ - item->setHorizontalAxis(hAxis); - item->setVerticalAxis(vAxis); - item->setModel(model); - item->setVerticalDataColumn(vData); - item->setHorizontalDataColumn(hData); - item->setZValue(zValue); -} - -void ProfileWidget2::setupSceneAndFlags() -{ - setScene(new QGraphicsScene(this)); - scene()->setSceneRect(0, 0, 100, 100); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scene()->setItemIndexMethod(QGraphicsScene::NoIndex); - setOptimizationFlags(QGraphicsView::DontSavePainterState); - setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); - setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); - setMouseTracking(true); - background->setFlag(QGraphicsItem::ItemIgnoresTransformations); -} - -void ProfileWidget2::resetZoom() -{ - if (!zoomLevel) - return; - const qreal defScale = 1.0 / qPow(zoomFactor, (qreal)zoomLevel); - scale(defScale, defScale); - zoomLevel = 0; -} - -// Currently just one dive, but the plan is to enable All of the selected dives. -void ProfileWidget2::plotDive(struct dive *d, bool force) -{ - static bool firstCall = true; - QTime measureDuration; // let's measure how long this takes us (maybe we'll turn of TTL calculation later - measureDuration.start(); - - if (currentState != ADD && currentState != PLAN) { - if (!d) { - if (selected_dive == -1) - return; - d = current_dive; // display the current dive - } - - // No need to do this again if we are already showing the same dive - // computer of the same dive, so we check the unique id of the dive - // and the selected dive computer number against the ones we are - // showing (can't compare the dive pointers as those might change). - if (d->id == displayed_dive.id && dc_number == dataModel->dcShown() && !force) - return; - - // this copies the dive and makes copies of all the relevant additional data - copy_dive(d, &displayed_dive); - gradientFactor->setText(QString("GF %1/%2").arg(prefs.gflow).arg(prefs.gfhigh)); - } else { - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - plannerModel->createTemporaryPlan(); - struct diveplan &diveplan = plannerModel->getDiveplan(); - if (!diveplan.dp) { - plannerModel->deleteTemporaryPlan(); - return; - } - gradientFactor->setText(QString("GF %1/%2").arg(diveplan.gflow).arg(diveplan.gfhigh)); - } - - // special handling for the first time we display things - int animSpeedBackup = 0; - if (firstCall && MainWindow::instance()->filesFromCommandLine()) { - animSpeedBackup = prefs.animation_speed; - prefs.animation_speed = 0; - firstCall = false; - } - - // restore default zoom level - resetZoom(); - - // reset some item visibility on printMode changes - toolTipItem->setVisible(!printMode); - rulerItem->setVisible(prefs.rulergraph && !printMode && currentState != PLAN && currentState != ADD); - - if (currentState == EMPTY) - setProfileState(); - - // next get the dive computer structure - if there are no samples - // let's create a fake profile that's somewhat reasonable for the - // data that we have - struct divecomputer *currentdc = select_dc(&displayed_dive); - Q_ASSERT(currentdc); - if (!currentdc || !currentdc->samples) { - currentdc = fake_dc(currentdc); - } - - bool setpointflag = (currentdc->divemode == CCR) && prefs.pp_graphs.po2 && current_dive; - bool sensorflag = setpointflag && prefs.show_ccr_sensors; - o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint); - ccrsensor1GasItem->setVisible(sensorflag); - ccrsensor2GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 1)); - ccrsensor3GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 2)); - - /* This struct holds all the data that's about to be plotted. - * I'm not sure this is the best approach ( but since we are - * interpolating some points of the Dive, maybe it is... ) - * The Calculation of the points should be done per graph, - * so I'll *not* calculate everything if something is not being - * shown. - */ - plotInfo = calculate_max_limits_new(&displayed_dive, currentdc); - create_plot_info_new(&displayed_dive, currentdc, &plotInfo, !shouldCalculateMaxDepth); - if (shouldCalculateMaxTime) - maxtime = get_maxtime(&plotInfo); - - /* Only update the max depth if it's bigger than the current ones - * when we are dragging the handler to plan / add dive. - * otherwhise, update normally. - */ - int newMaxDepth = get_maxdepth(&plotInfo); - if (!shouldCalculateMaxDepth) { - if (maxdepth < newMaxDepth) { - maxdepth = newMaxDepth; - } - } else { - maxdepth = newMaxDepth; - } - - dataModel->setDive(&displayed_dive, plotInfo); - toolTipItem->setPlotInfo(plotInfo); - - // It seems that I'll have a lot of boilerplate setting the model / axis for - // each item, I'll mostly like to fix this in the future, but I'll keep at this for now. - profileYAxis->setMaximum(maxdepth); - profileYAxis->updateTicks(); - - temperatureAxis->setMinimum(plotInfo.mintemp); - temperatureAxis->setMaximum(plotInfo.maxtemp - plotInfo.mintemp > 2000 ? plotInfo.maxtemp : plotInfo.mintemp + 2000); - - if (plotInfo.maxhr) { - heartBeatAxis->setMinimum(plotInfo.minhr); - heartBeatAxis->setMaximum(plotInfo.maxhr); - heartBeatAxis->updateTicks(HR_AXIS); // this shows the ticks - } - heartBeatAxis->setVisible(prefs.hrgraph && plotInfo.maxhr); - - percentageAxis->setMinimum(0); - percentageAxis->setMaximum(100); - percentageAxis->setVisible(false); - percentageAxis->updateTicks(HR_AXIS); - - timeAxis->setMaximum(maxtime); - int i, incr; - static int increments[8] = { 10, 20, 30, 60, 5 * 60, 10 * 60, 15 * 60, 30 * 60 }; - /* Time markers: at most every 10 seconds, but no more than 12 markers. - * We start out with 10 seconds and increment up to 30 minutes, - * depending on the dive time. - * This allows for 6h dives - enough (I hope) for even the craziest - * divers - but just in case, for those 8h depth-record-breaking dives, - * we double the interval if this still doesn't get us to 12 or fewer - * time markers */ - i = 0; - while (i < 7 && maxtime / increments[i] > 12) - i++; - incr = increments[i]; - while (maxtime / incr > 12) - incr *= 2; - timeAxis->setTickInterval(incr); - timeAxis->updateTicks(); - cylinderPressureAxis->setMinimum(plotInfo.minpressure); - cylinderPressureAxis->setMaximum(plotInfo.maxpressure); - - rulerItem->setPlotInfo(plotInfo); - tankItem->setData(dataModel, &plotInfo, &displayed_dive); - - dataModel->emitDataChanged(); - // The event items are a bit special since we don't know how many events are going to - // exist on a dive, so I cant create cache items for that. that's why they are here - // while all other items are up there on the constructor. - qDeleteAll(eventItems); - eventItems.clear(); - struct event *event = currentdc->events; - while (event) { - // if print mode is selected only draw headings, SP change, gas events or bookmark event - if (printMode) { - if (same_string(event->name, "") || - !(strcmp(event->name, "heading") == 0 || - (same_string(event->name, "SP change") && event->time.seconds == 0) || - event_is_gaschange(event) || - event->type == SAMPLE_EVENT_BOOKMARK)) { - event = event->next; - continue; - } - } - DiveEventItem *item = new DiveEventItem(); - item->setHorizontalAxis(timeAxis); - item->setVerticalAxis(profileYAxis); - item->setModel(dataModel); - item->setEvent(event); - item->setZValue(2); - scene()->addItem(item); - eventItems.push_back(item); - event = event->next; - } - // Only set visible the events that should be visible - Q_FOREACH (DiveEventItem *event, eventItems) { - event->setVisible(!event->shouldBeHidden()); - } - QString dcText = get_dc_nickname(currentdc->model, currentdc->deviceid); - int nr; - if ((nr = number_of_computers(&displayed_dive)) > 1) - dcText += tr(" (#%1 of %2)").arg(dc_number + 1).arg(nr); - if (dcText.isEmpty()) - dcText = tr("Unknown dive computer"); - diveComputerText->setText(dcText); - if (MainWindow::instance()->filesFromCommandLine() && animSpeedBackup != 0) { - prefs.animation_speed = animSpeedBackup; - } - - if (currentState == ADD || currentState == PLAN) { // TODO: figure a way to move this from here. - repositionDiveHandlers(); - DivePlannerPointsModel *model = DivePlannerPointsModel::instance(); - model->deleteTemporaryPlan(); - } - plotPictures(); - - // OK, how long did this take us? Anything above the second is way too long, - // so if we are calculation TTS / NDL then let's force that off. - if (measureDuration.elapsed() > 1000 && prefs.calcndltts) { - MainWindow::instance()->turnOffNdlTts(); - MainWindow::instance()->getNotificationWidget()->showNotification(tr("Show NDL / TTS was disabled because of excessive processing time"), KMessageWidget::Error); - } - MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); - -} - -void ProfileWidget2::recalcCeiling() -{ - diveCeiling->recalc(); -} - -void ProfileWidget2::settingsChanged() -{ - // if we are showing calculated ceilings then we have to replot() - // because the GF could have changed; otherwise we try to avoid replot() - bool needReplot = prefs.calcceiling; - if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) { - profileYAxis->animateChangeLine(itemPos.depth.shrinked); - temperatureAxis->setPos(itemPos.temperatureAll.pos.on); - temperatureAxis->animateChangeLine(itemPos.temperature.shrinked); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked); - - if (prefs.tankbar) { - percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); - percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); - heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); - }else { - percentageAxis->setPos(itemPos.percentage.pos.on); - percentageAxis->animateChangeLine(itemPos.percentage.expanded); - heartBeatAxis->setPos(itemPos.heartBeat.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); - } - gasYAxis->setPos(itemPos.partialPressureTissue.pos.on); - gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded); - - } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) { - profileYAxis->animateChangeLine(itemPos.depth.intermediate); - temperatureAxis->setPos(itemPos.temperature.pos.on); - temperatureAxis->animateChangeLine(itemPos.temperature.intermediate); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate); - if (prefs.tankbar) { - percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); - percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); - gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on); - gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded); - heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); - } else { - gasYAxis->setPos(itemPos.partialPressure.pos.on); - gasYAxis->animateChangeLine(itemPos.partialPressure.expanded); - percentageAxis->setPos(itemPos.percentage.pos.on); - percentageAxis->setLine(itemPos.percentage.expanded); - heartBeatAxis->setPos(itemPos.heartBeat.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); - } - } else { - profileYAxis->animateChangeLine(itemPos.depth.expanded); - if (prefs.tankbar) { - temperatureAxis->setPos(itemPos.temperatureAll.pos.on); - } else { - temperatureAxis->setPos(itemPos.temperature.pos.on); - } - temperatureAxis->animateChangeLine(itemPos.temperature.expanded); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded); - } - - tankItem->setVisible(prefs.tankbar); - if (prefs.zoomed_plot != isPlotZoomed) { - isPlotZoomed = prefs.zoomed_plot; - needReplot = true; - } - if (needReplot) - replot(); -} - -void ProfileWidget2::resizeEvent(QResizeEvent *event) -{ - QGraphicsView::resizeEvent(event); - fitInView(sceneRect(), Qt::IgnoreAspectRatio); - fixBackgroundPos(); -} - -void ProfileWidget2::mousePressEvent(QMouseEvent *event) -{ - if (zoomLevel) - return; - QGraphicsView::mousePressEvent(event); - if (currentState == PLAN) - shouldCalculateMaxTime = false; -} - -void ProfileWidget2::divePlannerHandlerClicked() -{ - if (zoomLevel) - return; - shouldCalculateMaxDepth = false; - replot(); -} - -void ProfileWidget2::divePlannerHandlerReleased() -{ - if (zoomLevel) - return; - shouldCalculateMaxDepth = true; - replot(); -} - -void ProfileWidget2::mouseReleaseEvent(QMouseEvent *event) -{ - if (zoomLevel) - return; - QGraphicsView::mouseReleaseEvent(event); - if (currentState == PLAN) { - shouldCalculateMaxTime = true; - replot(); - } -} - -void ProfileWidget2::fixBackgroundPos() -{ - static QPixmap toBeScaled(backgroundFile); - if (currentState != EMPTY) - return; - QPixmap p = toBeScaled.scaledToHeight(viewport()->height() - 40, Qt::SmoothTransformation); - int x = viewport()->width() / 2 - p.width() / 2; - int y = viewport()->height() / 2 - p.height() / 2; - background->setPixmap(p); - background->setX(mapToScene(x, 0).x()); - background->setY(mapToScene(y, 20).y()); -} - -void ProfileWidget2::wheelEvent(QWheelEvent *event) -{ - if (currentState == EMPTY) - return; - QPoint toolTipPos = mapFromScene(toolTipItem->pos()); - if (event->buttons() == Qt::LeftButton) - return; - if (event->delta() > 0 && zoomLevel < 20) { - scale(zoomFactor, zoomFactor); - zoomLevel++; - } else if (event->delta() < 0 && zoomLevel > 0) { - // Zooming out - scale(1.0 / zoomFactor, 1.0 / zoomFactor); - zoomLevel--; - } - scrollViewTo(event->pos()); - toolTipItem->setPos(mapToScene(toolTipPos)); -} - -void ProfileWidget2::mouseDoubleClickEvent(QMouseEvent *event) -{ - if (currentState == PLAN || currentState == ADD) { - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - QPointF mappedPos = mapToScene(event->pos()); - if (isPointOutOfBoundaries(mappedPos)) - return; - - int minutes = rint(timeAxis->valueAt(mappedPos) / 60); - int milimeters = rint(profileYAxis->valueAt(mappedPos) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); - plannerModel->addStop(milimeters, minutes * 60, 0, 0, true); - } -} - -bool ProfileWidget2::isPointOutOfBoundaries(const QPointF &point) const -{ - double xpos = timeAxis->valueAt(point); - double ypos = profileYAxis->valueAt(point); - return (xpos > timeAxis->maximum() || - xpos < timeAxis->minimum() || - ypos > profileYAxis->maximum() || - ypos < profileYAxis->minimum()); -} - -void ProfileWidget2::scrollViewTo(const QPoint &pos) -{ - /* since we cannot use translate() directly on the scene we hack on - * the scroll bars (hidden) functionality */ - if (!zoomLevel || currentState == EMPTY) - return; - QScrollBar *vs = verticalScrollBar(); - QScrollBar *hs = horizontalScrollBar(); - const qreal yRat = (qreal)pos.y() / viewport()->height(); - const qreal xRat = (qreal)pos.x() / viewport()->width(); - vs->setValue(yRat * vs->maximum()); - hs->setValue(xRat * hs->maximum()); -} - -void ProfileWidget2::mouseMoveEvent(QMouseEvent *event) -{ - QPointF pos = mapToScene(event->pos()); - toolTipItem->refresh(pos); - if (zoomLevel == 0) { - QGraphicsView::mouseMoveEvent(event); - } else { - QPoint toolTipPos = mapFromScene(toolTipItem->pos()); - scrollViewTo(event->pos()); - toolTipItem->setPos(mapToScene(toolTipPos)); - } - - qreal vValue = profileYAxis->valueAt(pos); - qreal hValue = timeAxis->valueAt(pos); - if (profileYAxis->maximum() >= vValue && profileYAxis->minimum() <= vValue) { - mouseFollowerHorizontal->setPos(timeAxis->pos().x(), pos.y()); - } - if (timeAxis->maximum() >= hValue && timeAxis->minimum() <= hValue) { - mouseFollowerVertical->setPos(pos.x(), profileYAxis->line().y1()); - } -} - -bool ProfileWidget2::eventFilter(QObject *object, QEvent *event) -{ - QGraphicsScene *s = qobject_cast(object); - if (s && event->type() == QEvent::GraphicsSceneHelp) { - event->ignore(); - return true; - } - return QGraphicsView::eventFilter(object, event); -} - -void ProfileWidget2::setEmptyState() -{ - // Then starting Empty State, move the background up. - if (currentState == EMPTY) - return; - - disconnectTemporaryConnections(); - setBackgroundBrush(getColor(::BACKGROUND, isGrayscale)); - dataModel->clear(); - currentState = EMPTY; - MainWindow::instance()->setEnabledToolbar(false); - - fixBackgroundPos(); - background->setVisible(true); - - profileYAxis->setVisible(false); - gasYAxis->setVisible(false); - timeAxis->setVisible(false); - temperatureAxis->setVisible(false); - cylinderPressureAxis->setVisible(false); - toolTipItem->setVisible(false); - diveComputerText->setVisible(false); - diveCeiling->setVisible(false); - gradientFactor->setVisible(false); - reportedCeiling->setVisible(false); - rulerItem->setVisible(false); - tankItem->setVisible(false); - pn2GasItem->setVisible(false); - po2GasItem->setVisible(false); - o2SetpointGasItem->setVisible(false); - ccrsensor1GasItem->setVisible(false); - ccrsensor2GasItem->setVisible(false); - ccrsensor3GasItem->setVisible(false); - pheGasItem->setVisible(false); - ambPressureItem->setVisible(false); - gflineItem->setVisible(false); - mouseFollowerHorizontal->setVisible(false); - mouseFollowerVertical->setVisible(false); - -#define HIDE_ALL(TYPE, CONTAINER) \ - Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false); - HIDE_ALL(DiveCalculatedTissue, allTissues); - HIDE_ALL(DivePercentageItem, allPercentages); - HIDE_ALL(DiveEventItem, eventItems); - HIDE_ALL(DiveHandler, handles); - HIDE_ALL(QGraphicsSimpleTextItem, gases); -#undef HIDE_ALL -} - -void ProfileWidget2::setProfileState() -{ - // Then starting Empty State, move the background up. - if (currentState == PROFILE) - return; - - disconnectTemporaryConnections(); - connect(DivePictureModel::instance(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(plotPictures())); - connect(DivePictureModel::instance(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(plotPictures())); - connect(DivePictureModel::instance(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(plotPictures())); - /* show the same stuff that the profile shows. */ - - //TODO: Move the DC handling to another method. - MainWindow::instance()->enableShortcuts(); - - currentState = PROFILE; - MainWindow::instance()->setEnabledToolbar(true); - toolTipItem->readPos(); - setBackgroundBrush(getColor(::BACKGROUND, isGrayscale)); - - background->setVisible(false); - toolTipItem->setVisible(true); - profileYAxis->setVisible(true); - gasYAxis->setVisible(true); - timeAxis->setVisible(true); - temperatureAxis->setVisible(true); - cylinderPressureAxis->setVisible(true); - - profileYAxis->setPos(itemPos.depth.pos.on); - if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) { - profileYAxis->animateChangeLine(itemPos.depth.shrinked); - temperatureAxis->setPos(itemPos.temperatureAll.pos.on); - temperatureAxis->animateChangeLine(itemPos.temperature.shrinked); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked); - - if (prefs.tankbar) { - percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); - percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); - heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); - }else { - percentageAxis->setPos(itemPos.percentage.pos.on); - percentageAxis->animateChangeLine(itemPos.percentage.expanded); - heartBeatAxis->setPos(itemPos.heartBeat.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); - } - gasYAxis->setPos(itemPos.partialPressureTissue.pos.on); - gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded); - - } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) { - profileYAxis->animateChangeLine(itemPos.depth.intermediate); - temperatureAxis->setPos(itemPos.temperature.pos.on); - temperatureAxis->animateChangeLine(itemPos.temperature.intermediate); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate); - if (prefs.tankbar) { - percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); - percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); - gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on); - gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded); - heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); - } else { - gasYAxis->setPos(itemPos.partialPressure.pos.on); - gasYAxis->animateChangeLine(itemPos.partialPressure.expanded); - percentageAxis->setPos(itemPos.percentage.pos.on); - percentageAxis->setLine(itemPos.percentage.expanded); - heartBeatAxis->setPos(itemPos.heartBeat.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); - } - } else { - profileYAxis->animateChangeLine(itemPos.depth.expanded); - if (prefs.tankbar) { - temperatureAxis->setPos(itemPos.temperatureAll.pos.on); - } else { - temperatureAxis->setPos(itemPos.temperature.pos.on); - } - temperatureAxis->animateChangeLine(itemPos.temperature.expanded); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded); - } - pn2GasItem->setVisible(prefs.pp_graphs.pn2); - po2GasItem->setVisible(prefs.pp_graphs.po2); - pheGasItem->setVisible(prefs.pp_graphs.phe); - - bool setpointflag = current_dive && (current_dc->divemode == CCR) && prefs.pp_graphs.po2; - bool sensorflag = setpointflag && prefs.show_ccr_sensors; - o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint); - ccrsensor1GasItem->setVisible(sensorflag); - ccrsensor2GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 1)); - ccrsensor3GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 2)); - - timeAxis->setPos(itemPos.time.pos.on); - timeAxis->setLine(itemPos.time.expanded); - - cylinderPressureAxis->setPos(itemPos.cylinder.pos.on); - heartBeatItem->setVisible(prefs.hrgraph); - meanDepthItem->setVisible(prefs.show_average_depth); - - diveComputerText->setVisible(true); - diveComputerText->setPos(itemPos.dcLabel.on); - - diveCeiling->setVisible(prefs.calcceiling); - gradientFactor->setVisible(prefs.calcceiling); - reportedCeiling->setVisible(prefs.dcceiling); - - if (prefs.calcalltissues) { - Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) { - tissue->setVisible(true); - } - } - - if (prefs.percentagegraph) { - Q_FOREACH (DivePercentageItem *percentage, allPercentages) { - percentage->setVisible(true); - } - - ambPressureItem->setVisible(true); - gflineItem->setVisible(true); - } - - rulerItem->setVisible(prefs.rulergraph); - tankItem->setVisible(prefs.tankbar); - tankItem->setPos(itemPos.tankBar.on); - -#define HIDE_ALL(TYPE, CONTAINER) \ - Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false); - HIDE_ALL(DiveHandler, handles); - HIDE_ALL(QGraphicsSimpleTextItem, gases); -#undef HIDE_ALL - mouseFollowerHorizontal->setVisible(false); - mouseFollowerVertical->setVisible(false); -} - -void ProfileWidget2::clearHandlers() -{ - if (handles.count()) { - foreach (DiveHandler *handle, handles) { - scene()->removeItem(handle); - delete handle; - } - handles.clear(); - } -} - -void ProfileWidget2::setToolTipVisibile(bool visible) -{ - toolTipItem->setVisible(visible); -} - -void ProfileWidget2::setAddState() -{ - if (currentState == ADD) - return; - - clearHandlers(); - setProfileState(); - mouseFollowerHorizontal->setVisible(true); - mouseFollowerVertical->setVisible(true); - mouseFollowerHorizontal->setLine(timeAxis->line()); - mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y())); - disconnectTemporaryConnections(); - //TODO: Move this method to another place, shouldn't be on mainwindow. - MainWindow::instance()->disableShortcuts(false); - actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left); - actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right); - actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up); - actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down); - actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape); - actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete); - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); - connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); - connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(pointInserted(const QModelIndex &, int, int))); - connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(pointsRemoved(const QModelIndex &, int, int))); - /* show the same stuff that the profile shows. */ - currentState = ADD; /* enable the add state. */ - diveCeiling->setVisible(true); - gradientFactor->setVisible(true); - setBackgroundBrush(QColor("#A7DCFF")); -} - -void ProfileWidget2::setPlanState() -{ - if (currentState == PLAN) - return; - - setProfileState(); - mouseFollowerHorizontal->setVisible(true); - mouseFollowerVertical->setVisible(true); - mouseFollowerHorizontal->setLine(timeAxis->line()); - mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y())); - disconnectTemporaryConnections(); - //TODO: Move this method to another place, shouldn't be on mainwindow. - MainWindow::instance()->disableShortcuts(); - actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left); - actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right); - actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up); - actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down); - actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape); - actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete); - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); - connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); - connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(pointInserted(const QModelIndex &, int, int))); - connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(pointsRemoved(const QModelIndex &, int, int))); - /* show the same stuff that the profile shows. */ - currentState = PLAN; /* enable the add state. */ - diveCeiling->setVisible(true); - gradientFactor->setVisible(true); - setBackgroundBrush(QColor("#D7E3EF")); -} - -extern struct ev_select *ev_namelist; -extern int evn_allocated; -extern int evn_used; - -bool ProfileWidget2::isPlanner() -{ - return currentState == PLAN; -} - -bool ProfileWidget2::isAddOrPlanner() -{ - return currentState == PLAN || currentState == ADD; -} - -struct plot_data *ProfileWidget2::getEntryFromPos(QPointF pos) -{ - // find the time stamp corresponding to the mouse position - int seconds = timeAxis->valueAt(pos); - struct plot_data *entry = NULL; - - for (int i = 0; i < plotInfo.nr; i++) { - entry = plotInfo.entry + i; - if (entry->sec >= seconds) - break; - } - return entry; -} - -void ProfileWidget2::setReplot(bool state) -{ - replotEnabled = state; -} - -void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) -{ - if (currentState == ADD || currentState == PLAN) { - QGraphicsView::contextMenuEvent(event); - return; - } - QMenu m; - bool isDCName = false; - if (selected_dive == -1) - return; - // figure out if we are ontop of the dive computer name in the profile - QGraphicsItem *sceneItem = itemAt(mapFromGlobal(event->globalPos())); - if (sceneItem) { - QGraphicsItem *parentItem = sceneItem; - while (parentItem) { - if (parentItem->data(SUBSURFACE_OBJ_DATA) == SUBSURFACE_OBJ_DC_TEXT) { - isDCName = true; - break; - } - parentItem = parentItem->parentItem(); - } - if (isDCName) { - if (dc_number == 0 && count_divecomputers() == 1) - // nothing to do, can't delete or reorder - return; - // create menu to show when right clicking on dive computer name - if (dc_number > 0) - m.addAction(tr("Make first divecomputer"), this, SLOT(makeFirstDC())); - if (count_divecomputers() > 1) - m.addAction(tr("Delete this divecomputer"), this, SLOT(deleteCurrentDC())); - m.exec(event->globalPos()); - // don't show the regular profile context menu - return; - } - } - // create the profile context menu - QPointF scenePos = mapToScene(event->pos()); - struct plot_data *entry = getEntryFromPos(scenePos); - GasSelectionModel *model = GasSelectionModel::instance(); - model->repopulate(); - int rowCount = model->rowCount(); - if (rowCount > 1) { - // if we have more than one gas, offer to switch to another one - QMenu *gasChange = m.addMenu(tr("Add gas change")); - for (int i = 0; i < rowCount; i++) { - QAction *action = new QAction(&m); - action->setText(model->data(model->index(i, 0), Qt::DisplayRole).toString() + QString(tr(" (Tank %1)")).arg(i + 1)); - connect(action, SIGNAL(triggered(bool)), this, SLOT(changeGas())); - action->setData(event->globalPos()); - if (i == entry->cylinderindex) - action->setDisabled(true); - gasChange->addAction(action); - } - } - QAction *setpointAction = m.addAction(tr("Add set-point change"), this, SLOT(addSetpointChange())); - setpointAction->setData(event->globalPos()); - QAction *action = m.addAction(tr("Add bookmark"), this, SLOT(addBookmark())); - action->setData(event->globalPos()); - - if (same_string(current_dc->model, "manually added dive")) - QAction *editProfileAction = m.addAction(tr("Edit the profile"), MainWindow::instance(), SLOT(editCurrentDive())); - - if (DiveEventItem *item = dynamic_cast(sceneItem)) { - action = new QAction(&m); - action->setText(tr("Remove event")); - action->setData(QVariant::fromValue(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(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(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(sender()); - DiveEventItem *item = static_cast(action->data().value()); - 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(sender()); - DiveEventItem *item = static_cast(action->data().value()); - 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(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(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(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(sender()); - DiveEventItem *item = static_cast(action->data().value()); - 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(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(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(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(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(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 selectedIndexes; - Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { - if (DiveHandler *handler = qgraphicsitem_cast(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(); - // 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()); - 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/desktop-widgets/profile/profilewidget2.h b/desktop-widgets/profile/profilewidget2.h deleted file mode 100644 index f11ec5be1..000000000 --- a/desktop-widgets/profile/profilewidget2.h +++ /dev/null @@ -1,211 +0,0 @@ -#ifndef PROFILEWIDGET2_H -#define PROFILEWIDGET2_H - -#include - -// /* 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 eventItems; - DiveTextItem *diveComputerText; - DiveCalculatedCeiling *diveCeiling; - DiveTextItem *gradientFactor; - QList allTissues; - DiveReportedCeiling *reportedCeiling; - PartialPressureGasItem *pn2GasItem; - PartialPressureGasItem *pheGasItem; - PartialPressureGasItem *po2GasItem; - PartialPressureGasItem *o2SetpointGasItem; - PartialPressureGasItem *ccrsensor1GasItem; - PartialPressureGasItem *ccrsensor2GasItem; - PartialPressureGasItem *ccrsensor3GasItem; - DiveCartesianAxis *heartBeatAxis; - DiveHeartrateItem *heartBeatItem; - DiveCartesianAxis *percentageAxis; - QList allPercentages; - DiveAmbPressureItem *ambPressureItem; - DiveGFLineItem *gflineItem; - DiveLineItem *mouseFollowerVertical; - DiveLineItem *mouseFollowerHorizontal; - RulerItem2 *rulerItem; - TankItem *tankItem; - bool isGrayscale; - bool printMode; - - //specifics for ADD and PLAN - QList handles; - QList gases; - QList pictures; - void repositionDiveHandlers(); - int fixHandlerIndex(DiveHandler *activeHandler); - friend class DiveHandler; - QHash actionsForKeys; - bool shouldCalculateMaxTime; - bool shouldCalculateMaxDepth; - int maxtime; - int maxdepth; - double fontPrintScale; -}; - -#endif // PROFILEWIDGET2_H diff --git a/desktop-widgets/profile/ruleritem.cpp b/desktop-widgets/profile/ruleritem.cpp deleted file mode 100644 index 830985552..000000000 --- a/desktop-widgets/profile/ruleritem.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "ruleritem.h" -#include "preferences.h" -#include "mainwindow.h" -#include "profilewidget2.h" -#include "display.h" - -#include - -#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(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/desktop-widgets/profile/ruleritem.h b/desktop-widgets/profile/ruleritem.h deleted file mode 100644 index 4fad0451c..000000000 --- a/desktop-widgets/profile/ruleritem.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef RULERITEM_H -#define RULERITEM_H - -#include -#include -#include -#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/desktop-widgets/profile/tankitem.cpp b/desktop-widgets/profile/tankitem.cpp deleted file mode 100644 index c0e75a371..000000000 --- a/desktop-widgets/profile/tankitem.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "tankitem.h" -#include "diveplotdatamodel.h" -#include "divetextitem.h" -#include "profile.h" -#include - -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/desktop-widgets/profile/tankitem.h b/desktop-widgets/profile/tankitem.h deleted file mode 100644 index fd685fc82..000000000 --- a/desktop-widgets/profile/tankitem.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef TANKITEM_H -#define TANKITEM_H - -#include -#include -#include -#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 rects; -}; - -#endif // TANKITEM_H diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index 62a9cc646..43ad1ddc0 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -14,7 +14,7 @@ #include "libdivecomputer/parser.h" #include "divelistview.h" #include "display.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "undocommands.h" class MinMaxAvgWidgetPrivate { diff --git a/desktop-widgets/socialnetworks.cpp b/desktop-widgets/socialnetworks.cpp index 6e191267a..0794c764e 100644 --- a/desktop-widgets/socialnetworks.cpp +++ b/desktop-widgets/socialnetworks.cpp @@ -16,7 +16,7 @@ #include #include #include "mainwindow.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "pref.h" #include "helpers.h" #include "ui_socialnetworksdialog.h" 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 + +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 +#include + +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..bf5a5380c --- /dev/null +++ b/profile-widget/divecartesianaxis.cpp @@ -0,0 +1,459 @@ +#include "divecartesianaxis.h" +#include "divetextitem.h" +#include "helpers.h" +#include "preferences.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 +void emptyList(QList &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 +#include +#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 labels; + QList 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 +#include + +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..581f6f9b4 --- /dev/null +++ b/profile-widget/divepixmapitem.cpp @@ -0,0 +1,130 @@ +#include "divepixmapitem.h" +#include "animationfunctions.h" +#include "divepicturemodel.h" +#include + +#include +#include +#include + +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 +#include + +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..7cdccee32 --- /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.h" +#include "diveplannermodel.h" +#include "helpers.h" +#include "libdivecomputer/parser.h" +#include "mainwindow.h" +#include "maintab.h" +#include "profilewidget2.h" +#include "diveplanner.h" + +#include + +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())); + 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(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 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 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 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 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 +#include +#include + +#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 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 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 align, double offset); + void plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags align, double offset); + QVector 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 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 +#include + +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 + +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(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 +#include + +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 +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#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 ToolTip; + QVector toolTips; + ToolTip entryToolTip; + QGraphicsSimpleTextItem *title; + Status status; + QRectF rectangle; + QRectF nextRectangle; + DiveCartesianAxis *timeAxis; + plot_info pInfo; + int lastTime; + QTime refreshTime; + QList oldSelection; +}; + +#endif // DIVETOOLTIPITEM_H diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp new file mode 100644 index 000000000..3ccd1bb6d --- /dev/null +++ b/profile-widget/profilewidget2.cpp @@ -0,0 +1,1836 @@ +#include "profilewidget2.h" +#include "diveplotdatamodel.h" +#include "helpers.h" +#include "profile.h" +#include "diveeventitem.h" +#include "divetextitem.h" +#include "divetooltipitem.h" +#include "planner.h" +#include "device.h" +#include "ruleritem.h" +#include "tankitem.h" +#include "pref.h" +#include "divepicturewidget.h" +#include "diveplannermodel.h" +#include "models.h" +#include "divepicturemodel.h" +#include "maintab.h" +#include "diveplanner.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_DEBUG +#include +#endif +#include "mainwindow.h" +#include + +/* This is the global 'Item position' variable. + * it should tell you where to position things up + * on the canvas. + * + * please, please, please, use this instead of + * hard coding the item on the scene with a random + * value. + */ +static struct _ItemPos { + struct _Pos { + QPointF on; + QPointF off; + }; + struct _Axis { + _Pos pos; + QLineF shrinked; + QLineF expanded; + QLineF intermediate; + }; + _Pos background; + _Pos dcLabel; + _Pos tankBar; + _Axis depth; + _Axis partialPressure; + _Axis partialPressureTissue; + _Axis partialPressureWithTankBar; + _Axis percentage; + _Axis percentageWithTankBar; + _Axis time; + _Axis cylinder; + _Axis temperature; + _Axis temperatureAll; + _Axis heartBeat; + _Axis heartBeatWithTankBar; +} itemPos; + +ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent), + currentState(INVALID), + dataModel(new DivePlotDataModel(this)), + zoomLevel(0), + zoomFactor(1.15), + background(new DivePixmapItem()), + backgroundFile(":poster"), + toolTipItem(new ToolTipItem()), + isPlotZoomed(prefs.zoomed_plot),// no! bad use of prefs. 'PreferencesDialog::loadSettings' NOT CALLED yet. + profileYAxis(new DepthAxis()), + gasYAxis(new PartialGasPressureAxis()), + temperatureAxis(new TemperatureAxis()), + timeAxis(new TimeAxis()), + diveProfileItem(new DiveProfileItem()), + temperatureItem(new DiveTemperatureItem()), + meanDepthItem(new DiveMeanDepthItem()), + cylinderPressureAxis(new DiveCartesianAxis()), + gasPressureItem(new DiveGasPressureItem()), + diveComputerText(new DiveTextItem()), + diveCeiling(new DiveCalculatedCeiling()), + gradientFactor(new DiveTextItem()), + reportedCeiling(new DiveReportedCeiling()), + pn2GasItem(new PartialPressureGasItem()), + pheGasItem(new PartialPressureGasItem()), + po2GasItem(new PartialPressureGasItem()), + o2SetpointGasItem(new PartialPressureGasItem()), + ccrsensor1GasItem(new PartialPressureGasItem()), + ccrsensor2GasItem(new PartialPressureGasItem()), + ccrsensor3GasItem(new PartialPressureGasItem()), + heartBeatAxis(new DiveCartesianAxis()), + heartBeatItem(new DiveHeartrateItem()), + percentageAxis(new DiveCartesianAxis()), + ambPressureItem(new DiveAmbPressureItem()), + gflineItem(new DiveGFLineItem()), + mouseFollowerVertical(new DiveLineItem()), + mouseFollowerHorizontal(new DiveLineItem()), + rulerItem(new RulerItem2()), + tankItem(new TankItem()), + isGrayscale(false), + printMode(false), + shouldCalculateMaxTime(true), + shouldCalculateMaxDepth(true), + fontPrintScale(1.0) +{ + // would like to be able to ASSERT here that PreferencesDialog::loadSettings has been called. + isPlotZoomed = prefs.zoomed_plot; // now it seems that 'prefs' has loaded our preferences + + memset(&plotInfo, 0, sizeof(plotInfo)); + + setupSceneAndFlags(); + setupItemSizes(); + setupItemOnScene(); + addItemsToScene(); + scene()->installEventFilter(this); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); + QAction *action = NULL; +#define ADD_ACTION(SHORTCUT, Slot) \ + action = new QAction(this); \ + action->setShortcut(SHORTCUT); \ + action->setShortcutContext(Qt::WindowShortcut); \ + addAction(action); \ + connect(action, SIGNAL(triggered(bool)), this, SLOT(Slot)); \ + actionsForKeys[SHORTCUT] = action; + + ADD_ACTION(Qt::Key_Escape, keyEscAction()); + ADD_ACTION(Qt::Key_Delete, keyDeleteAction()); + ADD_ACTION(Qt::Key_Up, keyUpAction()); + ADD_ACTION(Qt::Key_Down, keyDownAction()); + ADD_ACTION(Qt::Key_Left, keyLeftAction()); + ADD_ACTION(Qt::Key_Right, keyRightAction()); +#undef ADD_ACTION + +#if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE) + QTableView *diveDepthTableView = new QTableView(); + diveDepthTableView->setModel(dataModel); + diveDepthTableView->show(); +#endif +} + + +ProfileWidget2::~ProfileWidget2() +{ + delete background; + delete toolTipItem; + delete profileYAxis; + delete gasYAxis; + delete temperatureAxis; + delete timeAxis; + delete diveProfileItem; + delete temperatureItem; + delete meanDepthItem; + delete cylinderPressureAxis; + delete gasPressureItem; + delete diveComputerText; + delete diveCeiling; + delete reportedCeiling; + delete pn2GasItem; + delete pheGasItem; + delete po2GasItem; + delete o2SetpointGasItem; + delete ccrsensor1GasItem; + delete ccrsensor2GasItem; + delete ccrsensor3GasItem; + delete heartBeatAxis; + delete heartBeatItem; + delete percentageAxis; + delete ambPressureItem; + delete gflineItem; + delete mouseFollowerVertical; + delete mouseFollowerHorizontal; + delete rulerItem; + delete tankItem; +} + +#define SUBSURFACE_OBJ_DATA 1 +#define SUBSURFACE_OBJ_DC_TEXT 0x42 + +void ProfileWidget2::addItemsToScene() +{ + scene()->addItem(background); + scene()->addItem(toolTipItem); + scene()->addItem(profileYAxis); + scene()->addItem(gasYAxis); + scene()->addItem(temperatureAxis); + scene()->addItem(timeAxis); + scene()->addItem(diveProfileItem); + scene()->addItem(cylinderPressureAxis); + scene()->addItem(temperatureItem); + scene()->addItem(meanDepthItem); + scene()->addItem(gasPressureItem); + // I cannot seem to figure out if an object that I find with itemAt() on the scene + // is the object I am looking for - my guess is there's a simple way in Qt to do that + // but nothing I tried worked. + // so instead this adds a special magic key/value pair to the object to mark it + diveComputerText->setData(SUBSURFACE_OBJ_DATA, SUBSURFACE_OBJ_DC_TEXT); + scene()->addItem(diveComputerText); + scene()->addItem(diveCeiling); + scene()->addItem(gradientFactor); + scene()->addItem(reportedCeiling); + scene()->addItem(pn2GasItem); + scene()->addItem(pheGasItem); + scene()->addItem(po2GasItem); + scene()->addItem(o2SetpointGasItem); + scene()->addItem(ccrsensor1GasItem); + scene()->addItem(ccrsensor2GasItem); + scene()->addItem(ccrsensor3GasItem); + scene()->addItem(percentageAxis); + scene()->addItem(heartBeatAxis); + scene()->addItem(heartBeatItem); + scene()->addItem(rulerItem); + scene()->addItem(rulerItem->sourceNode()); + scene()->addItem(rulerItem->destNode()); + scene()->addItem(tankItem); + scene()->addItem(mouseFollowerHorizontal); + scene()->addItem(mouseFollowerVertical); + QPen pen(QColor(Qt::red).lighter()); + pen.setWidth(0); + mouseFollowerHorizontal->setPen(pen); + mouseFollowerVertical->setPen(pen); + Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) { + scene()->addItem(tissue); + } + Q_FOREACH (DivePercentageItem *percentage, allPercentages) { + scene()->addItem(percentage); + } + scene()->addItem(ambPressureItem); + scene()->addItem(gflineItem); +} + +void ProfileWidget2::setupItemOnScene() +{ + background->setZValue(9999); + toolTipItem->setZValue(9998); + toolTipItem->setTimeAxis(timeAxis); + rulerItem->setZValue(9997); + tankItem->setZValue(100); + + profileYAxis->setOrientation(DiveCartesianAxis::TopToBottom); + profileYAxis->setMinimum(0); + profileYAxis->setTickInterval(M_OR_FT(10, 30)); + profileYAxis->setTickSize(0.5); + profileYAxis->setLineSize(96); + + timeAxis->setLineSize(92); + timeAxis->setTickSize(-0.5); + + gasYAxis->setOrientation(DiveCartesianAxis::BottomToTop); + gasYAxis->setTickInterval(1); + gasYAxis->setTickSize(1); + gasYAxis->setMinimum(0); + gasYAxis->setModel(dataModel); + gasYAxis->setFontLabelScale(0.7); + gasYAxis->setLineSize(96); + + heartBeatAxis->setOrientation(DiveCartesianAxis::BottomToTop); + heartBeatAxis->setTickSize(0.2); + heartBeatAxis->setTickInterval(10); + heartBeatAxis->setFontLabelScale(0.7); + heartBeatAxis->setLineSize(96); + + percentageAxis->setOrientation(DiveCartesianAxis::BottomToTop); + percentageAxis->setTickSize(0.2); + percentageAxis->setTickInterval(10); + percentageAxis->setFontLabelScale(0.7); + percentageAxis->setLineSize(96); + + temperatureAxis->setOrientation(DiveCartesianAxis::BottomToTop); + temperatureAxis->setTickSize(2); + temperatureAxis->setTickInterval(300); + + cylinderPressureAxis->setOrientation(DiveCartesianAxis::BottomToTop); + cylinderPressureAxis->setTickSize(2); + cylinderPressureAxis->setTickInterval(30000); + + + diveComputerText->setAlignment(Qt::AlignRight | Qt::AlignTop); + diveComputerText->setBrush(getColor(TIME_TEXT, isGrayscale)); + + rulerItem->setAxis(timeAxis, profileYAxis); + tankItem->setHorizontalAxis(timeAxis); + + // show the gradient factor at the top in the center + gradientFactor->setY(0); + gradientFactor->setX(50); + gradientFactor->setBrush(getColor(PRESSURE_TEXT)); + gradientFactor->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + + setupItem(reportedCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1); + setupItem(diveCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1); + for (int i = 0; i < 16; i++) { + DiveCalculatedTissue *tissueItem = new DiveCalculatedTissue(); + setupItem(tissueItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::TISSUE_1 + i, DivePlotDataModel::TIME, 1 + i); + allTissues.append(tissueItem); + DivePercentageItem *percentageItem = new DivePercentageItem(i); + setupItem(percentageItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::PERCENTAGE_1 + i, DivePlotDataModel::TIME, 1 + i); + allPercentages.append(percentageItem); + } + setupItem(gasPressureItem, timeAxis, cylinderPressureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1); + setupItem(temperatureItem, timeAxis, temperatureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1); + setupItem(heartBeatItem, timeAxis, heartBeatAxis, dataModel, DivePlotDataModel::HEARTBEAT, DivePlotDataModel::TIME, 1); + setupItem(ambPressureItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::AMBPRESSURE, DivePlotDataModel::TIME, 1); + setupItem(gflineItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::GFLINE, DivePlotDataModel::TIME, 1); + setupItem(diveProfileItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::DEPTH, DivePlotDataModel::TIME, 0); + setupItem(meanDepthItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::INSTANT_MEANDEPTH, DivePlotDataModel::TIME, 1); + + +#define CREATE_PP_GAS(ITEM, VERTICAL_COLUMN, COLOR, COLOR_ALERT, THRESHOULD_SETTINGS, VISIBILITY_SETTINGS) \ + setupItem(ITEM, timeAxis, gasYAxis, dataModel, DivePlotDataModel::VERTICAL_COLUMN, DivePlotDataModel::TIME, 0); \ + ITEM->setThreshouldSettingsKey(THRESHOULD_SETTINGS); \ + ITEM->setVisibilitySettingsKey(VISIBILITY_SETTINGS); \ + ITEM->setColors(getColor(COLOR, isGrayscale), getColor(COLOR_ALERT, isGrayscale)); \ + ITEM->settingsChanged(); \ + ITEM->setZValue(99); + + CREATE_PP_GAS(pn2GasItem, PN2, PN2, PN2_ALERT, &prefs.pp_graphs.pn2_threshold, "pn2graph"); + CREATE_PP_GAS(pheGasItem, PHE, PHE, PHE_ALERT, &prefs.pp_graphs.phe_threshold, "phegraph"); + CREATE_PP_GAS(po2GasItem, PO2, PO2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph"); + CREATE_PP_GAS(o2SetpointGasItem, O2SETPOINT, PO2_ALERT, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph"); + CREATE_PP_GAS(ccrsensor1GasItem, CCRSENSOR1, CCRSENSOR1, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); + CREATE_PP_GAS(ccrsensor2GasItem, CCRSENSOR2, CCRSENSOR2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); + CREATE_PP_GAS(ccrsensor3GasItem, CCRSENSOR3, CCRSENSOR3, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); +#undef CREATE_PP_GAS + + temperatureAxis->setTextVisible(false); + temperatureAxis->setLinesVisible(false); + cylinderPressureAxis->setTextVisible(false); + cylinderPressureAxis->setLinesVisible(false); + timeAxis->setLinesVisible(true); + profileYAxis->setLinesVisible(true); + gasYAxis->setZValue(timeAxis->zValue() + 1); + heartBeatAxis->setTextVisible(true); + heartBeatAxis->setLinesVisible(true); + percentageAxis->setTextVisible(true); + percentageAxis->setLinesVisible(true); + + replotEnabled = true; +} + +void ProfileWidget2::replot(struct dive *d) +{ + if (!replotEnabled) + return; + dataModel->clear(); + plotDive(d, true); +} + +void ProfileWidget2::setupItemSizes() +{ + // Scene is *always* (double) 100 / 100. + // Background Config + /* Much probably a better math is needed here. + * good thing is that we only need to change the + * Axis and everything else is auto-adjusted.* + */ + + itemPos.background.on.setX(0); + itemPos.background.on.setY(0); + itemPos.background.off.setX(0); + itemPos.background.off.setY(110); + + //Depth Axis Config + itemPos.depth.pos.on.setX(3); + itemPos.depth.pos.on.setY(3); + itemPos.depth.pos.off.setX(-2); + itemPos.depth.pos.off.setY(3); + itemPos.depth.expanded.setP1(QPointF(0, 0)); + itemPos.depth.expanded.setP2(QPointF(0, 85)); + itemPos.depth.shrinked.setP1(QPointF(0, 0)); + itemPos.depth.shrinked.setP2(QPointF(0, 55)); + itemPos.depth.intermediate.setP1(QPointF(0, 0)); + itemPos.depth.intermediate.setP2(QPointF(0, 65)); + + // Time Axis Config + itemPos.time.pos.on.setX(3); + itemPos.time.pos.on.setY(95); + itemPos.time.pos.off.setX(3); + itemPos.time.pos.off.setY(110); + itemPos.time.expanded.setP1(QPointF(0, 0)); + itemPos.time.expanded.setP2(QPointF(94, 0)); + + // Partial Gas Axis Config + itemPos.partialPressure.pos.on.setX(97); + itemPos.partialPressure.pos.on.setY(75); + itemPos.partialPressure.pos.off.setX(110); + itemPos.partialPressure.pos.off.setY(63); + itemPos.partialPressure.expanded.setP1(QPointF(0, 0)); + itemPos.partialPressure.expanded.setP2(QPointF(0, 19)); + itemPos.partialPressureWithTankBar = itemPos.partialPressure; + itemPos.partialPressureWithTankBar.expanded.setP2(QPointF(0, 17)); + itemPos.partialPressureTissue = itemPos.partialPressure; + itemPos.partialPressureTissue.pos.on.setX(97); + itemPos.partialPressureTissue.pos.on.setY(65); + itemPos.partialPressureTissue.expanded.setP2(QPointF(0, 16)); + + // cylinder axis config + itemPos.cylinder.pos.on.setX(3); + itemPos.cylinder.pos.on.setY(20); + itemPos.cylinder.pos.off.setX(-10); + itemPos.cylinder.pos.off.setY(20); + itemPos.cylinder.expanded.setP1(QPointF(0, 15)); + itemPos.cylinder.expanded.setP2(QPointF(0, 50)); + itemPos.cylinder.shrinked.setP1(QPointF(0, 0)); + itemPos.cylinder.shrinked.setP2(QPointF(0, 20)); + itemPos.cylinder.intermediate.setP1(QPointF(0, 0)); + itemPos.cylinder.intermediate.setP2(QPointF(0, 20)); + + // Temperature axis config + itemPos.temperature.pos.on.setX(3); + itemPos.temperature.pos.on.setY(60); + itemPos.temperatureAll.pos.on.setY(51); + itemPos.temperature.pos.off.setX(-10); + itemPos.temperature.pos.off.setY(40); + itemPos.temperature.expanded.setP1(QPointF(0, 20)); + itemPos.temperature.expanded.setP2(QPointF(0, 33)); + itemPos.temperature.shrinked.setP1(QPointF(0, 2)); + itemPos.temperature.shrinked.setP2(QPointF(0, 12)); + itemPos.temperature.intermediate.setP1(QPointF(0, 2)); + itemPos.temperature.intermediate.setP2(QPointF(0, 12)); + + // Heartbeat axis config + itemPos.heartBeat.pos.on.setX(3); + itemPos.heartBeat.pos.on.setY(82); + itemPos.heartBeat.expanded.setP1(QPointF(0, 0)); + itemPos.heartBeat.expanded.setP2(QPointF(0, 10)); + itemPos.heartBeatWithTankBar = itemPos.heartBeat; + itemPos.heartBeatWithTankBar.expanded.setP2(QPointF(0, 7)); + + // Percentage axis config + itemPos.percentage.pos.on.setX(3); + itemPos.percentage.pos.on.setY(80); + itemPos.percentage.expanded.setP1(QPointF(0, 0)); + itemPos.percentage.expanded.setP2(QPointF(0, 15)); + itemPos.percentageWithTankBar = itemPos.percentage; + itemPos.percentageWithTankBar.expanded.setP2(QPointF(0, 12)); + + itemPos.dcLabel.on.setX(3); + itemPos.dcLabel.on.setY(100); + itemPos.dcLabel.off.setX(-10); + itemPos.dcLabel.off.setY(100); + + itemPos.tankBar.on.setX(0); + itemPos.tankBar.on.setY(91.5); +} + +void ProfileWidget2::setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *hAxis, + DiveCartesianAxis *vAxis, DivePlotDataModel *model, + int vData, int hData, int zValue) +{ + item->setHorizontalAxis(hAxis); + item->setVerticalAxis(vAxis); + item->setModel(model); + item->setVerticalDataColumn(vData); + item->setHorizontalDataColumn(hData); + item->setZValue(zValue); +} + +void ProfileWidget2::setupSceneAndFlags() +{ + setScene(new QGraphicsScene(this)); + scene()->setSceneRect(0, 0, 100, 100); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scene()->setItemIndexMethod(QGraphicsScene::NoIndex); + setOptimizationFlags(QGraphicsView::DontSavePainterState); + setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); + setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); + setMouseTracking(true); + background->setFlag(QGraphicsItem::ItemIgnoresTransformations); +} + +void ProfileWidget2::resetZoom() +{ + if (!zoomLevel) + return; + const qreal defScale = 1.0 / qPow(zoomFactor, (qreal)zoomLevel); + scale(defScale, defScale); + zoomLevel = 0; +} + +// Currently just one dive, but the plan is to enable All of the selected dives. +void ProfileWidget2::plotDive(struct dive *d, bool force) +{ + static bool firstCall = true; + QTime measureDuration; // let's measure how long this takes us (maybe we'll turn of TTL calculation later + measureDuration.start(); + + if (currentState != ADD && currentState != PLAN) { + if (!d) { + if (selected_dive == -1) + return; + d = current_dive; // display the current dive + } + + // No need to do this again if we are already showing the same dive + // computer of the same dive, so we check the unique id of the dive + // and the selected dive computer number against the ones we are + // showing (can't compare the dive pointers as those might change). + if (d->id == displayed_dive.id && dc_number == dataModel->dcShown() && !force) + return; + + // this copies the dive and makes copies of all the relevant additional data + copy_dive(d, &displayed_dive); + gradientFactor->setText(QString("GF %1/%2").arg(prefs.gflow).arg(prefs.gfhigh)); + } else { + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + plannerModel->createTemporaryPlan(); + struct diveplan &diveplan = plannerModel->getDiveplan(); + if (!diveplan.dp) { + plannerModel->deleteTemporaryPlan(); + return; + } + gradientFactor->setText(QString("GF %1/%2").arg(diveplan.gflow).arg(diveplan.gfhigh)); + } + + // special handling for the first time we display things + int animSpeedBackup = 0; + if (firstCall && MainWindow::instance()->filesFromCommandLine()) { + animSpeedBackup = prefs.animation_speed; + prefs.animation_speed = 0; + firstCall = false; + } + + // restore default zoom level + resetZoom(); + + // reset some item visibility on printMode changes + toolTipItem->setVisible(!printMode); + rulerItem->setVisible(prefs.rulergraph && !printMode && currentState != PLAN && currentState != ADD); + + if (currentState == EMPTY) + setProfileState(); + + // next get the dive computer structure - if there are no samples + // let's create a fake profile that's somewhat reasonable for the + // data that we have + struct divecomputer *currentdc = select_dc(&displayed_dive); + Q_ASSERT(currentdc); + if (!currentdc || !currentdc->samples) { + currentdc = fake_dc(currentdc); + } + + bool setpointflag = (currentdc->divemode == CCR) && prefs.pp_graphs.po2 && current_dive; + bool sensorflag = setpointflag && prefs.show_ccr_sensors; + o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint); + ccrsensor1GasItem->setVisible(sensorflag); + ccrsensor2GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 1)); + ccrsensor3GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 2)); + + /* This struct holds all the data that's about to be plotted. + * I'm not sure this is the best approach ( but since we are + * interpolating some points of the Dive, maybe it is... ) + * The Calculation of the points should be done per graph, + * so I'll *not* calculate everything if something is not being + * shown. + */ + plotInfo = calculate_max_limits_new(&displayed_dive, currentdc); + create_plot_info_new(&displayed_dive, currentdc, &plotInfo, !shouldCalculateMaxDepth); + if (shouldCalculateMaxTime) + maxtime = get_maxtime(&plotInfo); + + /* Only update the max depth if it's bigger than the current ones + * when we are dragging the handler to plan / add dive. + * otherwhise, update normally. + */ + int newMaxDepth = get_maxdepth(&plotInfo); + if (!shouldCalculateMaxDepth) { + if (maxdepth < newMaxDepth) { + maxdepth = newMaxDepth; + } + } else { + maxdepth = newMaxDepth; + } + + dataModel->setDive(&displayed_dive, plotInfo); + toolTipItem->setPlotInfo(plotInfo); + + // It seems that I'll have a lot of boilerplate setting the model / axis for + // each item, I'll mostly like to fix this in the future, but I'll keep at this for now. + profileYAxis->setMaximum(maxdepth); + profileYAxis->updateTicks(); + + temperatureAxis->setMinimum(plotInfo.mintemp); + temperatureAxis->setMaximum(plotInfo.maxtemp - plotInfo.mintemp > 2000 ? plotInfo.maxtemp : plotInfo.mintemp + 2000); + + if (plotInfo.maxhr) { + heartBeatAxis->setMinimum(plotInfo.minhr); + heartBeatAxis->setMaximum(plotInfo.maxhr); + heartBeatAxis->updateTicks(HR_AXIS); // this shows the ticks + } + heartBeatAxis->setVisible(prefs.hrgraph && plotInfo.maxhr); + + percentageAxis->setMinimum(0); + percentageAxis->setMaximum(100); + percentageAxis->setVisible(false); + percentageAxis->updateTicks(HR_AXIS); + + timeAxis->setMaximum(maxtime); + int i, incr; + static int increments[8] = { 10, 20, 30, 60, 5 * 60, 10 * 60, 15 * 60, 30 * 60 }; + /* Time markers: at most every 10 seconds, but no more than 12 markers. + * We start out with 10 seconds and increment up to 30 minutes, + * depending on the dive time. + * This allows for 6h dives - enough (I hope) for even the craziest + * divers - but just in case, for those 8h depth-record-breaking dives, + * we double the interval if this still doesn't get us to 12 or fewer + * time markers */ + i = 0; + while (i < 7 && maxtime / increments[i] > 12) + i++; + incr = increments[i]; + while (maxtime / incr > 12) + incr *= 2; + timeAxis->setTickInterval(incr); + timeAxis->updateTicks(); + cylinderPressureAxis->setMinimum(plotInfo.minpressure); + cylinderPressureAxis->setMaximum(plotInfo.maxpressure); + + rulerItem->setPlotInfo(plotInfo); + tankItem->setData(dataModel, &plotInfo, &displayed_dive); + + dataModel->emitDataChanged(); + // The event items are a bit special since we don't know how many events are going to + // exist on a dive, so I cant create cache items for that. that's why they are here + // while all other items are up there on the constructor. + qDeleteAll(eventItems); + eventItems.clear(); + struct event *event = currentdc->events; + while (event) { + // if print mode is selected only draw headings, SP change, gas events or bookmark event + if (printMode) { + if (same_string(event->name, "") || + !(strcmp(event->name, "heading") == 0 || + (same_string(event->name, "SP change") && event->time.seconds == 0) || + event_is_gaschange(event) || + event->type == SAMPLE_EVENT_BOOKMARK)) { + event = event->next; + continue; + } + } + DiveEventItem *item = new DiveEventItem(); + item->setHorizontalAxis(timeAxis); + item->setVerticalAxis(profileYAxis); + item->setModel(dataModel); + item->setEvent(event); + item->setZValue(2); + scene()->addItem(item); + eventItems.push_back(item); + event = event->next; + } + // Only set visible the events that should be visible + Q_FOREACH (DiveEventItem *event, eventItems) { + event->setVisible(!event->shouldBeHidden()); + } + QString dcText = get_dc_nickname(currentdc->model, currentdc->deviceid); + int nr; + if ((nr = number_of_computers(&displayed_dive)) > 1) + dcText += tr(" (#%1 of %2)").arg(dc_number + 1).arg(nr); + if (dcText.isEmpty()) + dcText = tr("Unknown dive computer"); + diveComputerText->setText(dcText); + if (MainWindow::instance()->filesFromCommandLine() && animSpeedBackup != 0) { + prefs.animation_speed = animSpeedBackup; + } + + if (currentState == ADD || currentState == PLAN) { // TODO: figure a way to move this from here. + repositionDiveHandlers(); + DivePlannerPointsModel *model = DivePlannerPointsModel::instance(); + model->deleteTemporaryPlan(); + } + plotPictures(); + + // OK, how long did this take us? Anything above the second is way too long, + // so if we are calculation TTS / NDL then let's force that off. + if (measureDuration.elapsed() > 1000 && prefs.calcndltts) { + MainWindow::instance()->turnOffNdlTts(); + MainWindow::instance()->getNotificationWidget()->showNotification(tr("Show NDL / TTS was disabled because of excessive processing time"), KMessageWidget::Error); + } + MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + +} + +void ProfileWidget2::recalcCeiling() +{ + diveCeiling->recalc(); +} + +void ProfileWidget2::settingsChanged() +{ + // if we are showing calculated ceilings then we have to replot() + // because the GF could have changed; otherwise we try to avoid replot() + bool needReplot = prefs.calcceiling; + if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) { + profileYAxis->animateChangeLine(itemPos.depth.shrinked); + temperatureAxis->setPos(itemPos.temperatureAll.pos.on); + temperatureAxis->animateChangeLine(itemPos.temperature.shrinked); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked); + + if (prefs.tankbar) { + percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); + percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); + heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); + }else { + percentageAxis->setPos(itemPos.percentage.pos.on); + percentageAxis->animateChangeLine(itemPos.percentage.expanded); + heartBeatAxis->setPos(itemPos.heartBeat.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); + } + gasYAxis->setPos(itemPos.partialPressureTissue.pos.on); + gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded); + + } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) { + profileYAxis->animateChangeLine(itemPos.depth.intermediate); + temperatureAxis->setPos(itemPos.temperature.pos.on); + temperatureAxis->animateChangeLine(itemPos.temperature.intermediate); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate); + if (prefs.tankbar) { + percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); + percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); + gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on); + gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded); + heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); + } else { + gasYAxis->setPos(itemPos.partialPressure.pos.on); + gasYAxis->animateChangeLine(itemPos.partialPressure.expanded); + percentageAxis->setPos(itemPos.percentage.pos.on); + percentageAxis->setLine(itemPos.percentage.expanded); + heartBeatAxis->setPos(itemPos.heartBeat.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); + } + } else { + profileYAxis->animateChangeLine(itemPos.depth.expanded); + if (prefs.tankbar) { + temperatureAxis->setPos(itemPos.temperatureAll.pos.on); + } else { + temperatureAxis->setPos(itemPos.temperature.pos.on); + } + temperatureAxis->animateChangeLine(itemPos.temperature.expanded); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded); + } + + tankItem->setVisible(prefs.tankbar); + if (prefs.zoomed_plot != isPlotZoomed) { + isPlotZoomed = prefs.zoomed_plot; + needReplot = true; + } + if (needReplot) + replot(); +} + +void ProfileWidget2::resizeEvent(QResizeEvent *event) +{ + QGraphicsView::resizeEvent(event); + fitInView(sceneRect(), Qt::IgnoreAspectRatio); + fixBackgroundPos(); +} + +void ProfileWidget2::mousePressEvent(QMouseEvent *event) +{ + if (zoomLevel) + return; + QGraphicsView::mousePressEvent(event); + if (currentState == PLAN) + shouldCalculateMaxTime = false; +} + +void ProfileWidget2::divePlannerHandlerClicked() +{ + if (zoomLevel) + return; + shouldCalculateMaxDepth = false; + replot(); +} + +void ProfileWidget2::divePlannerHandlerReleased() +{ + if (zoomLevel) + return; + shouldCalculateMaxDepth = true; + replot(); +} + +void ProfileWidget2::mouseReleaseEvent(QMouseEvent *event) +{ + if (zoomLevel) + return; + QGraphicsView::mouseReleaseEvent(event); + if (currentState == PLAN) { + shouldCalculateMaxTime = true; + replot(); + } +} + +void ProfileWidget2::fixBackgroundPos() +{ + static QPixmap toBeScaled(backgroundFile); + if (currentState != EMPTY) + return; + QPixmap p = toBeScaled.scaledToHeight(viewport()->height() - 40, Qt::SmoothTransformation); + int x = viewport()->width() / 2 - p.width() / 2; + int y = viewport()->height() / 2 - p.height() / 2; + background->setPixmap(p); + background->setX(mapToScene(x, 0).x()); + background->setY(mapToScene(y, 20).y()); +} + +void ProfileWidget2::wheelEvent(QWheelEvent *event) +{ + if (currentState == EMPTY) + return; + QPoint toolTipPos = mapFromScene(toolTipItem->pos()); + if (event->buttons() == Qt::LeftButton) + return; + if (event->delta() > 0 && zoomLevel < 20) { + scale(zoomFactor, zoomFactor); + zoomLevel++; + } else if (event->delta() < 0 && zoomLevel > 0) { + // Zooming out + scale(1.0 / zoomFactor, 1.0 / zoomFactor); + zoomLevel--; + } + scrollViewTo(event->pos()); + toolTipItem->setPos(mapToScene(toolTipPos)); +} + +void ProfileWidget2::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (currentState == PLAN || currentState == ADD) { + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + QPointF mappedPos = mapToScene(event->pos()); + if (isPointOutOfBoundaries(mappedPos)) + return; + + int minutes = rint(timeAxis->valueAt(mappedPos) / 60); + int milimeters = rint(profileYAxis->valueAt(mappedPos) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); + plannerModel->addStop(milimeters, minutes * 60, 0, 0, true); + } +} + +bool ProfileWidget2::isPointOutOfBoundaries(const QPointF &point) const +{ + double xpos = timeAxis->valueAt(point); + double ypos = profileYAxis->valueAt(point); + return (xpos > timeAxis->maximum() || + xpos < timeAxis->minimum() || + ypos > profileYAxis->maximum() || + ypos < profileYAxis->minimum()); +} + +void ProfileWidget2::scrollViewTo(const QPoint &pos) +{ + /* since we cannot use translate() directly on the scene we hack on + * the scroll bars (hidden) functionality */ + if (!zoomLevel || currentState == EMPTY) + return; + QScrollBar *vs = verticalScrollBar(); + QScrollBar *hs = horizontalScrollBar(); + const qreal yRat = (qreal)pos.y() / viewport()->height(); + const qreal xRat = (qreal)pos.x() / viewport()->width(); + vs->setValue(yRat * vs->maximum()); + hs->setValue(xRat * hs->maximum()); +} + +void ProfileWidget2::mouseMoveEvent(QMouseEvent *event) +{ + QPointF pos = mapToScene(event->pos()); + toolTipItem->refresh(pos); + if (zoomLevel == 0) { + QGraphicsView::mouseMoveEvent(event); + } else { + QPoint toolTipPos = mapFromScene(toolTipItem->pos()); + scrollViewTo(event->pos()); + toolTipItem->setPos(mapToScene(toolTipPos)); + } + + qreal vValue = profileYAxis->valueAt(pos); + qreal hValue = timeAxis->valueAt(pos); + if (profileYAxis->maximum() >= vValue && profileYAxis->minimum() <= vValue) { + mouseFollowerHorizontal->setPos(timeAxis->pos().x(), pos.y()); + } + if (timeAxis->maximum() >= hValue && timeAxis->minimum() <= hValue) { + mouseFollowerVertical->setPos(pos.x(), profileYAxis->line().y1()); + } +} + +bool ProfileWidget2::eventFilter(QObject *object, QEvent *event) +{ + QGraphicsScene *s = qobject_cast(object); + if (s && event->type() == QEvent::GraphicsSceneHelp) { + event->ignore(); + return true; + } + return QGraphicsView::eventFilter(object, event); +} + +void ProfileWidget2::setEmptyState() +{ + // Then starting Empty State, move the background up. + if (currentState == EMPTY) + return; + + disconnectTemporaryConnections(); + setBackgroundBrush(getColor(::BACKGROUND, isGrayscale)); + dataModel->clear(); + currentState = EMPTY; + MainWindow::instance()->setEnabledToolbar(false); + + fixBackgroundPos(); + background->setVisible(true); + + profileYAxis->setVisible(false); + gasYAxis->setVisible(false); + timeAxis->setVisible(false); + temperatureAxis->setVisible(false); + cylinderPressureAxis->setVisible(false); + toolTipItem->setVisible(false); + diveComputerText->setVisible(false); + diveCeiling->setVisible(false); + gradientFactor->setVisible(false); + reportedCeiling->setVisible(false); + rulerItem->setVisible(false); + tankItem->setVisible(false); + pn2GasItem->setVisible(false); + po2GasItem->setVisible(false); + o2SetpointGasItem->setVisible(false); + ccrsensor1GasItem->setVisible(false); + ccrsensor2GasItem->setVisible(false); + ccrsensor3GasItem->setVisible(false); + pheGasItem->setVisible(false); + ambPressureItem->setVisible(false); + gflineItem->setVisible(false); + mouseFollowerHorizontal->setVisible(false); + mouseFollowerVertical->setVisible(false); + +#define HIDE_ALL(TYPE, CONTAINER) \ + Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false); + HIDE_ALL(DiveCalculatedTissue, allTissues); + HIDE_ALL(DivePercentageItem, allPercentages); + HIDE_ALL(DiveEventItem, eventItems); + HIDE_ALL(DiveHandler, handles); + HIDE_ALL(QGraphicsSimpleTextItem, gases); +#undef HIDE_ALL +} + +void ProfileWidget2::setProfileState() +{ + // Then starting Empty State, move the background up. + if (currentState == PROFILE) + return; + + disconnectTemporaryConnections(); + connect(DivePictureModel::instance(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(plotPictures())); + connect(DivePictureModel::instance(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(plotPictures())); + connect(DivePictureModel::instance(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(plotPictures())); + /* show the same stuff that the profile shows. */ + + //TODO: Move the DC handling to another method. + MainWindow::instance()->enableShortcuts(); + + currentState = PROFILE; + MainWindow::instance()->setEnabledToolbar(true); + toolTipItem->readPos(); + setBackgroundBrush(getColor(::BACKGROUND, isGrayscale)); + + background->setVisible(false); + toolTipItem->setVisible(true); + profileYAxis->setVisible(true); + gasYAxis->setVisible(true); + timeAxis->setVisible(true); + temperatureAxis->setVisible(true); + cylinderPressureAxis->setVisible(true); + + profileYAxis->setPos(itemPos.depth.pos.on); + if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) { + profileYAxis->animateChangeLine(itemPos.depth.shrinked); + temperatureAxis->setPos(itemPos.temperatureAll.pos.on); + temperatureAxis->animateChangeLine(itemPos.temperature.shrinked); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked); + + if (prefs.tankbar) { + percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); + percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); + heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); + }else { + percentageAxis->setPos(itemPos.percentage.pos.on); + percentageAxis->animateChangeLine(itemPos.percentage.expanded); + heartBeatAxis->setPos(itemPos.heartBeat.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); + } + gasYAxis->setPos(itemPos.partialPressureTissue.pos.on); + gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded); + + } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) { + profileYAxis->animateChangeLine(itemPos.depth.intermediate); + temperatureAxis->setPos(itemPos.temperature.pos.on); + temperatureAxis->animateChangeLine(itemPos.temperature.intermediate); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate); + if (prefs.tankbar) { + percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); + percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); + gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on); + gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded); + heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); + } else { + gasYAxis->setPos(itemPos.partialPressure.pos.on); + gasYAxis->animateChangeLine(itemPos.partialPressure.expanded); + percentageAxis->setPos(itemPos.percentage.pos.on); + percentageAxis->setLine(itemPos.percentage.expanded); + heartBeatAxis->setPos(itemPos.heartBeat.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); + } + } else { + profileYAxis->animateChangeLine(itemPos.depth.expanded); + if (prefs.tankbar) { + temperatureAxis->setPos(itemPos.temperatureAll.pos.on); + } else { + temperatureAxis->setPos(itemPos.temperature.pos.on); + } + temperatureAxis->animateChangeLine(itemPos.temperature.expanded); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded); + } + pn2GasItem->setVisible(prefs.pp_graphs.pn2); + po2GasItem->setVisible(prefs.pp_graphs.po2); + pheGasItem->setVisible(prefs.pp_graphs.phe); + + bool setpointflag = current_dive && (current_dc->divemode == CCR) && prefs.pp_graphs.po2; + bool sensorflag = setpointflag && prefs.show_ccr_sensors; + o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint); + ccrsensor1GasItem->setVisible(sensorflag); + ccrsensor2GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 1)); + ccrsensor3GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 2)); + + timeAxis->setPos(itemPos.time.pos.on); + timeAxis->setLine(itemPos.time.expanded); + + cylinderPressureAxis->setPos(itemPos.cylinder.pos.on); + heartBeatItem->setVisible(prefs.hrgraph); + meanDepthItem->setVisible(prefs.show_average_depth); + + diveComputerText->setVisible(true); + diveComputerText->setPos(itemPos.dcLabel.on); + + diveCeiling->setVisible(prefs.calcceiling); + gradientFactor->setVisible(prefs.calcceiling); + reportedCeiling->setVisible(prefs.dcceiling); + + if (prefs.calcalltissues) { + Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) { + tissue->setVisible(true); + } + } + + if (prefs.percentagegraph) { + Q_FOREACH (DivePercentageItem *percentage, allPercentages) { + percentage->setVisible(true); + } + + ambPressureItem->setVisible(true); + gflineItem->setVisible(true); + } + + rulerItem->setVisible(prefs.rulergraph); + tankItem->setVisible(prefs.tankbar); + tankItem->setPos(itemPos.tankBar.on); + +#define HIDE_ALL(TYPE, CONTAINER) \ + Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false); + HIDE_ALL(DiveHandler, handles); + HIDE_ALL(QGraphicsSimpleTextItem, gases); +#undef HIDE_ALL + mouseFollowerHorizontal->setVisible(false); + mouseFollowerVertical->setVisible(false); +} + +void ProfileWidget2::clearHandlers() +{ + if (handles.count()) { + foreach (DiveHandler *handle, handles) { + scene()->removeItem(handle); + delete handle; + } + handles.clear(); + } +} + +void ProfileWidget2::setToolTipVisibile(bool visible) +{ + toolTipItem->setVisible(visible); +} + +void ProfileWidget2::setAddState() +{ + if (currentState == ADD) + return; + + clearHandlers(); + setProfileState(); + mouseFollowerHorizontal->setVisible(true); + mouseFollowerVertical->setVisible(true); + mouseFollowerHorizontal->setLine(timeAxis->line()); + mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y())); + disconnectTemporaryConnections(); + //TODO: Move this method to another place, shouldn't be on mainwindow. + MainWindow::instance()->disableShortcuts(false); + actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left); + actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right); + actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up); + actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down); + actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape); + actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete); + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); + connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); + connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(pointInserted(const QModelIndex &, int, int))); + connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(pointsRemoved(const QModelIndex &, int, int))); + /* show the same stuff that the profile shows. */ + currentState = ADD; /* enable the add state. */ + diveCeiling->setVisible(true); + gradientFactor->setVisible(true); + setBackgroundBrush(QColor("#A7DCFF")); +} + +void ProfileWidget2::setPlanState() +{ + if (currentState == PLAN) + return; + + setProfileState(); + mouseFollowerHorizontal->setVisible(true); + mouseFollowerVertical->setVisible(true); + mouseFollowerHorizontal->setLine(timeAxis->line()); + mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y())); + disconnectTemporaryConnections(); + //TODO: Move this method to another place, shouldn't be on mainwindow. + MainWindow::instance()->disableShortcuts(); + actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left); + actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right); + actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up); + actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down); + actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape); + actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete); + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); + connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); + connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(pointInserted(const QModelIndex &, int, int))); + connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(pointsRemoved(const QModelIndex &, int, int))); + /* show the same stuff that the profile shows. */ + currentState = PLAN; /* enable the add state. */ + diveCeiling->setVisible(true); + gradientFactor->setVisible(true); + setBackgroundBrush(QColor("#D7E3EF")); +} + +extern struct ev_select *ev_namelist; +extern int evn_allocated; +extern int evn_used; + +bool ProfileWidget2::isPlanner() +{ + return currentState == PLAN; +} + +bool ProfileWidget2::isAddOrPlanner() +{ + return currentState == PLAN || currentState == ADD; +} + +struct plot_data *ProfileWidget2::getEntryFromPos(QPointF pos) +{ + // find the time stamp corresponding to the mouse position + int seconds = timeAxis->valueAt(pos); + struct plot_data *entry = NULL; + + for (int i = 0; i < plotInfo.nr; i++) { + entry = plotInfo.entry + i; + if (entry->sec >= seconds) + break; + } + return entry; +} + +void ProfileWidget2::setReplot(bool state) +{ + replotEnabled = state; +} + +void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) +{ + if (currentState == ADD || currentState == PLAN) { + QGraphicsView::contextMenuEvent(event); + return; + } + QMenu m; + bool isDCName = false; + if (selected_dive == -1) + return; + // figure out if we are ontop of the dive computer name in the profile + QGraphicsItem *sceneItem = itemAt(mapFromGlobal(event->globalPos())); + if (sceneItem) { + QGraphicsItem *parentItem = sceneItem; + while (parentItem) { + if (parentItem->data(SUBSURFACE_OBJ_DATA) == SUBSURFACE_OBJ_DC_TEXT) { + isDCName = true; + break; + } + parentItem = parentItem->parentItem(); + } + if (isDCName) { + if (dc_number == 0 && count_divecomputers() == 1) + // nothing to do, can't delete or reorder + return; + // create menu to show when right clicking on dive computer name + if (dc_number > 0) + m.addAction(tr("Make first divecomputer"), this, SLOT(makeFirstDC())); + if (count_divecomputers() > 1) + m.addAction(tr("Delete this divecomputer"), this, SLOT(deleteCurrentDC())); + m.exec(event->globalPos()); + // don't show the regular profile context menu + return; + } + } + // create the profile context menu + QPointF scenePos = mapToScene(event->pos()); + struct plot_data *entry = getEntryFromPos(scenePos); + GasSelectionModel *model = GasSelectionModel::instance(); + model->repopulate(); + int rowCount = model->rowCount(); + if (rowCount > 1) { + // if we have more than one gas, offer to switch to another one + QMenu *gasChange = m.addMenu(tr("Add gas change")); + for (int i = 0; i < rowCount; i++) { + QAction *action = new QAction(&m); + action->setText(model->data(model->index(i, 0), Qt::DisplayRole).toString() + QString(tr(" (Tank %1)")).arg(i + 1)); + connect(action, SIGNAL(triggered(bool)), this, SLOT(changeGas())); + action->setData(event->globalPos()); + if (i == entry->cylinderindex) + action->setDisabled(true); + gasChange->addAction(action); + } + } + QAction *setpointAction = m.addAction(tr("Add set-point change"), this, SLOT(addSetpointChange())); + setpointAction->setData(event->globalPos()); + QAction *action = m.addAction(tr("Add bookmark"), this, SLOT(addBookmark())); + action->setData(event->globalPos()); + + if (same_string(current_dc->model, "manually added dive")) + QAction *editProfileAction = m.addAction(tr("Edit the profile"), MainWindow::instance(), SLOT(editCurrentDive())); + + if (DiveEventItem *item = dynamic_cast(sceneItem)) { + action = new QAction(&m); + action->setText(tr("Remove event")); + action->setData(QVariant::fromValue(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(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(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(sender()); + DiveEventItem *item = static_cast(action->data().value()); + 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(sender()); + DiveEventItem *item = static_cast(action->data().value()); + 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(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(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(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(sender()); + DiveEventItem *item = static_cast(action->data().value()); + 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(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(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(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(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(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 selectedIndexes; + Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { + if (DiveHandler *handler = qgraphicsitem_cast(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(); + // 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()); + 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..f11ec5be1 --- /dev/null +++ b/profile-widget/profilewidget2.h @@ -0,0 +1,211 @@ +#ifndef PROFILEWIDGET2_H +#define PROFILEWIDGET2_H + +#include + +// /* 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 eventItems; + DiveTextItem *diveComputerText; + DiveCalculatedCeiling *diveCeiling; + DiveTextItem *gradientFactor; + QList allTissues; + DiveReportedCeiling *reportedCeiling; + PartialPressureGasItem *pn2GasItem; + PartialPressureGasItem *pheGasItem; + PartialPressureGasItem *po2GasItem; + PartialPressureGasItem *o2SetpointGasItem; + PartialPressureGasItem *ccrsensor1GasItem; + PartialPressureGasItem *ccrsensor2GasItem; + PartialPressureGasItem *ccrsensor3GasItem; + DiveCartesianAxis *heartBeatAxis; + DiveHeartrateItem *heartBeatItem; + DiveCartesianAxis *percentageAxis; + QList allPercentages; + DiveAmbPressureItem *ambPressureItem; + DiveGFLineItem *gflineItem; + DiveLineItem *mouseFollowerVertical; + DiveLineItem *mouseFollowerHorizontal; + RulerItem2 *rulerItem; + TankItem *tankItem; + bool isGrayscale; + bool printMode; + + //specifics for ADD and PLAN + QList handles; + QList gases; + QList pictures; + void repositionDiveHandlers(); + int fixHandlerIndex(DiveHandler *activeHandler); + friend class DiveHandler; + QHash 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..830985552 --- /dev/null +++ b/profile-widget/ruleritem.cpp @@ -0,0 +1,179 @@ +#include "ruleritem.h" +#include "preferences.h" +#include "mainwindow.h" +#include "profilewidget2.h" +#include "display.h" + +#include + +#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(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 +#include +#include +#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 + +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 +#include +#include +#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 rects; +}; + +#endif // TANKITEM_H diff --git a/qt-models/CMakeLists.txt b/qt-models/CMakeLists.txt index 463d61412..c9bcf5c3d 100644 --- a/qt-models/CMakeLists.txt +++ b/qt-models/CMakeLists.txt @@ -23,6 +23,7 @@ set(SUBSURFACE_MODELS_LIB_SRCS divesitepicturesmodel.cpp ssrfsortfilterproxymodel.cpp ) + source_group("Subsurface Models" FILES ${SUBSURFACE_MODELS}) add_library(subsurface_models STATIC ${SUBSURFACE_MODELS_LIB_SRCS}) target_link_libraries(subsurface_models ${QT_LIBRARIES}) \ No newline at end of file -- cgit v1.2.3-70-g09d2