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