aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Maximilian Güntner <maximilian.guentner@gmail.com>2013-09-25 02:07:07 +0200
committerGravatar Maximilian Güntner <maximilian.guentner@gmail.com>2013-09-27 18:42:19 +0200
commit248f1b86d15b63fe5774667f8408e2abbd5f9504 (patch)
treef284af11c30b98566de74ffd0105a927ae31cead
parent3312c9a3a4306a7448c45cd2c1eba3d0cc3503c3 (diff)
downloadsubsurface-248f1b86d15b63fe5774667f8408e2abbd5f9504.tar.gz
Added a ruler which can be dragged along the profile
This patch adds a ruler QGraphicsItem which can be dragged along the profile. The ruler displays minimum, maximum and average for depth and speed (ascent/descent rate). Also, all used gas will be displayed. This also adds a new attribute to struct plot_data to store the speed (not just as velocity_t). Signed-off-by: Maximilian Güntner <maximilian.guentner@gmail.com>
-rw-r--r--dive.h4
-rw-r--r--profile.c134
-rw-r--r--profile.h2
-rw-r--r--qt-ui/profilegraphics.cpp229
-rw-r--r--qt-ui/profilegraphics.h50
5 files changed, 411 insertions, 8 deletions
diff --git a/dive.h b/dive.h
index fcfc20b34..dbfaf7202 100644
--- a/dive.h
+++ b/dive.h
@@ -658,6 +658,10 @@ const char *weekday(int wday);
const char *monthname(int mon);
#define UTF8_DEGREE "\xc2\xb0"
+#define UTF8_DELTA "\xce\x94"
+#define UTF8_UPWARDS_ARROW "\xE2\x86\x91"
+#define UTF8_DOWNWARDS_ARROW "\xE2\x86\x93"
+#define UTF8_AVERAGE "\xc3\xb8"
#define UCS4_DEGREE 0xb0
#define UTF8_SUBSCRIPT_2 "\xe2\x82\x82"
#define UTF8_WHITESTAR "\xe2\x98\x86"
diff --git a/profile.c b/profile.c
index a5c5d1120..b20899419 100644
--- a/profile.c
+++ b/profile.c
@@ -3,6 +3,7 @@
* uses cairo to draw it
*/
#include <glib/gi18n.h>
+#include <limits.h>
#include "dive.h"
#include "display.h"
@@ -389,7 +390,8 @@ static struct plot_info *analyze_plot_info(struct plot_info *pi)
/* vertical velocity in mm/sec */
/* Linus wants to smooth this - let's at least look at the samples that aren't FAST or CRAZY */
if (entry[0].sec - entry[-1].sec) {
- entry->velocity = velocity((entry[0].depth - entry[-1].depth) / (entry[0].sec - entry[-1].sec));
+ entry->speed = (entry[0].depth - entry[-1].depth) / (entry[0].sec - entry[-1].sec);
+ entry->velocity = velocity(entry->speed);
/* if our samples are short and we aren't too FAST*/
if (entry[0].sec - entry[-1].sec < 15 && entry->velocity < FAST) {
int past = -2;
@@ -400,6 +402,7 @@ static struct plot_info *analyze_plot_info(struct plot_info *pi)
}
} else {
entry->velocity = STABLE;
+ entry->speed = 0;
}
}
@@ -849,8 +852,8 @@ static struct plot_data *populate_plot_entries(struct dive *dive, struct divecom
}
/* Add two final surface events */
- plot_data[idx++].sec = lasttime+10;
- plot_data[idx++].sec = lasttime+20;
+ plot_data[idx++].sec = lasttime+1;
+ plot_data[idx++].sec = lasttime+2;
pi->nr = idx;
return plot_data;
@@ -1212,14 +1215,16 @@ static void plot_string(struct plot_data *entry, char *buf, int bufsize,
int pressurevalue, mod, ead, end, eadd;
const char *depth_unit, *pressure_unit, *temp_unit;
char *buf2 = malloc(bufsize);
- double depthvalue, tempvalue;
+ double depthvalue, tempvalue, speedvalue;
depthvalue = get_depth_units(depth, NULL, &depth_unit);
snprintf(buf, bufsize, _("D:%.1f %s"), depthvalue, depth_unit);
+
if (prefs.show_time) {
memcpy(buf2, buf, bufsize);
snprintf(buf, bufsize, _("%s\nT:%d:%02d"), buf2, FRACTION(entry->sec, 60));
}
+
if (pressure) {
pressurevalue = get_pressure_units(pressure, &pressure_unit);
memcpy(buf2, buf, bufsize);
@@ -1230,6 +1235,14 @@ static void plot_string(struct plot_data *entry, char *buf, int bufsize,
memcpy(buf2, buf, bufsize);
snprintf(buf, bufsize, _("%s\nT:%.1f %s"), buf2, tempvalue, temp_unit);
}
+
+ speedvalue = get_depth_units(abs(entry->speed), NULL, &depth_unit);
+ memcpy(buf2, buf, bufsize);
+ /* Ascending speeds are positive, descending are negative */
+ if (entry->speed > 0)
+ speedvalue *= -1;
+ snprintf(buf, bufsize, _("%s\nV:%.2f %s/s"), buf2, speedvalue, depth_unit);
+
if (entry->ceiling) {
depthvalue = get_depth_units(entry->ceiling, NULL, &depth_unit);
memcpy(buf2, buf, bufsize);
@@ -1330,3 +1343,116 @@ void get_plot_details(struct graphics_context *gc, int time, char *buf, int bufs
if (entry)
plot_string(entry, buf, bufsize, entry->depth, pressure, temp, pi->has_ndl);
}
+
+/* Compare two plot_data entries and writes the results into a string */
+void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int bufsize, int sum)
+{
+ struct plot_data *start, *stop, *data;
+ const char *depth_unit, *pressure_unit;
+ char *buf2 = malloc(bufsize);
+ int avg_speed, max_speed, min_speed;
+ int delta_depth, avg_depth, max_depth, min_depth;
+ int bar_used, last_pressure, pressurevalue;
+ int count, last_sec, delta_time;
+
+ double depthvalue, speedvalue;
+
+ if (bufsize > 0)
+ buf[0] = '\0';
+ if (e1 == NULL || e2 == NULL)
+ return;
+
+ if (e1->sec < e2->sec) {
+ start = e1;
+ stop = e2;
+ } else if (e1->sec > e2->sec) {
+ start = e2;
+ stop = e1;
+ } else {
+ return;
+ }
+ count = 0;
+ max_speed = 0;
+ min_speed = INT_MAX;
+
+ delta_depth = abs(start->depth-stop->depth);
+ delta_time = abs(start->sec-stop->sec);
+ avg_depth = 0;
+ max_depth = 0;
+ min_depth = INT_MAX;
+ bar_used = 0;
+
+ last_sec = start->sec;
+ last_pressure = GET_PRESSURE(start);
+
+ while (data != stop) {
+ data = start+count;
+ if (sum)
+ avg_speed += abs(data->speed)*(data->sec-last_sec);
+ else
+ avg_speed += data->speed*(data->sec-last_sec);
+ avg_depth += data->depth*(data->sec-last_sec);
+
+ if (abs(data->speed) < min_speed)
+ min_speed = abs(data->speed);
+ if (abs(data->speed) > max_speed)
+ max_speed = abs(data->speed);
+
+ if (data->depth < min_depth)
+ min_depth = data->depth;
+ if (data->depth > max_depth)
+ max_depth = data->depth;
+ /* Try to detect gas changes */
+ if (GET_PRESSURE(data) > last_pressure+2000)
+ last_pressure = GET_PRESSURE(data);
+ else
+ bar_used += last_pressure-GET_PRESSURE(data);
+
+
+ count+=1;
+ last_sec = data->sec;
+ last_pressure = GET_PRESSURE(data);
+ }
+ avg_depth /= stop->sec-start->sec;
+ avg_speed /= stop->sec-start->sec;
+
+ snprintf(buf, bufsize, _("%sT: %d:%02d min"), UTF8_DELTA, delta_time/60, delta_time%60);
+ memcpy(buf2, buf, bufsize);
+
+ depthvalue = get_depth_units(delta_depth, NULL, &depth_unit);
+ snprintf(buf, bufsize, _("%s %sD:%.1f%s"), buf2, UTF8_DELTA, depthvalue, depth_unit);
+ memcpy(buf2, buf, bufsize);
+
+ depthvalue = get_depth_units(min_depth, NULL, &depth_unit);
+ snprintf(buf, bufsize, _("%s %sD:%.1f%s"), buf2, UTF8_DOWNWARDS_ARROW, depthvalue, depth_unit);
+ memcpy(buf2, buf, bufsize);
+
+ depthvalue = get_depth_units(max_depth, NULL, &depth_unit);
+ snprintf(buf, bufsize, _("%s %sD:%.1f %s"), buf2, UTF8_UPWARDS_ARROW, depthvalue, depth_unit);
+ memcpy(buf2, buf, bufsize);
+
+ depthvalue = get_depth_units(avg_depth, NULL, &depth_unit);
+ snprintf(buf, bufsize, _("%s %sD:%.1f%s\n"), buf2, UTF8_AVERAGE, depthvalue, depth_unit);
+ memcpy(buf2, buf, bufsize);
+
+ speedvalue = get_depth_units(min_speed, NULL, &depth_unit);
+ snprintf(buf, bufsize, _("%s%sV:%.2f%s/s"), buf2, UTF8_DOWNWARDS_ARROW, speedvalue, depth_unit);
+ memcpy(buf2, buf, bufsize);
+
+ speedvalue = get_depth_units(max_speed, NULL, &depth_unit);
+ snprintf(buf, bufsize, _("%s %sV:%.2f%s/s"), buf2, UTF8_UPWARDS_ARROW, speedvalue, depth_unit);
+ memcpy(buf2, buf, bufsize);
+
+ speedvalue = get_depth_units(avg_speed, NULL, &depth_unit);
+ snprintf(buf, bufsize, _("%s %sV:%.2f%s/s"), buf2, UTF8_AVERAGE, speedvalue, depth_unit);
+ memcpy(buf2, buf, bufsize);
+
+ /* Only print if gas has been used */
+ if (bar_used) {
+ pressurevalue = get_pressure_units(bar_used, &pressure_unit);
+ memcpy(buf2, buf, bufsize);
+ snprintf(buf, bufsize, _("%s %sP:%d %s"), buf2, UTF8_DELTA, pressurevalue, pressure_unit);
+ }
+
+ free(buf2);
+}
diff --git a/profile.h b/profile.h
index 45e5a11fa..2de469f94 100644
--- a/profile.h
+++ b/profile.h
@@ -33,6 +33,7 @@ struct plot_data {
double po2, pn2, phe;
double mod, ead, end, eadd;
velocity_t velocity;
+ int speed;
struct plot_data *min[3];
struct plot_data *max[3];
int avg[3];
@@ -42,6 +43,7 @@ void calculate_max_limits(struct dive *dive, struct divecomputer *dc, struct gra
struct plot_info *create_plot_info(struct dive *dive, struct divecomputer *dc, struct graphics_context *gc);
int setup_temperature_limits(struct graphics_context *gc);
int get_cylinder_pressure_range(struct graphics_context *gc);
+void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int bufsize, int sum);
struct ev_select {
char *ev_name;
diff --git a/qt-ui/profilegraphics.cpp b/qt-ui/profilegraphics.cpp
index f9cf071e1..c0f1431b7 100644
--- a/qt-ui/profilegraphics.cpp
+++ b/qt-ui/profilegraphics.cpp
@@ -16,6 +16,7 @@
#include <QGraphicsSceneHoverEvent>
#include <QMouseEvent>
#include <qtextdocument.h>
+#include <limits>
#include "../color.h"
#include "../display.h"
@@ -43,10 +44,11 @@ extern struct ev_select *ev_namelist;
extern int evn_allocated;
extern int evn_used;
-ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent), toolTip(0) , dive(0), diveDC(0)
+ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent), toolTip(0) , dive(0), diveDC(0), rulerItem(0)
{
printMode = false;
isGrayscale = false;
+ rulerEnabled = false;
gc.printer = false;
fill_profile_color();
setScene(new QGraphicsScene());
@@ -175,11 +177,18 @@ void ProfileGraphicsView::clear()
{
resetTransform();
zoomLevel = 0;
- if(toolTip){
+ if(toolTip) {
scene()->removeItem(toolTip);
toolTip->deleteLater();
toolTip = 0;
}
+ if(rulerItem) {
+ remove_ruler();
+ rulerItem->destNode()->deleteLater();
+ rulerItem->sourceNode()->deleteLater();
+ rulerItem->deleteLater();
+ rulerItem=0;
+ }
scene()->clear();
}
@@ -214,7 +223,7 @@ void ProfileGraphicsView::plot(struct dive *d, bool forceRedraw)
dive = d;
diveDC = d ? dc : NULL;
- if (!isVisible() || !dive) {
+ if (!isVisible() || !dive || !mainWindow()) {
return;
}
setBackgroundBrush(getColor(BACKGROUND));
@@ -274,6 +283,9 @@ void ProfileGraphicsView::plot(struct dive *d, bool forceRedraw)
plot_events(dc);
+ if (rulerEnabled && !printMode)
+ create_ruler();
+
/* Temperature profile */
plot_temperature_profile();
@@ -336,6 +348,9 @@ void ProfileGraphicsView::plot(struct dive *d, bool forceRedraw)
connect(timeEditor, SIGNAL(editingFinished(QString)), this, SLOT(edit_dive_time(QString)));
scene()->addItem(timeEditor);
}
+
+ if (rulerEnabled && !printMode)
+ add_ruler();
}
void ProfileGraphicsView::plot_depth_scale()
@@ -840,6 +855,57 @@ void ProfileGraphicsView::plot_one_event(struct event *ev)
item->setToolTip(name);
}
+void ProfileGraphicsView::create_ruler()
+{
+ int x,y;
+ struct plot_info *pi = &gc.pi;
+ struct plot_data *data = pi->entry;
+
+ RulerNodeItem *first = new RulerNodeItem(0, gc);
+ RulerNodeItem *second = new RulerNodeItem(0, gc);
+
+ x = SCALEXGC(data->sec);
+ y = data->depth;
+
+ first->setPos(x,y);
+
+ data = pi->entry+(pi->nr-1);
+ x = SCALEXGC(data->sec);
+ y = data->depth;
+
+ second->setPos(x,y);
+ //Make sure that both points already have their entries
+ first->recalculate();
+ second->recalculate();
+
+ rulerItem = new RulerItem(0, first, second);
+ first->setRuler(rulerItem);
+ second->setRuler(rulerItem);
+}
+
+void ProfileGraphicsView::add_ruler()
+{
+ if (! scene()->items().contains(rulerItem)) {
+ scene()->addItem(rulerItem->sourceNode());
+ scene()->addItem(rulerItem->destNode());
+ scene()->addItem(rulerItem);
+ rulerItem->recalculate();
+ }
+}
+
+void ProfileGraphicsView::remove_ruler()
+{
+ if (rulerItem) {
+ if (scene()->items().contains(rulerItem))
+ scene()->removeItem(rulerItem);
+ if (scene()->items().contains(rulerItem->sourceNode()))
+ scene()->removeItem(rulerItem->sourceNode());
+ if (scene()->items().contains(rulerItem->destNode()))
+ scene()->removeItem(rulerItem->destNode());
+ }
+}
+
+
void ProfileGraphicsView::plot_depth_profile()
{
int i, incr;
@@ -1131,7 +1197,7 @@ QGraphicsItemGroup *ProfileGraphicsView::plot_text(text_render_options_t *tro,co
void ProfileGraphicsView::resizeEvent(QResizeEvent *event)
{
- fitInView(sceneRect());
+ refresh();
}
void ProfileGraphicsView::plot_temperature_profile()
@@ -1430,6 +1496,161 @@ EventItem::EventItem(QGraphicsItem* parent, bool grayscale): QGraphicsPolygonIte
ball->setPen(QPen(getColor(ALERT_FG)));
}
+
+
+RulerNodeItem::RulerNodeItem(QGraphicsItem *parent, graphics_context context) : QGraphicsEllipseItem(parent), gc(context), entry(NULL) , ruler(NULL)
+{
+ setRect(QRect(QPoint(-8,8),QPoint(8,-8)));
+ setBrush(QColor(0xff, 0, 0, 127));
+ setPen(QColor("#FF0000"));
+ setFlag(QGraphicsItem::ItemIsMovable);
+ setFlag(ItemSendsGeometryChanges);
+ setFlag(ItemIgnoresTransformations);
+}
+
+void RulerNodeItem::setRuler(RulerItem *r)
+{
+ ruler = r;
+}
+
+void RulerNodeItem::recalculate()
+{
+ struct plot_info *pi = &gc.pi;
+ struct plot_data *data = pi->entry+(pi->nr-1);
+ uint16_t count = 0;
+ if (x() < 0) {
+ setPos(0, y());
+ }
+ else if (x() > SCALEXGC(data->sec)) {
+ setPos(SCALEXGC(data->sec), y());
+ }
+ else {
+ data = pi->entry;
+ count=0;
+ while (SCALEXGC(data->sec) < x() && count < pi->nr) {
+ data = pi->entry+count;
+ count++;
+ }
+ setPos(SCALEGC(data->sec, data->depth));
+ entry=data;
+ }
+}
+
+QVariant RulerNodeItem::itemChange(GraphicsItemChange change, const QVariant &value)
+{
+ if(change == ItemPositionHasChanged) {
+ recalculate();
+ if(ruler != NULL)
+ ruler->recalculate();
+ if (scene()) {
+ scene()->update();
+ }
+ }
+ return QGraphicsEllipseItem::itemChange(change, value);
+}
+
+RulerItem::RulerItem(QGraphicsItem *parent, RulerNodeItem *sourceNode, RulerNodeItem *destNode) : QGraphicsObject(parent), source(sourceNode), dest(destNode)
+{
+ recalculate();
+}
+
+void RulerItem::recalculate()
+{
+ char buffer[500];
+ QPointF tmp;
+ QFont font;
+ QFontMetrics fm(font);
+
+ if (source == NULL || dest == NULL)
+ 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);
+
+ compare_samples(source->entry, dest->entry, buffer, 500, 1);
+ text = QString(buffer);
+
+ QRect r = fm.boundingRect(QRect(QPoint(10,-1*INT_MAX), QPoint(line.length()-10, 0)), Qt::TextWordWrap, text);
+ if (r.height() < 10)
+ height = 10;
+ else
+ height = r.height();
+
+ QLineF line_n = line.normalVector();
+ line_n.setLength(height);
+ if (scene()) {
+ /* Determine whether we draw down or upwards */
+ if (scene()->sceneRect().contains(line_n.p2()) &&
+ scene()->sceneRect().contains(endPoint+QPointF(line_n.dx(),line_n.dy())))
+ paint_direction = -1;
+ else
+ paint_direction = 1;
+ }
+}
+
+RulerNodeItem *RulerItem::sourceNode() const
+{
+ return source;
+}
+
+RulerNodeItem *RulerItem::destNode() const
+{
+ return dest;
+}
+
+void RulerItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ QLineF line(startPoint, endPoint);
+ QLineF line_n = line.normalVector();
+ painter->setPen(QColor(Qt::black));
+ painter->setBrush(Qt::NoBrush);
+ line_n.setLength(height);
+
+ if (paint_direction == 1)
+ line_n.setAngle(line_n.angle()+180);
+ painter->drawLine(line);
+ painter->drawLine(line_n);
+ painter->drawLine(line_n.p1() + QPointF(line.dx(), line.dy()), line_n.p2() + QPointF(line.dx(), line.dy()));
+
+ //Draw Text
+ painter->save();
+ painter->translate(startPoint.x(), startPoint.y());
+ painter->rotate(line.angle()*-1);
+ if (paint_direction == 1)
+ painter->translate(0, height);
+ painter->setPen(Qt::black);
+ painter->drawText(QRectF(QPointF(10,-1*height), QPointF(line.length()-10, 0)), Qt::TextWordWrap, text);
+ painter->restore();
+}
+
+QRectF RulerItem::boundingRect() const
+{
+ return shape().controlPointRect();
+}
+
+QPainterPath RulerItem::shape() const
+{
+ QPainterPath path;
+ QLineF line(startPoint, endPoint);
+ QLineF line_n = line.normalVector();
+ line_n.setLength(height);
+ if (paint_direction == 1)
+ line_n.setAngle(line_n.angle()+180);
+ path.moveTo(startPoint);
+ path.lineTo(line_n.p2());
+ path.lineTo(line_n.p2() + QPointF(line.dx(), line.dy()));
+ path.lineTo(endPoint);
+ path.lineTo(startPoint);
+ return path;
+}
+
GraphicsTextEditor::GraphicsTextEditor(QGraphicsItem* parent): QGraphicsTextItem(parent)
{
}
diff --git a/qt-ui/profilegraphics.h b/qt-ui/profilegraphics.h
index 0bcbf7529..deb729023 100644
--- a/qt-ui/profilegraphics.h
+++ b/qt-ui/profilegraphics.h
@@ -57,6 +57,49 @@ private:
QRectF nextRectangle;
};
+class RulerItem;
+
+class RulerNodeItem : public QObject, public QGraphicsEllipseItem
+{
+ Q_OBJECT
+ friend class RulerItem;
+public:
+ explicit RulerNodeItem(QGraphicsItem* parent, graphics_context gc);
+ void setRuler(RulerItem *r);
+ void recalculate();
+
+protected:
+ QVariant itemChange(GraphicsItemChange change, const QVariant & value );
+
+private:
+ graphics_context gc;
+ struct plot_data *entry;
+ RulerItem* ruler;
+};
+
+class RulerItem : public QGraphicsObject
+{
+ Q_OBJECT
+public:
+ explicit RulerItem(QGraphicsItem* parent,
+ RulerNodeItem *sourceMarker,
+ RulerNodeItem *destMarker);
+ void recalculate();
+
+ RulerNodeItem* sourceNode() const;
+ RulerNodeItem* destNode() const;
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * widget = 0);
+ QRectF boundingRect() const;
+ QPainterPath shape() const;
+
+private:
+ QPointF startPoint, endPoint;
+ RulerNodeItem *source, *dest;
+ QString text;
+ int height;
+ int paint_direction;
+};
+
class EventItem : public QGraphicsPolygonItem
{
public:
@@ -128,6 +171,10 @@ private:
void plot_pp_text();
void plot_depth_scale();
+ void create_ruler();
+ void add_ruler();
+ void remove_ruler();
+
QColor getColor(const color_indice_t i);
QColor get_sac_color(int sac, int avg_sac);
void scrollViewTo(const QPoint pos);
@@ -139,6 +186,8 @@ private:
struct dive *dive;
struct divecomputer *diveDC;
int zoomLevel;
+
+ bool rulerEnabled;
bool printMode;
bool isGrayscale;
@@ -147,6 +196,7 @@ private:
QGraphicsItem* timeMarkers;
QGraphicsItem* depthMarkers;
QGraphicsItem* diveComputer;
+ RulerItem *rulerItem;
// For 'Plan' mode.:
GraphicsTextEditor *depthEditor;