summaryrefslogtreecommitdiffstats
path: root/qt-ui/profilegraphics.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'qt-ui/profilegraphics.cpp')
-rw-r--r--qt-ui/profilegraphics.cpp1397
1 files changed, 1397 insertions, 0 deletions
diff --git a/qt-ui/profilegraphics.cpp b/qt-ui/profilegraphics.cpp
new file mode 100644
index 000000000..637c7fe95
--- /dev/null
+++ b/qt-ui/profilegraphics.cpp
@@ -0,0 +1,1397 @@
+#include "profilegraphics.h"
+
+#include <QGraphicsScene>
+#include <QResizeEvent>
+#include <QGraphicsLineItem>
+#include <QPen>
+#include <QBrush>
+#include <QDebug>
+#include <QLineF>
+#include <QSettings>
+#include <QIcon>
+#include <QPropertyAnimation>
+#include <QGraphicsSceneHoverEvent>
+#include <QMouseEvent>
+
+#include "../color.h"
+#include "../display.h"
+#include "../dive.h"
+#include "../profile.h"
+
+#include <libdivecomputer/parser.h>
+#include <libdivecomputer/version.h>
+
+#define SAC_COLORS_START_IDX SAC_1
+#define SAC_COLORS 9
+#define VELOCITY_COLORS_START_IDX VELO_STABLE
+#define VELOCITY_COLORS 5
+
+static struct graphics_context last_gc;
+static double plot_scale = SCALE_SCREEN;
+
+typedef enum {
+ /* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */
+ SAC_1, SAC_2, SAC_3, SAC_4, SAC_5, SAC_6, SAC_7, SAC_8, SAC_9,
+
+ /* Velocity colors. Order is still important, ref VELOCITY_COLORS_START_IDX. */
+ VELO_STABLE, VELO_SLOW, VELO_MODERATE, VELO_FAST, VELO_CRAZY,
+
+ /* gas colors */
+ PO2, PO2_ALERT, PN2, PN2_ALERT, PHE, PHE_ALERT, PP_LINES,
+
+ /* Other colors */
+ TEXT_BACKGROUND, ALERT_BG, ALERT_FG, EVENTS, SAMPLE_DEEP, SAMPLE_SHALLOW,
+ SMOOTHED, MINUTE, TIME_GRID, TIME_TEXT, DEPTH_GRID, MEAN_DEPTH, DEPTH_TOP,
+ DEPTH_BOTTOM, TEMP_TEXT, TEMP_PLOT, SAC_DEFAULT, BOUNDING_BOX, PRESSURE_TEXT, BACKGROUND,
+ CEILING_SHALLOW, CEILING_DEEP, CALC_CEILING_SHALLOW, CALC_CEILING_DEEP
+} color_indice_t;
+
+
+#define COLOR(x, y, z) QVector<QColor>() << x << y << z;
+/* profile_color[color indice] = COLOR(screen color, b/w printer color, color printer}} printer & screen colours could be different */
+QMap<color_indice_t, QVector<QColor> > profile_color;
+void fill_profile_color()
+{
+ profile_color[SAC_1] = COLOR(FUNGREEN1, BLACK1_LOW_TRANS, FUNGREEN1);
+ profile_color[SAC_2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1);
+ profile_color[SAC_3] = COLOR(ATLANTIS1, BLACK1_LOW_TRANS, ATLANTIS1);
+ profile_color[SAC_4] = COLOR(ATLANTIS2, BLACK1_LOW_TRANS, ATLANTIS2);
+ profile_color[SAC_5] = COLOR(EARLSGREEN1, BLACK1_LOW_TRANS, EARLSGREEN1);
+ profile_color[SAC_6] = COLOR(HOKEYPOKEY1, BLACK1_LOW_TRANS, HOKEYPOKEY1);
+ profile_color[SAC_7] = COLOR(TUSCANY1, BLACK1_LOW_TRANS, TUSCANY1);
+ profile_color[SAC_8] = COLOR(CINNABAR1, BLACK1_LOW_TRANS, CINNABAR1);
+ profile_color[SAC_9] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1);
+
+ profile_color[VELO_STABLE] = COLOR(CAMARONE1, BLACK1_LOW_TRANS, CAMARONE1);
+ profile_color[VELO_SLOW] = COLOR(LIMENADE1, BLACK1_LOW_TRANS, LIMENADE1);
+ profile_color[VELO_MODERATE] = COLOR(RIOGRANDE1, BLACK1_LOW_TRANS, RIOGRANDE1);
+ profile_color[VELO_FAST] = COLOR(PIRATEGOLD1, BLACK1_LOW_TRANS, PIRATEGOLD1);
+ profile_color[VELO_CRAZY] = COLOR(RED1, BLACK1_LOW_TRANS, RED1);
+
+ profile_color[PO2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1);
+ profile_color[PO2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1);
+ profile_color[PN2] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS);
+ profile_color[PN2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1);
+ profile_color[PHE] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT);
+ profile_color[PHE_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1);
+ profile_color[PP_LINES] = COLOR(BLACK1_HIGH_TRANS, BLACK1_HIGH_TRANS, BLACK1_HIGH_TRANS);
+
+ profile_color[TEXT_BACKGROUND] = COLOR(CONCRETE1_LOWER_TRANS, WHITE1, CONCRETE1_LOWER_TRANS);
+ profile_color[ALERT_BG] = COLOR(BROOM1_LOWER_TRANS, BLACK1_LOW_TRANS, BROOM1_LOWER_TRANS);
+ profile_color[ALERT_FG] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS);
+ profile_color[EVENTS] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1);
+ profile_color[SAMPLE_DEEP] = COLOR(PERSIANRED1, BLACK1_LOW_TRANS, PERSIANRED1);
+ profile_color[SAMPLE_SHALLOW] = COLOR(PERSIANRED1, BLACK1_LOW_TRANS, PERSIANRED1);
+ profile_color[SMOOTHED] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_LOW_TRANS, REDORANGE1_HIGH_TRANS);
+ profile_color[MINUTE] = COLOR(MEDIUMREDVIOLET1_HIGHER_TRANS, BLACK1_LOW_TRANS, MEDIUMREDVIOLET1_HIGHER_TRANS);
+ profile_color[TIME_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS);
+ profile_color[TIME_TEXT] = COLOR(FORESTGREEN1, BLACK1_LOW_TRANS, FORESTGREEN1);
+ profile_color[DEPTH_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS);
+ profile_color[MEAN_DEPTH] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS);
+ profile_color[DEPTH_BOTTOM] = COLOR(GOVERNORBAY1_MED_TRANS, BLACK1_HIGH_TRANS, GOVERNORBAY1_MED_TRANS);
+ profile_color[DEPTH_TOP] = COLOR(MERCURY1_MED_TRANS, WHITE1_MED_TRANS, MERCURY1_MED_TRANS);
+ profile_color[TEMP_TEXT] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2);
+ profile_color[TEMP_PLOT] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS);
+ profile_color[SAC_DEFAULT] = COLOR(WHITE1, BLACK1_LOW_TRANS, FORESTGREEN1);
+ profile_color[BOUNDING_BOX] = COLOR(WHITE1, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS);
+ profile_color[PRESSURE_TEXT] = COLOR(KILLARNEY1, BLACK1_LOW_TRANS, KILLARNEY1);
+ profile_color[BACKGROUND] = COLOR(SPRINGWOOD1, BLACK1_LOW_TRANS, SPRINGWOOD1);
+ profile_color[CEILING_SHALLOW] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_HIGH_TRANS, REDORANGE1_HIGH_TRANS);
+ profile_color[CEILING_DEEP] = COLOR(RED1_MED_TRANS, BLACK1_HIGH_TRANS, RED1_MED_TRANS);
+ profile_color[CALC_CEILING_SHALLOW] = COLOR(FUNGREEN1_HIGH_TRANS, BLACK1_HIGH_TRANS, FUNGREEN1_HIGH_TRANS);
+ profile_color[CALC_CEILING_DEEP] = COLOR(APPLE1_HIGH_TRANS, BLACK1_HIGH_TRANS, APPLE1_HIGH_TRANS);
+
+}
+#undef COLOR
+
+struct text_render_options{
+ double size;
+ color_indice_t color;
+ double hpos, vpos;
+};
+
+extern struct ev_select *ev_namelist;
+extern int evn_allocated;
+extern int evn_used;
+
+ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent), toolTip(0) , dive(0)
+{
+ gc.printer = false;
+ setScene(new QGraphicsScene());
+ setBackgroundBrush(QColor("#F3F3E6"));
+ scene()->installEventFilter(this);
+
+ setRenderHint(QPainter::Antialiasing);
+ setRenderHint(QPainter::HighQualityAntialiasing);
+ setRenderHint(QPainter::SmoothPixmapTransform);
+
+ defaultPen.setJoinStyle(Qt::RoundJoin);
+ defaultPen.setCapStyle(Qt::RoundCap);
+ defaultPen.setWidth(2);
+ defaultPen.setCosmetic(true);
+
+ setHorizontalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
+ setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
+
+ fill_profile_color();
+}
+
+void ProfileGraphicsView::wheelEvent(QWheelEvent* event)
+{
+ if (!toolTip)
+ return;
+
+ setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
+
+ // Scale the view / do the zoom
+ QPoint toolTipPos = mapFromScene(toolTip->pos());
+ double scaleFactor = 1.15;
+ if(event->delta() > 0 && zoomLevel <= 10) {
+ scale(scaleFactor, scaleFactor);
+ zoomLevel++;
+ } else if (zoomLevel >= 0) {
+ // Zooming out
+ scale(1.0 / scaleFactor, 1.0 / scaleFactor);
+ zoomLevel--;
+ }
+ toolTip->setPos(mapToScene(toolTipPos).x(), mapToScene(toolTipPos).y());
+}
+
+void ProfileGraphicsView::mouseMoveEvent(QMouseEvent* event)
+{
+ if (!toolTip)
+ return;
+
+ toolTip->refresh(&gc, mapToScene(event->pos()));
+
+ QPoint toolTipPos = mapFromScene(toolTip->pos());
+
+ double dx = sceneRect().x();
+ double dy = sceneRect().y();
+
+ ensureVisible(event->pos().x() + dx, event->pos().y() + dy, 1, 1);
+
+ toolTip->setPos(mapToScene(toolTipPos).x(), mapToScene(toolTipPos).y());
+
+ if (zoomLevel < 0)
+ QGraphicsView::mouseMoveEvent(event);
+}
+
+bool ProfileGraphicsView::eventFilter(QObject* obj, QEvent* event)
+{
+ // This will "Eat" the default tooltip behavior.
+ if (event->type() == QEvent::GraphicsSceneHelp) {
+ event->ignore();
+ return true;
+ }
+ return QGraphicsView::eventFilter(obj, event);
+}
+
+static void plot_set_scale(scale_mode_t scale)
+{
+ switch (scale) {
+ default:
+ case SC_SCREEN:
+ plot_scale = SCALE_SCREEN;
+ break;
+ case SC_PRINT:
+ plot_scale = SCALE_PRINT;
+ break;
+ }
+}
+
+void ProfileGraphicsView::showEvent(QShowEvent* event)
+{
+ // Program just opened,
+ // but the dive was not ploted.
+ // force a replot by modifying the dive
+ // hold by the view, and issuing a plot.
+ if (dive){
+ dive = 0;
+ plot(get_dive(selected_dive));
+ }
+}
+
+void ProfileGraphicsView::clear()
+{
+ scene()->clear();
+ resetTransform();
+ zoomLevel = 0;
+ toolTip = 0;
+}
+
+void ProfileGraphicsView::plot(struct dive *d)
+{
+ if (dive == d)
+ return;
+
+ clear();
+ dive = d;
+
+ if(!isVisible() || !dive){
+ return;
+ }
+
+ scene()->setSceneRect(0,0, viewport()->width()-50, viewport()->height()-50);
+
+ QSettings s;
+ s.beginGroup("ProfileMap");
+ QPointF toolTipPos = s.value("tooltip_position", QPointF(0,0)).toPointF();
+ s.endGroup();
+
+ toolTip = new ToolTipItem();
+ toolTip->setPos(toolTipPos);
+
+ scene()->addItem(toolTip);
+
+ struct divecomputer *dc = &dive->dc;
+
+
+ // Fix this for printing / screen later.
+ // plot_set_scale(scale_mode_t);
+
+ if (!dc->samples) {
+ static struct sample fake[4];
+ static struct divecomputer fakedc;
+ fakedc = dive->dc;
+ fakedc.sample = fake;
+ fakedc.samples = 4;
+
+ /* The dive has no samples, so create a few fake ones. This assumes an
+ ascent/descent rate of 9 m/min, which is just below the limit for FAST. */
+ int duration = dive->dc.duration.seconds;
+ int maxdepth = dive->dc.maxdepth.mm;
+ int asc_desc_time = dive->dc.maxdepth.mm*60/9000;
+ if (asc_desc_time * 2 >= duration)
+ asc_desc_time = duration / 2;
+ fake[1].time.seconds = asc_desc_time;
+ fake[1].depth.mm = maxdepth;
+ fake[2].time.seconds = duration - asc_desc_time;
+ fake[2].depth.mm = maxdepth;
+ fake[3].time.seconds = duration * 1.00;
+ fakedc.events = dc->events;
+ dc = &fakedc;
+ }
+
+ /*
+ * Set up limits that are independent of
+ * the dive computer
+ */
+ calculate_max_limits(dive, dc, &gc);
+
+ QRectF profile_grid_area = scene()->sceneRect();
+ gc.maxx = (profile_grid_area.width() - 2 * profile_grid_area.x());
+ gc.maxy = (profile_grid_area.height() - 2 * profile_grid_area.y());
+
+ dc = select_dc(dc);
+
+ /* This is per-dive-computer. Right now we just do the first one */
+ gc.pi = *create_plot_info(dive, dc, &gc);
+
+ /* Depth profile */
+ plot_depth_profile();
+
+ plot_events(dc);
+
+ /* Temperature profile */
+ plot_temperature_profile();
+
+ /* Cylinder pressure plot */
+ plot_cylinder_pressure(dive, dc);
+
+ /* Text on top of all graphs.. */
+ plot_temperature_text();
+
+ plot_depth_text();
+
+ plot_cylinder_pressure_text();
+
+ plot_deco_text();
+
+ /* Bounding box */
+ QPen pen = defaultPen;
+ pen.setColor(profile_color[TIME_GRID].at(0));
+ QGraphicsRectItem *rect = new QGraphicsRectItem(profile_grid_area);
+ rect->setPen(pen);
+ scene()->addItem(rect);
+
+ /* Put the dive computer name in the lower left corner */
+ QString nick(get_dc_nickname(dc->model, dc->deviceid));
+ if (nick.isEmpty())
+ nick = QString(dc->model);
+
+ if (nick.isEmpty())
+ nick = tr("unknown divecomputer");
+
+ gc.leftx = 0; gc.rightx = 1.0;
+ gc.topy = 0; gc.bottomy = 1.0;
+
+ text_render_options_t computer = {DC_TEXT_SIZE, TIME_TEXT, LEFT, MIDDLE};
+ diveComputer = plot_text(&computer, QPointF(gc.leftx, gc.bottomy), nick);
+ // The Time ruler should be right after the DiveComputer:
+ timeMarkers->setPos(0, diveComputer->y());
+
+ if (PP_GRAPHS_ENABLED) {
+ plot_pp_gas_profile();
+ plot_pp_text();
+ }
+
+
+ /* now shift the translation back by half the margin;
+ * this way we can draw the vertical scales on both sides */
+ //cairo_translate(gc->cr, -drawing_area->x / 2.0, 0);
+
+ //gc->maxx += drawing_area->x;
+ //gc->leftx = -(drawing_area->x / drawing_area->width) / 2.0;
+ //gc->rightx = 1.0 - gc->leftx;
+
+ plot_depth_scale();
+
+#if 0
+ if (gc->printer) {
+ free(pi->entry);
+ last_pi_entry = pi->entry = NULL;
+ pi->nr = 0;
+ }
+#endif
+
+ QRectF r = scene()->itemsBoundingRect();
+ scene()->setSceneRect(r.x() - 15, r.y() -15, r.width() + 30, r.height() + 30);
+ if(zoomLevel == 0){
+ fitInView(sceneRect());
+ }
+}
+
+void ProfileGraphicsView::plot_depth_scale()
+{
+ int i, maxdepth, marker;
+ static text_render_options_t tro = {DEPTH_TEXT_SIZE, SAMPLE_DEEP, RIGHT, MIDDLE};
+
+ /* Depth markers: every 30 ft or 10 m*/
+ maxdepth = get_maxdepth(&gc.pi);
+ gc.topy = 0; gc.bottomy = maxdepth;
+
+ switch (prefs.units.length) {
+ case units::METERS: marker = 10000; break;
+ case units::FEET: marker = 9144; break; /* 30 ft */
+ }
+
+ QColor c(profile_color[DEPTH_GRID].first());
+
+ /* don't write depth labels all the way to the bottom as
+ * there may be other graphs below the depth plot (like
+ * partial pressure graphs) where this would look out
+ * of place - so we only make sure that we print the next
+ * marker below the actual maxdepth of the dive */
+ depthMarkers = new QGraphicsRectItem();
+ for (i = marker; i <= gc.pi.maxdepth + marker; i += marker) {
+ double d = get_depth_units(i, NULL, NULL);
+ plot_text(&tro, QPointF(-0.002, i), QString::number(d), depthMarkers);
+ }
+ scene()->addItem(depthMarkers);
+ depthMarkers->setPos(depthMarkers->pos().x() - 10, 0);
+}
+
+void ProfileGraphicsView::plot_pp_text()
+{
+ double pp, dpp, m;
+ int hpos;
+ static text_render_options_t tro = {PP_TEXT_SIZE, PP_LINES, LEFT, MIDDLE};
+
+ setup_pp_limits(&gc);
+ pp = floor(gc.pi.maxpp * 10.0) / 10.0 + 0.2;
+ dpp = pp > 4 ? 1.0 : 0.5;
+ hpos = gc.pi.entry[gc.pi.nr - 1].sec;
+ QColor c = profile_color[PP_LINES].first();
+
+ for (m = 0.0; m <= pp; m += dpp) {
+ QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, m), SCALEGC(hpos, m));
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ plot_text(&tro, QPointF(hpos + 30, m), QString::number(m));
+ }
+}
+
+void ProfileGraphicsView::plot_pp_gas_profile()
+{
+ int i;
+ struct plot_data *entry;
+ struct plot_info *pi = &gc.pi;
+
+ setup_pp_limits(&gc);
+ QColor c;
+ QPointF from, to;
+ //if (prefs.pp_graphs.pn2) {
+ c = profile_color[PN2].first();
+ entry = pi->entry;
+ from = QPointF(SCALEGC(entry->sec, entry->pn2));
+ for (i = 1; i < pi->nr; i++) {
+ entry++;
+ if (entry->pn2 < prefs.pp_graphs.pn2_threshold){
+ to = QPointF(SCALEGC(entry->sec, entry->pn2));
+ QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y());
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ from = to;
+ }
+ else{
+ from = QPointF(SCALEGC(entry->sec, entry->pn2));
+ }
+ }
+
+ c = profile_color[PN2_ALERT].first();
+ entry = pi->entry;
+ from = QPointF(SCALEGC(entry->sec, entry->pn2));
+ for (i = 1; i < pi->nr; i++) {
+ entry++;
+ if (entry->pn2 >= prefs.pp_graphs.pn2_threshold){
+ to = QPointF(SCALEGC(entry->sec, entry->pn2));
+ QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y());
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ from = to;
+ }
+ else{
+ from = QPointF(SCALEGC(entry->sec, entry->pn2));
+ }
+ }
+ //}
+
+ //if (prefs.pp_graphs.phe) {
+ c = profile_color[PHE].first();
+ entry = pi->entry;
+
+ from = QPointF(SCALEGC(entry->sec, entry->phe));
+ for (i = 1; i < pi->nr; i++) {
+ entry++;
+ if (entry->phe < prefs.pp_graphs.phe_threshold){
+ to = QPointF(SCALEGC(entry->sec, entry->phe));
+ QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y());
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ from = to;
+ }
+ else{
+ from = QPointF(SCALEGC(entry->sec, entry->phe));
+ }
+ }
+
+ c = profile_color[PHE_ALERT].first();
+ entry = pi->entry;
+ from = QPointF(SCALEGC(entry->sec, entry->phe));
+ for (i = 1; i < pi->nr; i++) {
+ entry++;
+ if (entry->phe >= prefs.pp_graphs.phe_threshold){
+ to = QPointF(SCALEGC(entry->sec, entry->phe));
+ QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y());
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ from = to;
+ }
+ else{
+ from = QPointF(SCALEGC(entry->sec, entry->phe));
+ }
+ }
+ //}
+ //if (prefs.pp_graphs.po2) {
+ c = profile_color[PO2].first();
+ entry = pi->entry;
+ from = QPointF(SCALEGC(entry->sec, entry->po2));
+ for (i = 1; i < pi->nr; i++) {
+ entry++;
+ if (entry->po2 < prefs.pp_graphs.po2_threshold){
+ to = QPointF(SCALEGC(entry->sec, entry->po2));
+ QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y());
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ from = to;
+ }
+ else{
+ from = QPointF(SCALEGC(entry->sec, entry->po2));
+ }
+ }
+
+ c = profile_color[PO2_ALERT].first();
+ entry = pi->entry;
+ from = QPointF(SCALEGC(entry->sec, entry->po2));
+ for (i = 1; i < pi->nr; i++) {
+ entry++;
+ if (entry->po2 >= prefs.pp_graphs.po2_threshold){
+ to = QPointF(SCALEGC(entry->sec, entry->po2));
+ QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y());
+ item->setPen(QPen(c));
+ scene()->addItem(item);
+ from = to;
+ }
+ else{
+ from = QPointF(SCALEGC(entry->sec, entry->po2));
+ }
+ }
+ //}
+}
+
+void ProfileGraphicsView::plot_deco_text()
+{
+ if (prefs.profile_calc_ceiling) {
+ float x = gc.leftx + (gc.rightx - gc.leftx) / 2;
+ float y = gc.topy = 1.0;
+ static text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, CENTER, -0.2};
+ gc.bottomy = 0.0;
+ plot_text(&tro, QPointF(x, y), QString("GF %1/%2").arg(prefs.gflow * 100).arg(prefs.gfhigh * 100));
+ }
+}
+
+void ProfileGraphicsView::plot_cylinder_pressure_text()
+{
+ int i;
+ 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;
+ struct plot_info *pi = &gc.pi;
+
+ if (!get_cylinder_pressure_range(&gc))
+ return;
+
+ cyl = -1;
+ for (i = 0; i < pi->nr; i++) {
+ entry = pi->entry + i;
+ mbar = GET_PRESSURE(entry);
+
+ if (!mbar)
+ continue;
+ if (cyl != entry->cylinderindex) {
+ cyl = entry->cylinderindex;
+ if (!seen_cyl[cyl]) {
+ plot_pressure_value(mbar, entry->sec, LEFT, BOTTOM);
+ seen_cyl[cyl] = TRUE;
+ }
+ }
+ last_pressure[cyl] = mbar;
+ last_time[cyl] = entry->sec;
+ }
+
+ for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) {
+ if (last_time[cyl]) {
+ plot_pressure_value(last_pressure[cyl], last_time[cyl], CENTER, TOP);
+ }
+ }
+}
+
+void ProfileGraphicsView::plot_pressure_value(int mbar, int sec, double xalign, double yalign)
+{
+ int pressure;
+ const char *unit;
+
+ pressure = get_pressure_units(mbar, &unit);
+ static text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, xalign, yalign};
+ plot_text(&tro, QPointF(sec, mbar), QString("%1 %2").arg(pressure).arg(unit));
+}
+
+void ProfileGraphicsView::plot_depth_text()
+{
+ int maxtime, maxdepth;
+
+ /* Get plot scaling limits */
+ maxtime = get_maxtime(&gc.pi);
+ maxdepth = get_maxdepth(&gc.pi);
+
+ gc.leftx = 0; gc.rightx = maxtime;
+ gc.topy = 0; gc.bottomy = maxdepth;
+
+ plot_text_samples();
+}
+
+void ProfileGraphicsView::plot_text_samples()
+{
+ static text_render_options_t deep = {14, SAMPLE_DEEP, CENTER, TOP};
+ static text_render_options_t shallow = {14, SAMPLE_SHALLOW, CENTER, BOTTOM};
+ int i;
+ int last = -1;
+
+ struct plot_info* pi = &gc.pi;
+
+ for (i = 0; i < pi->nr; i++) {
+ struct plot_data *entry = pi->entry + i;
+
+ if (entry->depth < 2000)
+ continue;
+
+ if ((entry == entry->max[2]) && entry->depth / 100 != last) {
+ plot_depth_sample(entry, &deep);
+ last = entry->depth / 100;
+ }
+
+ if ((entry == entry->min[2]) && entry->depth / 100 != last) {
+ plot_depth_sample(entry, &shallow);
+ last = entry->depth / 100;
+ }
+
+ if (entry->depth != last)
+ last = -1;
+ }
+}
+
+void ProfileGraphicsView::plot_depth_sample(struct plot_data *entry,text_render_options_t *tro)
+{
+ int sec = entry->sec, decimals;
+ double d;
+
+ d = get_depth_units(entry->depth, &decimals, NULL);
+
+ plot_text(tro, QPointF(sec, entry->depth), QString("%1").arg(d, 0, 'f', 1));
+}
+
+
+void ProfileGraphicsView::plot_temperature_text()
+{
+ int i;
+ int last = -300, sec = 0;
+ int last_temperature = 0, last_printed_temp = 0;
+ plot_info *pi = &gc.pi;
+
+ if (!setup_temperature_limits(&gc))
+ return;
+
+ for (i = 0; i < pi->nr; i++) {
+ struct plot_data *entry = pi->entry+i;
+ int mkelvin = entry->temperature;
+ sec = entry->sec;
+
+ if (!mkelvin)
+ continue;
+ last_temperature = mkelvin;
+ /* 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)
+ plot_single_temp_text(sec,mkelvin);
+ last_printed_temp = mkelvin;
+ }
+ /* 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_temperature > 200000 &&
+ ((abs(last_temperature - last_printed_temp) > 500) || ((double)last / (double)sec < 0.75)))
+ plot_single_temp_text(sec, last_temperature);
+}
+
+void ProfileGraphicsView::plot_single_temp_text(int sec, int mkelvin)
+{
+ double deg;
+ const char *unit;
+ static text_render_options_t tro = {TEMP_TEXT_SIZE, TEMP_TEXT, LEFT, TOP};
+ deg = get_temp_units(mkelvin, &unit);
+ plot_text(&tro, QPointF(sec, mkelvin), QString("%1%2").arg(deg, 0, 'f', 1).arg(unit)); //"%.2g%s"
+}
+
+void ProfileGraphicsView::plot_cylinder_pressure(struct dive *dive, struct divecomputer *dc)
+{
+ int i;
+ int last = -1, last_index = -1;
+ int lift_pen = FALSE;
+ int first_plot = TRUE;
+ int sac = 0;
+ struct plot_data *last_entry = NULL;
+
+ if (!get_cylinder_pressure_range(&gc))
+ return;
+
+ QPointF from, to;
+ for (i = 0; i < gc.pi.nr; i++) {
+ int mbar;
+ struct plot_data *entry = gc.pi.entry + i;
+
+ mbar = GET_PRESSURE(entry);
+ if (entry->cylinderindex != last_index) {
+ lift_pen = TRUE;
+ last_entry = NULL;
+ }
+ if (!mbar) {
+ lift_pen = TRUE;
+ continue;
+ }
+ if (!last_entry) {
+ last = i;
+ last_entry = entry;
+ sac = get_local_sac(entry, gc.pi.entry + i + 1, dive);
+ } else {
+ int j;
+ sac = 0;
+ for (j = last; j < i; j++)
+ sac += get_local_sac(gc.pi.entry + j, gc.pi.entry + j + 1, dive);
+ sac /= (i - last);
+ if (entry->sec - last_entry->sec >= SAC_WINDOW) {
+ last++;
+ last_entry = gc.pi.entry + last;
+ }
+ }
+
+ QColor c = get_sac_color(sac, dive->sac);
+
+ if (lift_pen) {
+ if (!first_plot && entry->cylinderindex == last_index) {
+ /* if we have a previous event from the same tank,
+ * draw at least a short line */
+ int prev_pr;
+ prev_pr = GET_PRESSURE(entry - 1);
+
+ QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC((entry-1)->sec, prev_pr), SCALEGC(entry->sec, mbar));
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ } else {
+ first_plot = FALSE;
+ from = QPointF(SCALEGC(entry->sec, mbar));
+ }
+ lift_pen = FALSE;
+ } else {
+ to = QPointF(SCALEGC(entry->sec, mbar));
+ QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y());
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ }
+
+
+ from = QPointF(SCALEGC(entry->sec, mbar));
+ last_index = entry->cylinderindex;
+ }
+}
+
+
+/* set the color for the pressure plot according to temporary sac rate
+ * as compared to avg_sac; the calculation simply maps the delta between
+ * sac and avg_sac to indexes 0 .. (SAC_COLORS - 1) with everything
+ * more than 6000 ml/min below avg_sac mapped to 0 */
+QColor ProfileGraphicsView::get_sac_color(int sac, int avg_sac)
+{
+ int sac_index = 0;
+ int delta = sac - avg_sac + 7000;
+
+ if (!gc.printer) {
+ sac_index = delta / 2000;
+ if (sac_index < 0)
+ sac_index = 0;
+ if (sac_index > SAC_COLORS - 1)
+ sac_index = SAC_COLORS - 1;
+ return profile_color[ (color_indice_t) (SAC_COLORS_START_IDX + sac_index)].first();
+ }
+ return profile_color[SAC_DEFAULT].first();
+}
+
+void ProfileGraphicsView::plot_events(struct divecomputer *dc)
+{
+ struct event *event = dc->events;
+
+// if (gc->printer){
+// return;
+// }
+
+ while (event) {
+ plot_one_event(event);
+ event = event->next;
+ }
+}
+
+void ProfileGraphicsView::plot_one_event(struct event *ev)
+{
+ int i, depth = 0;
+ struct plot_info *pi = &gc.pi;
+
+ /* is plotting of this event disabled? */
+ if (ev->name) {
+ for (i = 0; i < evn_used; i++) {
+ if (! strcmp(ev->name, ev_namelist[i].ev_name)) {
+ if (ev_namelist[i].plot_ev)
+ break;
+ else
+ return;
+ }
+ }
+ }
+
+ if (ev->time.seconds < 30 && !strcmp(ev->name, "gaschange"))
+ /* a gas change in the first 30 seconds is the way of some dive computers
+ * to tell us the gas that is used; let's not plot a marker for that */
+ return;
+
+ for (i = 0; i < pi->nr; i++) {
+ struct plot_data *data = pi->entry + i;
+ if (ev->time.seconds < data->sec)
+ break;
+ depth = data->depth;
+ }
+
+ /* draw a little triangular marker and attach tooltip */
+
+ int x = SCALEXGC(ev->time.seconds);
+ int y = SCALEYGC(depth);
+
+ EventItem *item = new EventItem();
+ item->setPos(x, y);
+ scene()->addItem(item);
+
+ /* we display the event on screen - so translate */
+ QString name = tr(ev->name);
+ if (ev->value) {
+ if (ev->name && name == "gaschange") {
+ unsigned int he = ev->value >> 16;
+ unsigned int o2 = ev->value & 0xffff;
+
+ name += ": ";
+ name += (he) ? QString("%1/%2").arg(o2, he)
+ : (o2 == 21) ? name += tr("air")
+ : QString("%1% %2").arg(o2).arg("O" UTF8_SUBSCRIPT_2);
+
+ } else if (ev->name && !strcmp(ev->name, "SP change")) {
+ name += QString(":%1").arg((double) ev->value / 1000);
+ } else {
+ name += QString(":%1").arg(ev->value);
+ }
+ } else if (ev->name && name == "SP change") {
+ name += tr("Bailing out to OC");
+ } else {
+ name += ev->flags == SAMPLE_FLAGS_BEGIN ? tr("Starts with space!"," begin") :
+ ev->flags == SAMPLE_FLAGS_END ? tr("Starts with space!", " end") : "";
+ }
+
+ //item->setToolTipController(toolTip);
+ //item->addToolTip(name);
+ item->setToolTip(name);
+}
+
+void ProfileGraphicsView::plot_depth_profile()
+{
+ int i, incr;
+ int sec, depth;
+ struct plot_data *entry;
+ int maxtime, maxdepth, marker, maxline;
+ int increments[8] = { 10, 20, 30, 60, 5*60, 10*60, 15*60, 30*60 };
+
+ /* Get plot scaling limits */
+ maxtime = get_maxtime(&gc.pi);
+ maxdepth = get_maxdepth(&gc.pi);
+
+ gc.maxtime = maxtime;
+
+ /* Time markers: at most every 10 seconds, but no more than 12 markers.
+ * We start out with 10 seconds and increment up to 30 minutes,
+ * depending on the dive time.
+ * This allows for 6h dives - enough (I hope) for even the craziest
+ * divers - but just in case, for those 8h depth-record-breaking dives,
+ * we double the interval if this still doesn't get us to 12 or fewer
+ * time markers */
+ i = 0;
+ while (maxtime / increments[i] > 12 && i < 7)
+ i++;
+ incr = increments[i];
+ while (maxtime / incr > 12)
+ incr *= 2;
+
+ gc.leftx = 0; gc.rightx = maxtime;
+ gc.topy = 0; gc.bottomy = 1.0;
+
+ last_gc = gc;
+
+ QColor c = profile_color[TIME_GRID].at(0);
+ for (i = incr; i < maxtime; i += incr) {
+ QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(i, 0), SCALEGC(i, 1));
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ }
+
+ timeMarkers = new QGraphicsRectItem();
+ /* now the text on the time markers */
+ struct text_render_options tro = {DEPTH_TEXT_SIZE, TIME_TEXT, CENTER, TOP};
+ if (maxtime < 600) {
+ /* Be a bit more verbose with shorter dives */
+ for (i = incr; i < maxtime; i += incr)
+ plot_text(&tro, QPointF(i, 0), QString("%1:%2").arg(i/60).arg(i%60), timeMarkers);
+ } else {
+ /* Only render the time on every second marker for normal dives */
+ for (i = incr; i < maxtime; i += 2 * incr)
+ plot_text(&tro, QPointF(i, 0), QString("%1").arg(QString::number(i/60)), timeMarkers);
+ }
+ timeMarkers->setPos(0,0);
+ scene()->addItem(timeMarkers);
+
+ /* Depth markers: every 30 ft or 10 m*/
+ gc.leftx = 0; gc.rightx = 1.0;
+ gc.topy = 0; gc.bottomy = maxdepth;
+ switch (prefs.units.length) {
+ case units::METERS:
+ marker = 10000;
+ break;
+ case units::FEET:
+ marker = 9144;
+ break; /* 30 ft */
+ }
+ maxline = MAX(gc.pi.maxdepth + marker, maxdepth * 2 / 3);
+
+ c = profile_color[DEPTH_GRID].at(0);
+
+ for (i = marker; i < maxline; i += marker) {
+ QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, i), SCALEGC(1, i));
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ }
+
+ gc.leftx = 0; gc.rightx = maxtime;
+ c = profile_color[MEAN_DEPTH].at(0);
+
+ /* Show mean depth */
+ if (! gc.printer) {
+ QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, gc.pi.meandepth),
+ SCALEGC(gc.pi.entry[gc.pi.nr - 1].sec, gc.pi.meandepth));
+ QPen pen(defaultPen);
+ pen.setColor(c);
+ item->setPen(pen);
+ scene()->addItem(item);
+ }
+
+#if 0
+ /*
+ * These are good for debugging text placement etc,
+ * but not for actual display..
+ */
+ if (0) {
+ plot_smoothed_profile(gc, pi);
+ plot_minmax_profile(gc, pi);
+ }
+#endif
+
+ /* Do the depth profile for the neat fill */
+ gc.topy = 0; gc.bottomy = maxdepth;
+
+ entry = gc.pi.entry;
+
+ QPolygonF p;
+ QLinearGradient pat(0.0,0.0,0.0,scene()->height());
+ QGraphicsPolygonItem *neatFill = NULL;
+
+ p.append(QPointF(SCALEGC(0, 0)));
+ for (i = 0; i < gc.pi.nr; i++, entry++)
+ p.append(QPointF(SCALEGC(entry->sec, entry->depth)));
+
+ /* Show any ceiling we may have encountered */
+ for (i = gc.pi.nr - 1; i >= 0; i--, entry--) {
+ if (entry->ndl) {
+ /* non-zero NDL implies this is a safety stop, no ceiling */
+ p.append(QPointF(SCALEGC(entry->sec, 0)));
+ } else if (entry->stopdepth < entry->depth) {
+ p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth)));
+ } else {
+ p.append(QPointF(SCALEGC(entry->sec, entry->depth)));
+ }
+ }
+ pat.setColorAt(1, profile_color[DEPTH_BOTTOM].first());
+ pat.setColorAt(0, profile_color[DEPTH_TOP].first());
+
+ neatFill = new QGraphicsPolygonItem();
+ neatFill->setPolygon(p);
+ neatFill->setBrush(QBrush(pat));
+ neatFill->setPen(QPen(QBrush(Qt::transparent),0));
+ scene()->addItem(neatFill);
+
+
+ /* if the user wants the deco ceiling more visible, do that here (this
+ * basically draws over the background that we had allowed to shine
+ * through so far) */
+ // TODO: port the prefs.profile_red_ceiling to QSettings
+
+ //if (prefs.profile_red_ceiling) {
+ p.clear();
+ pat.setColorAt(0, profile_color[CEILING_SHALLOW].first());
+ pat.setColorAt(1, profile_color[CEILING_DEEP].first());
+
+ entry = gc.pi.entry;
+ p.append(QPointF(SCALEGC(0, 0)));
+ for (i = 0; i < gc.pi.nr; i++, entry++) {
+ if (entry->ndl == 0 && entry->stopdepth) {
+ if (entry->ndl == 0 && entry->stopdepth < entry->depth) {
+ p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth)));
+ } else {
+ p.append(QPointF(SCALEGC(entry->sec, entry->depth)));
+ }
+ } else {
+ p.append(QPointF(SCALEGC(entry->sec, 0)));
+ }
+ }
+
+ neatFill = new QGraphicsPolygonItem();
+ neatFill->setBrush(QBrush(pat));
+ neatFill->setPolygon(p);
+ neatFill->setPen(QPen(QBrush(Qt::NoBrush),0));
+ scene()->addItem(neatFill);
+ //}
+
+ /* finally, plot the calculated ceiling over all this */
+ // TODO: Port the profile_calc_ceiling to QSettings
+ // if (prefs.profile_calc_ceiling) {
+
+ pat.setColorAt(0, profile_color[CALC_CEILING_SHALLOW].first());
+ pat.setColorAt(1, profile_color[CALC_CEILING_DEEP].first());
+
+ entry = gc.pi.entry;
+ p.clear();
+ p.append(QPointF(SCALEGC(0, 0)));
+ for (i = 0; i < gc.pi.nr; i++, entry++) {
+ if (entry->ceiling)
+ p.append(QPointF(SCALEGC(entry->sec, entry->ceiling)));
+ else
+ p.append(QPointF(SCALEGC(entry->sec, 0)));
+ }
+ p.append(QPointF(SCALEGC((entry-1)->sec, 0)));
+ neatFill = new QGraphicsPolygonItem();
+ neatFill->setPolygon(p);
+ neatFill->setPen(QPen(QBrush(Qt::NoBrush),0));
+ neatFill->setBrush(pat);
+ scene()->addItem(neatFill);
+ //}
+ /* next show where we have been bad and crossed the dc's ceiling */
+ pat.setColorAt(0, profile_color[CEILING_SHALLOW].first());
+ pat.setColorAt(1, profile_color[CEILING_DEEP].first());
+
+ entry = gc.pi.entry;
+ p.clear();
+ p.append(QPointF(SCALEGC(0, 0)));
+ for (i = 0; i < gc.pi.nr; i++, entry++)
+ p.append(QPointF(SCALEGC(entry->sec, entry->depth)));
+
+ for (i = gc.pi.nr - 1; i >= 0; i--, entry--) {
+ if (entry->ndl == 0 && entry->stopdepth > entry->depth) {
+ p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth)));
+ } else {
+ p.append(QPointF(SCALEGC(entry->sec, entry->depth)));
+ }
+ }
+
+ neatFill = new QGraphicsPolygonItem();
+ neatFill->setPolygon(p);
+ neatFill->setPen(QPen(QBrush(Qt::NoBrush),0));
+ neatFill->setBrush(QBrush(pat));
+ scene()->addItem(neatFill);
+
+ /* Now do it again for the velocity colors */
+ entry = gc.pi.entry;
+ for (i = 1; i < gc.pi.nr; i++) {
+ entry++;
+ sec = entry->sec;
+ /* we want to draw the segments in different colors
+ * representing the vertical velocity, so we need to
+ * chop this into short segments */
+ depth = entry->depth;
+ QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(entry[-1].sec, entry[-1].depth), SCALEGC(sec, depth));
+ QPen pen(defaultPen);
+ pen.setColor(profile_color[ (color_indice_t) (VELOCITY_COLORS_START_IDX + entry->velocity)].first());
+ item->setPen(pen);
+ scene()->addItem(item);
+ }
+}
+
+QGraphicsSimpleTextItem *ProfileGraphicsView::plot_text(text_render_options_t *tro,const QPointF& pos, const QString& text, QGraphicsItem *parent)
+{
+ QFontMetrics fm(font());
+
+ double dx = tro->hpos * (fm.width(text));
+ double dy = tro->vpos * (fm.height());
+
+ QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem(text, parent);
+ QPointF point(SCALEGC(pos.x(), pos.y())); // This is neded because of the SCALE macro.
+
+ item->setPos(point.x() + dx, point.y() + dy);
+ item->setBrush(QBrush(profile_color[tro->color].first()));
+ item->setFlag(QGraphicsItem::ItemIgnoresTransformations);
+
+ if(!parent)
+ scene()->addItem(item);
+ return item;
+}
+
+void ProfileGraphicsView::resizeEvent(QResizeEvent *event)
+{
+ plot(dive);
+}
+
+void ProfileGraphicsView::plot_temperature_profile()
+{
+ int last = 0;
+
+ if (!setup_temperature_limits(&gc))
+ return;
+
+ QPointF from;
+ QPointF to;
+ QColor color = profile_color[TEMP_PLOT].first();
+
+ for (int i = 0; i < gc.pi.nr; i++) {
+ struct plot_data *entry = gc.pi.entry + i;
+ int mkelvin = entry->temperature;
+ int sec = entry->sec;
+ if (!mkelvin) {
+ if (!last)
+ continue;
+ mkelvin = last;
+ }
+ if (last) {
+ to = QPointF(SCALEGC(sec, mkelvin));
+ QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y());
+ QPen pen(defaultPen);
+ pen.setColor(color);
+ item->setPen(pen);
+ scene()->addItem(item);
+ from = to;
+ }
+ else{
+ from = QPointF(SCALEGC(sec, mkelvin));
+ }
+ last = mkelvin;
+ }
+}
+
+void ToolTipItem::addToolTip(const QString& toolTip, const QIcon& icon)
+{
+ QGraphicsPixmapItem *iconItem = 0;
+ double yValue = title->boundingRect().height() + SPACING;
+ Q_FOREACH(ToolTip t, toolTips) {
+ yValue += t.second->boundingRect().height();
+ }
+ if (!icon.isNull()) {
+ iconItem = new QGraphicsPixmapItem(icon.pixmap(ICON_SMALL,ICON_SMALL), this);
+ iconItem->setPos(SPACING, yValue);
+ }
+
+ QGraphicsSimpleTextItem *textItem = new QGraphicsSimpleTextItem(toolTip, this);
+ textItem->setPos(SPACING + ICON_SMALL + SPACING, yValue);
+ textItem->setBrush(QBrush(Qt::white));
+ textItem->setFlag(ItemIgnoresTransformations);
+ toolTips[toolTip] = qMakePair(iconItem, textItem);
+ expand();
+}
+
+void ToolTipItem::removeToolTip(const QString& toolTip)
+{
+ ToolTip toBeRemoved = toolTips[toolTip];
+ delete toBeRemoved.first;
+ delete toBeRemoved.second;
+ toolTips.remove(toolTip);
+
+ int toolTipIndex = 0;
+
+ // We removed a toolTip, let's move the others to the correct location
+ Q_FOREACH(ToolTip t, toolTips) {
+ double yValue = title->boundingRect().height() + SPACING + toolTipIndex * ICON_SMALL + SPACING;
+
+ // Icons can be null.
+ if (t.first)
+ t.first->setPos(SPACING, yValue);
+
+ t.second->setPos(SPACING + ICON_SMALL + SPACING, yValue);
+ toolTipIndex++;
+ }
+
+ expand();
+}
+
+void ToolTipItem::refresh(struct graphics_context *gc, QPointF pos)
+{
+ clear();
+ int time = (pos.x() * gc->maxtime) / scene()->sceneRect().width();
+ char buffer[500];
+ get_plot_details(gc, time, buffer, 500);
+ addToolTip(QString(buffer));
+
+ QList<QGraphicsItem*> items = scene()->items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder, transform());
+ Q_FOREACH(QGraphicsItem *item, items) {
+ if (!item->toolTip().isEmpty())
+ addToolTip(item->toolTip());
+ }
+
+}
+
+void ToolTipItem::clear()
+{
+ Q_FOREACH(ToolTip t, toolTips) {
+ delete t.first;
+ delete t.second;
+ }
+ toolTips.clear();
+ expand();
+}
+
+void ToolTipItem::setRect(const QRectF& r)
+{
+
+ // qDeleteAll(childItems());
+ delete background;
+
+ rectangle = r;
+ setBrush(QBrush(Qt::white));
+ setPen(QPen(Qt::black, 0.5));
+
+ // Creates a 2pixels border
+ QPainterPath border;
+ border.addRoundedRect(-4, -4, rectangle.width() + 8, rectangle.height() + 10, 3, 3);
+ border.addRoundedRect(-1, -1, rectangle.width() + 3, rectangle.height() + 4, 3, 3);
+ setPath(border);
+
+ QPainterPath bg;
+ bg.addRoundedRect(-1, -1, rectangle.width() + 3, rectangle.height() + 4, 3, 3);
+
+ QColor c = QColor(Qt::black);
+ c.setAlpha(155);
+
+ QGraphicsPathItem *b = new QGraphicsPathItem(bg, this);
+ b->setFlag(ItemStacksBehindParent);
+ b->setFlags(ItemIgnoresTransformations);
+ b->setBrush(c);
+ b->setPen(QPen(QBrush(Qt::transparent), 0));
+ b->setZValue(-10);
+ background = b;
+
+ updateTitlePosition();
+}
+
+
+void ToolTipItem::collapse()
+{
+ QPropertyAnimation *animation = new QPropertyAnimation(this, "rect");
+ animation->setDuration(100);
+ animation->setStartValue(boundingRect());
+ animation->setEndValue(QRect(0, 0, ICON_SMALL, ICON_SMALL));
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+}
+
+void ToolTipItem::expand()
+{
+
+ if (!title){
+ return;
+ }
+
+ QRectF nextRectangle;
+ double width = 0, height = title->boundingRect().height() + SPACING;
+ Q_FOREACH(ToolTip t, toolTips) {
+ if (t.second->boundingRect().width() > width)
+ width = t.second->boundingRect().width();
+ height += t.second->boundingRect().height();
+ }
+ /* Left padding, Icon Size, space, right padding */
+ width += SPACING + ICON_SMALL + SPACING + SPACING;
+
+ if (width < title->boundingRect().width() + SPACING*2)
+ width = title->boundingRect().width() + SPACING*2;
+
+ if(height < ICON_SMALL)
+ height = ICON_SMALL;
+
+ nextRectangle.setWidth(width);
+ nextRectangle.setHeight(height);
+
+ QPropertyAnimation *animation = new QPropertyAnimation(this, "rect");
+ animation->setDuration(100);
+ animation->setStartValue(rectangle);
+ animation->setEndValue(nextRectangle);
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+
+}
+
+ToolTipItem::ToolTipItem(QGraphicsItem* parent): QGraphicsPathItem(parent), background(0)
+{
+ title = new QGraphicsSimpleTextItem(tr("Information"), this);
+ separator = new QGraphicsLineItem(this);
+
+ setFlag(ItemIgnoresTransformations);
+ setFlag(ItemIsMovable);
+
+ updateTitlePosition();
+ setZValue(99);
+}
+
+void ToolTipItem::updateTitlePosition()
+{
+ if (rectangle.width() < title->boundingRect().width() + SPACING*4) {
+ QRectF newRect = rectangle;
+ newRect.setWidth(title->boundingRect().width() + SPACING*4);
+ newRect.setHeight(newRect.height() ? newRect.height() : ICON_SMALL);
+ setRect(newRect);
+ }
+
+ title->setPos(boundingRect().width()/2 - title->boundingRect().width()/2 -1, 0);
+ title->setFlag(ItemIgnoresTransformations);
+ title->setPen(QPen(Qt::white, 1));
+ title->setBrush(Qt::white);
+
+ if (toolTips.size() > 0) {
+ double x1 = 0;
+ double y1 = title->pos().y() + SPACING/2 + title->boundingRect().height();
+ double x2 = boundingRect().width() - 10;
+ double y2 = y1;
+
+ separator->setLine(x1, y1, x2, y2);
+ separator->setFlag(ItemIgnoresTransformations);
+ separator->setPen(QPen(Qt::white));
+ separator->show();
+ } else {
+ separator->hide();
+ }
+}
+
+EventItem::EventItem(QGraphicsItem* parent): QGraphicsPolygonItem(parent)
+{
+ setFlag(ItemIgnoresTransformations);
+ setFlag(ItemIsFocusable);
+ setAcceptHoverEvents(true);
+
+ QPolygonF poly;
+ poly.push_back(QPointF(-8, 16));
+ poly.push_back(QPointF(8, 16));
+ poly.push_back(QPointF(0, 0));
+ poly.push_back(QPointF(-8, 16));
+
+ QPen defaultPen ;
+ defaultPen.setJoinStyle(Qt::RoundJoin);
+ defaultPen.setCapStyle(Qt::RoundCap);
+ defaultPen.setWidth(2);
+ defaultPen.setCosmetic(true);
+
+ QPen pen = defaultPen;
+ pen.setBrush(QBrush(profile_color[ALERT_BG].first()));
+
+ setPolygon(poly);
+ setBrush(QBrush(profile_color[ALERT_BG].first()));
+ setPen(pen);
+
+ QGraphicsLineItem *line = new QGraphicsLineItem(0,5,0,10, this);
+ line->setPen(QPen(Qt::black, 2));
+
+ QGraphicsEllipseItem *ball = new QGraphicsEllipseItem(-1, 12, 2,2, this);
+ ball->setBrush(QBrush(Qt::black));
+
+}