mirror of
https://github.com/subsurface/subsurface.git
synced 2024-12-01 06:30:26 +00:00
8c56b2f45e
This patch makes the 'Zoomed Plot' a preference that can be stored and retrieved, this way if the user sets the plot to be 'zoomed', this information will persist even if they closed or opened subsurface again. Also, added the 'Scale' button on the new profile, but didn't did the glue code yet. Signed-off-by: Tomaz Canabrava <tomaz.canabrava@intel.com> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
1646 lines
45 KiB
C++
1646 lines
45 KiB
C++
#include "profilegraphics.h"
|
|
#include "mainwindow.h"
|
|
#include "divelistview.h"
|
|
|
|
#include <QGraphicsScene>
|
|
#include <QResizeEvent>
|
|
#include <QGraphicsLineItem>
|
|
#include <QScrollBar>
|
|
#include <QPen>
|
|
#include <QBrush>
|
|
#include <QDebug>
|
|
#include <QLineF>
|
|
#include <QSettings>
|
|
#include <QIcon>
|
|
#include <QPropertyAnimation>
|
|
#include <QGraphicsSceneHoverEvent>
|
|
#include <QMouseEvent>
|
|
#include <QToolBar>
|
|
#include <qtextdocument.h>
|
|
#include <QMessageBox>
|
|
#include <limits>
|
|
|
|
#include "../color.h"
|
|
#include "../display.h"
|
|
#include "../dive.h"
|
|
#include "../profile.h"
|
|
#include "../device.h"
|
|
#include "../helpers.h"
|
|
#include "../planner.h"
|
|
#include "../gettextfromc.h"
|
|
#include "profile/divetooltipitem.h"
|
|
|
|
#include <libdivecomputer/parser.h>
|
|
#include <libdivecomputer/version.h>
|
|
|
|
static struct graphics_context last_gc;
|
|
|
|
#if PRINT_IMPLEMENTED
|
|
static double plot_scale = SCALE_SCREEN;
|
|
#endif
|
|
|
|
extern struct ev_select *ev_namelist;
|
|
extern int evn_allocated;
|
|
extern int evn_used;
|
|
|
|
#define TOOLBAR_POS \
|
|
QPoint(viewport()->geometry().width() - toolBarProxy->boundingRect().width(), \
|
|
viewport()->geometry().height() - toolBarProxy->boundingRect().height() )
|
|
|
|
ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent),
|
|
toolTip(0) , diveId(0), diveDC(0), rulerItem(0), toolBarProxy(0)
|
|
{
|
|
printMode = false;
|
|
isGrayscale = false;
|
|
rulerEnabled = false;
|
|
gc.printer = false;
|
|
fill_profile_color();
|
|
setScene(new QGraphicsScene(this));
|
|
|
|
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);
|
|
}
|
|
|
|
/* since we cannot use translate() directly on the scene we hack on
|
|
* the scroll bars (hidden) functionality */
|
|
void ProfileGraphicsView::scrollViewTo(const QPoint pos)
|
|
{
|
|
if (!zoomLevel)
|
|
return;
|
|
QScrollBar *vs = verticalScrollBar();
|
|
QScrollBar *hs = horizontalScrollBar();
|
|
const qreal yRat = pos.y() / sceneRect().height();
|
|
const qreal xRat = pos.x() / sceneRect().width();
|
|
const int vMax = vs->maximum();
|
|
const int hMax = hs->maximum();
|
|
const int vMin = vs->minimum();
|
|
const int hMin = hs->minimum();
|
|
/* QScrollBar receives crazy negative values for minimum */
|
|
vs->setValue(yRat * (vMax - vMin) + vMin * 0.9);
|
|
hs->setValue(xRat * (hMax - hMin) + hMin * 0.9);
|
|
}
|
|
|
|
void ProfileGraphicsView::wheelEvent(QWheelEvent* event)
|
|
{
|
|
if (!toolTip)
|
|
return;
|
|
|
|
// doesn't seem to work for Qt 4.8.1
|
|
// setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
|
|
|
|
// Scale the view / do the zoom
|
|
QPoint toolTipPos = mapFromScene(toolTip->pos());
|
|
|
|
double scaleFactor = 1.15;
|
|
if (event->delta() > 0 && zoomLevel < 20) {
|
|
scale(scaleFactor, scaleFactor);
|
|
zoomLevel++;
|
|
} else if (event->delta() < 0 && zoomLevel > 0) {
|
|
// Zooming out
|
|
scale(1.0 / scaleFactor, 1.0 / scaleFactor);
|
|
zoomLevel--;
|
|
}
|
|
|
|
scrollViewTo(event->pos());
|
|
toolTip->setPos(mapToScene(toolTipPos));
|
|
toolBarProxy->setPos(mapToScene(TOOLBAR_POS));
|
|
if (zoomLevel != 0) {
|
|
toolBarProxy->hide();
|
|
} else {
|
|
toolBarProxy->show();
|
|
}
|
|
}
|
|
|
|
void ProfileGraphicsView::contextMenuEvent(QContextMenuEvent* event)
|
|
{
|
|
if (selected_dive == -1)
|
|
return;
|
|
QMenu m;
|
|
QMenu *gasChange = m.addMenu(tr("Add Gas Change"));
|
|
GasSelectionModel *model = GasSelectionModel::instance();
|
|
model->repopulate();
|
|
int rowCount = model->rowCount();
|
|
for (int i = 0; i < rowCount; i++) {
|
|
QAction *action = new QAction(&m);
|
|
action->setText( model->data(model->index(i, 0),Qt::DisplayRole).toString());
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(changeGas()));
|
|
action->setData(event->globalPos());
|
|
gasChange->addAction(action);
|
|
}
|
|
QAction *action = m.addAction(tr("Add Bookmark"), this, SLOT(addBookmark()));
|
|
action->setData(event->globalPos());
|
|
QList<QGraphicsItem*> itemsAtPos = scene()->items(mapToScene(mapFromGlobal(event->globalPos())));
|
|
Q_FOREACH(QGraphicsItem *i, itemsAtPos) {
|
|
EventItem *item = dynamic_cast<EventItem*>(i);
|
|
if (!item)
|
|
continue;
|
|
action = new QAction(&m);
|
|
action->setText(tr("Remove Event"));
|
|
action->setData(QVariant::fromValue<void*>(item)); // so we know what to remove.
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(removeEvent()));
|
|
m.addAction(action);
|
|
action = new QAction(&m);
|
|
action->setText(tr("Hide similar events"));
|
|
action->setData(QVariant::fromValue<void*>(item));
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(hideEvents()));
|
|
m.addAction(action);
|
|
break;
|
|
}
|
|
bool some_hidden = false;
|
|
for (int i = 0; i < evn_used; i++) {
|
|
if (ev_namelist[i].plot_ev == false) {
|
|
some_hidden = true;
|
|
break;
|
|
}
|
|
}
|
|
if (some_hidden) {
|
|
action = m.addAction(tr("Unhide all events"), this, SLOT(unhideEvents()));
|
|
action->setData(event->globalPos());
|
|
}
|
|
m.exec(event->globalPos());
|
|
}
|
|
|
|
void ProfileGraphicsView::addBookmark()
|
|
{
|
|
QAction *action = qobject_cast<QAction*>(sender());
|
|
QPoint globalPos = action->data().toPoint();
|
|
QPoint viewPos = mapFromGlobal(globalPos);
|
|
QPointF scenePos = mapToScene(viewPos);
|
|
int seconds = scenePos.x() / gc.maxx * (gc.rightx - gc.leftx) + gc.leftx;
|
|
add_event(current_dc, seconds, SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark");
|
|
mark_divelist_changed(true);
|
|
plot(current_dive, true);
|
|
}
|
|
|
|
void ProfileGraphicsView::changeGas()
|
|
{
|
|
QAction *action = qobject_cast<QAction*>(sender());
|
|
QPoint globalPos = action->data().toPoint();
|
|
QPoint viewPos = mapFromGlobal(globalPos);
|
|
QPointF scenePos = mapToScene(viewPos);
|
|
QString gas = action->text();
|
|
int o2, he;
|
|
validate_gas(gas.toUtf8().constData(), &o2, &he);
|
|
int seconds = scenePos.x() / gc.maxx * (gc.rightx - gc.leftx) + gc.leftx;
|
|
add_gas_switch_event(current_dive, current_dc, seconds, get_gasidx(current_dive, o2, he));
|
|
// this means we potentially have a new tank that is being used and needs to be shown
|
|
fixup_dive(current_dive);
|
|
MainWindow::instance()->information()->updateDiveInfo(selected_dive);
|
|
mark_divelist_changed(true);
|
|
plot(current_dive, true);
|
|
}
|
|
|
|
void ProfileGraphicsView::hideEvents()
|
|
{
|
|
QAction *action = qobject_cast<QAction*>(sender());
|
|
EventItem *item = static_cast<EventItem*>(action->data().value<void*>());
|
|
struct event *event = item->ev;
|
|
|
|
if (QMessageBox::question(MainWindow::instance(), TITLE_OR_TEXT(
|
|
tr("Hide events"),
|
|
tr("Hide all %1 events?").arg(event->name)),
|
|
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
|
|
if (event->name) {
|
|
for (int i = 0; i < evn_used; i++) {
|
|
if (! strcmp(event->name, ev_namelist[i].ev_name)) {
|
|
ev_namelist[i].plot_ev = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
plot(current_dive, true);
|
|
}
|
|
}
|
|
|
|
void ProfileGraphicsView::unhideEvents()
|
|
{
|
|
for (int i = 0; i < evn_used; i++) {
|
|
ev_namelist[i].plot_ev = true;
|
|
}
|
|
plot(current_dive, true);
|
|
}
|
|
|
|
void ProfileGraphicsView::removeEvent()
|
|
{
|
|
QAction *action = qobject_cast<QAction*>(sender());
|
|
EventItem *item = static_cast<EventItem*>(action->data().value<void*>());
|
|
struct event *event = item->ev;
|
|
|
|
if (QMessageBox::question(MainWindow::instance(), TITLE_OR_TEXT(
|
|
tr("Remove the selected event?"),
|
|
tr("%1 @ %2:%3").arg(event->name)
|
|
.arg(event->time.seconds / 60)
|
|
.arg(event->time.seconds % 60, 2, 10, QChar('0'))),
|
|
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
|
|
struct event **ep = ¤t_dc->events;
|
|
while (ep && *ep != event)
|
|
ep = &(*ep)->next;
|
|
if (ep) {
|
|
*ep = event->next;
|
|
free(event);
|
|
}
|
|
mark_divelist_changed(true);
|
|
plot(current_dive, true);
|
|
}
|
|
}
|
|
|
|
|
|
void ProfileGraphicsView::mouseMoveEvent(QMouseEvent* event)
|
|
{
|
|
if (!toolTip)
|
|
return;
|
|
|
|
toolTip->refresh(&gc, mapToScene(event->pos()));
|
|
QPoint toolTipPos = mapFromScene(toolTip->pos());
|
|
scrollViewTo(event->pos());
|
|
|
|
if (zoomLevel == 0) {
|
|
QGraphicsView::mouseMoveEvent(event);
|
|
} else {
|
|
toolTip->setPos(mapToScene(toolTipPos));
|
|
toolBarProxy->setPos(mapToScene(TOOLBAR_POS));
|
|
}
|
|
}
|
|
|
|
bool ProfileGraphicsView::eventFilter(QObject* obj, QEvent* event)
|
|
{
|
|
if (event->type() == QEvent::Leave) {
|
|
if (toolTip && toolTip->isExpanded())
|
|
toolTip->collapse();
|
|
return true;
|
|
}
|
|
|
|
// This will "Eat" the default tooltip behavior if it is not on the toolBar.
|
|
if (event->type() == QEvent::GraphicsSceneHelp) {
|
|
if (toolBarProxy && !toolBarProxy->geometry().contains(mapToScene(mapFromGlobal(QCursor::pos())))) {
|
|
event->ignore();
|
|
return true;
|
|
}
|
|
}
|
|
return QGraphicsView::eventFilter(obj, event);
|
|
}
|
|
|
|
#if PRINT_IMPLEMENTED
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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 (diveId && !scene()->items().count()) {
|
|
diveId = 0;
|
|
plot(get_dive(selected_dive));
|
|
}
|
|
if (toolBarProxy)
|
|
toolBarProxy->setPos(mapToScene(TOOLBAR_POS));
|
|
}
|
|
|
|
void ProfileGraphicsView::clear()
|
|
{
|
|
resetTransform();
|
|
zoomLevel = 0;
|
|
if (toolTip) {
|
|
scene()->removeItem(toolTip);
|
|
toolTip->deleteLater();
|
|
toolTip = 0;
|
|
}
|
|
if (toolBarProxy) {
|
|
scene()->removeItem(toolBarProxy);
|
|
toolBarProxy->deleteLater();
|
|
toolBarProxy = 0;
|
|
}
|
|
if (rulerItem) {
|
|
remove_ruler();
|
|
rulerItem->destNode()->deleteLater();
|
|
rulerItem->sourceNode()->deleteLater();
|
|
rulerItem->deleteLater();
|
|
rulerItem=0;
|
|
}
|
|
scene()->clear();
|
|
}
|
|
|
|
void ProfileGraphicsView::refresh()
|
|
{
|
|
clear();
|
|
plot(current_dive, true);
|
|
}
|
|
|
|
void ProfileGraphicsView::setPrintMode(bool mode, bool grayscale)
|
|
{
|
|
printMode = mode;
|
|
isGrayscale = grayscale;
|
|
}
|
|
|
|
QColor ProfileGraphicsView::getColor(const color_indice_t i)
|
|
{
|
|
return profile_color[i].at((isGrayscale) ? 1 : 0);
|
|
}
|
|
|
|
void ProfileGraphicsView::plot(struct dive *d, bool forceRedraw)
|
|
{
|
|
struct divecomputer *dc = NULL;
|
|
|
|
if (d)
|
|
dc = select_dc(&d->dc);
|
|
|
|
if (!forceRedraw && getDiveById(diveId) == d && (d && dc == diveDC))
|
|
return;
|
|
|
|
clear();
|
|
diveId = d ? d->id : 0;
|
|
diveDC = d ? dc : NULL;
|
|
|
|
if (!isVisible() || !d || !MainWindow::instance()) {
|
|
return;
|
|
}
|
|
setBackgroundBrush(getColor(BACKGROUND));
|
|
|
|
// best place to put the focus stealer code.
|
|
setFocusProxy(MainWindow::instance()->dive_list());
|
|
scene()->setSceneRect(0,0, viewport()->width()-50, viewport()->height()-50);
|
|
|
|
toolTip = new ToolTipItem();
|
|
installEventFilter(toolTip);
|
|
scene()->addItem(toolTip);
|
|
if (printMode)
|
|
toolTip->setVisible(false);
|
|
|
|
// Fix this for printing / screen later.
|
|
// plot_set_scale(scale_mode_t);
|
|
|
|
if (!dc || !dc->samples) {
|
|
dc = fake_dc(dc);
|
|
}
|
|
|
|
QString nick = get_dc_nickname(dc->model, dc->deviceid);
|
|
if (nick.isEmpty())
|
|
nick = QString(dc->model);
|
|
|
|
if (nick.isEmpty())
|
|
nick = tr("unknown divecomputer");
|
|
|
|
if ( tr("unknown divecomputer") == nick) {
|
|
mode = PLAN;
|
|
} else {
|
|
mode = DIVE;
|
|
}
|
|
|
|
/*
|
|
* Set up limits that are independent of
|
|
* the dive computer
|
|
*/
|
|
calculate_max_limits(d, 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());
|
|
|
|
/* This is per-dive-computer */
|
|
gc.pi = *create_plot_info(d, dc, &gc, printMode);
|
|
|
|
/* Bounding box */
|
|
QPen pen = defaultPen;
|
|
pen.setColor(getColor(TIME_GRID));
|
|
QGraphicsRectItem *rect = new QGraphicsRectItem(profile_grid_area);
|
|
rect->setPen(pen);
|
|
scene()->addItem(rect);
|
|
|
|
/* Depth profile */
|
|
plot_depth_profile(); // TODO: PARTIALLY PORTED.
|
|
plot_events(dc); // PORTED
|
|
|
|
if (rulerEnabled && !printMode) // TODO: NOT PORTED.
|
|
create_ruler();
|
|
|
|
/* Temperature profile */
|
|
plot_temperature_profile(); // PORTED
|
|
|
|
/* Cylinder pressure plot */
|
|
plot_cylinder_pressure(); // PORTED
|
|
|
|
/* Text on top of all graphs.. */ // TODO: NOT PORTED, ANY TEXT.
|
|
plot_temperature_text();
|
|
plot_depth_text();
|
|
plot_cylinder_pressure_text();
|
|
plot_deco_text();
|
|
|
|
// NOT PORTED.
|
|
/* Put the dive computer name in the lower left corner */
|
|
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, TOP};
|
|
diveComputer = plot_text(&computer, QPointF(gc.leftx, gc.bottomy), nick);
|
|
// The Time ruler should be right after the DiveComputer:
|
|
timeMarkers->setPos(0, diveComputer->y());
|
|
|
|
// NOT PORTED.
|
|
if (PP_GRAPHS_ENABLED) {
|
|
plot_pp_gas_profile();
|
|
plot_pp_text();
|
|
}
|
|
|
|
// NOT PORTED.
|
|
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());
|
|
}
|
|
toolTip->readPos();
|
|
|
|
if (mode == PLAN) {
|
|
timeEditor = new GraphicsTextEditor();
|
|
timeEditor->setPlainText(d->duration.seconds ? QString::number(d->duration.seconds/60) : tr("Set Duration: 10 minutes"));
|
|
timeEditor->setPos(profile_grid_area.width() - timeEditor->boundingRect().width(), timeMarkers->y());
|
|
timeEditor->document();
|
|
connect(timeEditor, SIGNAL(editingFinished(QString)), this, SLOT(edit_dive_time(QString)));
|
|
scene()->addItem(timeEditor);
|
|
}
|
|
|
|
if (!printMode)
|
|
addControlItems(d);
|
|
|
|
if (rulerEnabled && !printMode)
|
|
add_ruler();
|
|
|
|
gc.rightx = get_maxtime(&gc.pi);
|
|
}
|
|
|
|
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;
|
|
marker = M_OR_FT(10,30);
|
|
|
|
/* 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::addControlItems(struct dive *d)
|
|
{
|
|
QAction *scaleAction = new QAction(QIcon(":scale"), tr("Scale"), this);
|
|
QAction *rulerAction = new QAction(QIcon(":ruler"), tr("Ruler"), this);
|
|
QToolBar *toolBar = new QToolBar("", 0);
|
|
rulerAction->setToolTip(tr("Measure properties of dive segments"));
|
|
scaleAction->setToolTip(tr("Scale your dive to screen size"));
|
|
toolBar->addAction(rulerAction);
|
|
toolBar->addAction(scaleAction);
|
|
toolBar->setOrientation(Qt::Vertical);
|
|
//make toolbar transparent
|
|
//toolBar->setStyleSheet(QString::fromUtf8 ("background-color: rgba(255,255,255,0);"));
|
|
|
|
connect(scaleAction, SIGNAL(triggered()), this, SLOT(on_scaleAction()));
|
|
connect(rulerAction, SIGNAL(triggered()), this, SLOT(on_rulerAction()));
|
|
//Put it into the lower right corner of the profile
|
|
|
|
QString defaultDC(d->dc.model);
|
|
if (defaultDC == "manually added dive" || defaultDC == "planned dive") {
|
|
QAction *editAction = new QAction(QIcon(":edit"), tr("Edit"), this);
|
|
toolBar->addAction(editAction);
|
|
connect(editAction, SIGNAL(triggered()), MainWindow::instance(), SLOT(editCurrentDive()));
|
|
}
|
|
toolBarProxy = scene()->addWidget(toolBar);
|
|
toolBarProxy->setPos(mapToScene(TOOLBAR_POS));
|
|
toolBarProxy->setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
|
}
|
|
|
|
void ProfileGraphicsView::plot_pp_text()
|
|
{
|
|
double pp, dpp, m;
|
|
int hpos;
|
|
static text_render_options_t tro = {PP_TEXT_SIZE, PP_LINES, LEFT, -0.75};
|
|
QGraphicsRectItem *pressureMarkers = new QGraphicsRectItem();
|
|
|
|
setup_pp_limits(&gc);
|
|
pp = floor(gc.pi.maxpp * 10.0) / 10.0 + 0.2;
|
|
dpp = pp > 4 ? 0.5 : 0.25;
|
|
hpos = gc.pi.entry[gc.pi.nr - 1].sec;
|
|
QColor c = getColor(PP_LINES);
|
|
|
|
bool alt = true;
|
|
for (m = 0.0; m <= pp; m += dpp) {
|
|
QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, m), SCALEGC(hpos, m));
|
|
QPen pen(defaultPen);
|
|
pen.setColor(c);
|
|
if ( IS_FP_SAME(QString::number(m).toDouble(), QString::number(m).toInt())) {
|
|
pen.setStyle(Qt::DashLine);
|
|
pen.setWidthF(1.2);
|
|
}
|
|
item->setPen(pen);
|
|
scene()->addItem(item);
|
|
qreal textPos = hpos;
|
|
if (alt)
|
|
plot_text(&tro, QPointF(textPos, m), QString::number(m), pressureMarkers);
|
|
alt = !alt;
|
|
}
|
|
scene()->addItem(pressureMarkers);
|
|
pressureMarkers->setPos(pressureMarkers->pos().x() + 10, 0);
|
|
}
|
|
|
|
void ProfileGraphicsView::plot_add_line(int sec, double val, QColor c, QPointF &from)
|
|
{
|
|
QPointF to = QPointF(SCALEGC(sec, val));
|
|
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;
|
|
}
|
|
|
|
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;
|
|
QPointF legendPos = QPointF(scene()->sceneRect().width() * 0.4, scene()->sceneRect().height() - scene()->sceneRect().height()*0.02);
|
|
|
|
if (prefs.pp_graphs.pn2) {
|
|
c = getColor(PN2);
|
|
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)
|
|
plot_add_line(entry->sec, entry->pn2, c, from);
|
|
else
|
|
from = QPointF(SCALEGC(entry->sec, entry->pn2));
|
|
}
|
|
|
|
c = getColor(PN2_ALERT);
|
|
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)
|
|
plot_add_line(entry->sec, entry->pn2, c, from);
|
|
else
|
|
from = QPointF(SCALEGC(entry->sec, entry->pn2));
|
|
}
|
|
createPPLegend(trUtf8("pN" UTF8_SUBSCRIPT_2),getColor(PN2), legendPos);
|
|
}
|
|
|
|
if (prefs.pp_graphs.phe) {
|
|
c = getColor(PHE);
|
|
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)
|
|
plot_add_line(entry->sec, entry->phe, c, from);
|
|
else
|
|
from = QPointF(SCALEGC(entry->sec, entry->phe));
|
|
}
|
|
|
|
c = getColor(PHE_ALERT);
|
|
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)
|
|
plot_add_line(entry->sec, entry->phe, c, from);
|
|
else
|
|
from = QPointF(SCALEGC(entry->sec, entry->phe));
|
|
}
|
|
createPPLegend(trUtf8("pHe"),getColor(PHE), legendPos);
|
|
}
|
|
if (prefs.pp_graphs.po2) {
|
|
c = getColor(PO2);
|
|
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)
|
|
plot_add_line(entry->sec, entry->po2, c, from);
|
|
else
|
|
from = QPointF(SCALEGC(entry->sec, entry->po2));
|
|
}
|
|
|
|
c = getColor(PO2_ALERT);
|
|
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)
|
|
plot_add_line(entry->sec, entry->po2, c, from);
|
|
else
|
|
from = QPointF(SCALEGC(entry->sec, entry->po2));
|
|
}
|
|
createPPLegend(trUtf8("pO" UTF8_SUBSCRIPT_2),getColor(PO2), legendPos);
|
|
}
|
|
}
|
|
|
|
void ProfileGraphicsView::createPPLegend(QString title, const QColor& c, QPointF& legendPos)
|
|
{
|
|
QGraphicsRectItem *rect = new QGraphicsRectItem(0, 0, scene()->sceneRect().width() * 0.01, scene()->sceneRect().width() * 0.01);
|
|
rect->setBrush(QBrush(c));
|
|
rect->setPos(legendPos);
|
|
rect->setPen(QPen(QColor(Qt::transparent)));
|
|
QGraphicsSimpleTextItem *text = new QGraphicsSimpleTextItem(title);
|
|
text->setPos(legendPos.x() + rect->boundingRect().width() + 5, legendPos.y() );
|
|
scene()->addItem(rect);
|
|
scene()->addItem(text);
|
|
legendPos.setX(text->pos().x() + text->boundingRect().width() + 20);
|
|
if (printMode) {
|
|
QFont f = text->font();
|
|
f.setPointSizeF( f.pointSizeF() * 0.7);
|
|
text->setFont(f);
|
|
}
|
|
}
|
|
|
|
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, BOTTOM};
|
|
gc.bottomy = 0.0;
|
|
plot_text(&tro, QPointF(x, y), QString("GF %1/%2").arg(prefs.gflow).arg(prefs.gfhigh));
|
|
}
|
|
}
|
|
|
|
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;
|
|
struct dive *dive = getDiveById(diveId);
|
|
Q_ASSERT(dive != NULL);
|
|
|
|
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);
|
|
plot_gas_value(mbar, entry->sec, LEFT, TOP,
|
|
get_o2(&dive->cylinder[cyl].gasmix),
|
|
get_he(&dive->cylinder[cyl].gasmix));
|
|
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_gas_value(int mbar, int sec, double xalign, double yalign, int o2, int he)
|
|
{
|
|
QString gas;
|
|
if (is_air(o2, he))
|
|
gas = tr("air");
|
|
else if (he == 0)
|
|
gas = QString(tr("EAN%1")).arg((o2 + 5) / 10);
|
|
else
|
|
gas = QString("%1/%2").arg((o2 + 5) / 10).arg((he + 5) / 10);
|
|
static text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, xalign, yalign};
|
|
plot_text(&tro, QPointF(sec, mbar), gas);
|
|
|
|
}
|
|
|
|
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()
|
|
{
|
|
int i;
|
|
int last_index = -1;
|
|
int lift_pen = false;
|
|
int first_plot = true;
|
|
|
|
if (!get_cylinder_pressure_range(&gc))
|
|
return;
|
|
|
|
struct dive *dive = getDiveById(diveId);
|
|
Q_ASSERT(dive != NULL);
|
|
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;
|
|
}
|
|
if (!mbar) {
|
|
lift_pen = true;
|
|
continue;
|
|
}
|
|
|
|
QColor c = get_sac_color(entry->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 getColor((color_indice_t)(SAC_COLORS_START_IDX + sac_index));
|
|
}
|
|
return getColor(SAC_DEFAULT);
|
|
}
|
|
|
|
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;
|
|
struct plot_info *pi = &gc.pi;
|
|
struct plot_data *entry = NULL;
|
|
|
|
/* 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++) {
|
|
entry = pi->entry + i;
|
|
if (ev->time.seconds < entry->sec)
|
|
break;
|
|
}
|
|
|
|
/* If we didn't find the right event, don't dereference null */
|
|
if (entry == NULL)
|
|
return;
|
|
|
|
/* draw a little triangular marker and attach tooltip */
|
|
|
|
int x = SCALEXGC(ev->time.seconds);
|
|
int y = SCALEYGC(entry->depth);
|
|
struct dive *dive = getDiveById(diveId);
|
|
Q_ASSERT(dive != NULL);
|
|
EventItem *item = new EventItem(ev, 0, isGrayscale);
|
|
item->setPos(x, y);
|
|
scene()->addItem(item);
|
|
|
|
if (ev->type == 123){
|
|
QPixmap picture;
|
|
picture.load(ev->name);
|
|
scene()->addPixmap(picture.scaledToHeight(100, Qt::SmoothTransformation))->setPos(x, y + 10);
|
|
}
|
|
|
|
|
|
/* we display the event on screen - so translate (with the correct context for events) */
|
|
QString name = gettextFromC::instance()->tr(ev->name);
|
|
if (ev->value) {
|
|
if (ev->name && strcmp(ev->name, "gaschange") == 0) {
|
|
int he = get_he(&dive->cylinder[entry->cylinderindex].gasmix);
|
|
int o2 = get_o2(&dive->cylinder[entry->cylinderindex].gasmix);
|
|
|
|
name += ": ";
|
|
if (he)
|
|
name += QString("%1/%2").arg((o2 + 5) / 10).arg((he + 5) / 10);
|
|
else if (is_air(o2, he))
|
|
name += tr("air");
|
|
else
|
|
name += QString(tr("EAN%1")).arg((o2 + 5) / 10);
|
|
|
|
} 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 += "\n" + tr("Bailing out to OC");
|
|
} else {
|
|
name += ev->flags == SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") :
|
|
ev->flags == SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : "";
|
|
}
|
|
|
|
//item->setToolTipController(toolTip);
|
|
//item->addToolTip(name);
|
|
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;
|
|
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 (i < 7 && maxtime / increments[i] > 12)
|
|
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 = getColor(TIME_GRID);
|
|
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, LINE_DOWN};
|
|
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, 2, 10, QChar('0')), 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;
|
|
marker = M_OR_FT(10,30);
|
|
maxline = qMax(gc.pi.maxdepth + marker, maxdepth * 2 / 3);
|
|
|
|
c = getColor(DEPTH_GRID);
|
|
|
|
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 = getColor(MEAN_DEPTH);
|
|
|
|
/* 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);
|
|
|
|
struct text_render_options depth_tro = {DEPTH_TEXT_SIZE, MEAN_DEPTH, LEFT, TOP};
|
|
QString depthLabel = get_depth_string(gc.pi.meandepth, true, true);
|
|
plot_text(&depth_tro, QPointF(gc.leftx, gc.pi.meandepth), depthLabel, item);
|
|
tro.hpos = RIGHT;
|
|
plot_text(&depth_tro, QPointF(gc.pi.entry[gc.pi.nr - 1].sec, gc.pi.meandepth), depthLabel, 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 */
|
|
if (prefs.profile_dc_ceiling) {
|
|
for (i = gc.pi.nr - 1; i >= 0; i--, entry--) {
|
|
if (!entry->in_deco) {
|
|
/* not in deco 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, getColor(DEPTH_BOTTOM));
|
|
pat.setColorAt(0, getColor(DEPTH_TOP));
|
|
|
|
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) */
|
|
if (prefs.profile_dc_ceiling && prefs.profile_red_ceiling) {
|
|
p.clear();
|
|
pat.setColorAt(0, getColor(CEILING_SHALLOW));
|
|
pat.setColorAt(1, getColor(CEILING_DEEP));
|
|
|
|
entry = gc.pi.entry;
|
|
p.append(QPointF(SCALEGC(0, 0)));
|
|
for (i = 0; i < gc.pi.nr; i++, entry++) {
|
|
if (entry->in_deco && entry->stopdepth) {
|
|
if (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 */
|
|
if (prefs.profile_calc_ceiling) {
|
|
pat.setColorAt(0, getColor(CALC_CEILING_SHALLOW));
|
|
pat.setColorAt(1, getColor(CALC_CEILING_DEEP));
|
|
|
|
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);
|
|
}
|
|
|
|
/* plot the calculated ceiling for all tissues */
|
|
if (prefs.profile_calc_ceiling && prefs.calc_all_tissues) {
|
|
int k;
|
|
for (k=0; k<16; k++) {
|
|
pat.setColorAt(0, getColor(CALC_CEILING_SHALLOW));
|
|
pat.setColorAt(1, QColor(100, 100, 100, 50));
|
|
|
|
entry = gc.pi.entry;
|
|
p.clear();
|
|
p.append(QPointF(SCALEGC(0, 0)));
|
|
for (i = 0; i < gc.pi.nr; i++, entry++) {
|
|
if ((entry->ceilings)[k])
|
|
p.append(QPointF(SCALEGC(entry->sec, (entry->ceilings)[k])));
|
|
else
|
|
p.append(QPointF(SCALEGC(entry->sec, 0)));
|
|
}
|
|
p.append(QPointF(SCALEGC((entry-1)->sec, 0)));
|
|
neatFill = new QGraphicsPolygonItem();
|
|
neatFill->setPolygon(p);
|
|
neatFill->setBrush(pat);
|
|
scene()->addItem(neatFill);
|
|
}
|
|
}
|
|
|
|
/* next show where we have been bad and crossed the dc's ceiling */
|
|
if (prefs.profile_dc_ceiling) {
|
|
pat.setColorAt(0, getColor(CEILING_SHALLOW));
|
|
pat.setColorAt(1, getColor(CEILING_DEEP));
|
|
|
|
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-- , entry--; i >= 0; i--, entry--) {
|
|
if (entry->in_deco && 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(getColor((color_indice_t)(VELOCITY_COLORS_START_IDX + entry->velocity)));
|
|
item->setPen(pen);
|
|
scene()->addItem(item);
|
|
}
|
|
}
|
|
|
|
QGraphicsItemGroup *ProfileGraphicsView::plot_text(text_render_options_t *tro,const QPointF& pos, const QString& text, QGraphicsItem *parent)
|
|
{
|
|
QFont fnt(font());
|
|
QFontMetrics fm(fnt);
|
|
|
|
if (printMode)
|
|
fnt.setPixelSize(tro->size);
|
|
|
|
QPointF point(SCALEGC(pos.x(), pos.y())); // This is neded because of the SCALE macro.
|
|
double dx = tro->hpos * (fm.width(text));
|
|
double dy = tro->vpos * (fm.height());
|
|
|
|
QGraphicsItemGroup *group = new QGraphicsItemGroup(parent);
|
|
QPainterPath textPath;
|
|
/* addText() uses bottom-left text baseline and the -3 offset is probably slightly off
|
|
* for different font sizes. */
|
|
textPath.addText(0, fm.height() - 3, fnt, text);
|
|
QPainterPathStroker stroker;
|
|
stroker.setWidth(3);
|
|
QGraphicsPathItem *strokedItem = new QGraphicsPathItem(stroker.createStroke(textPath), group);
|
|
strokedItem->setBrush(QBrush(getColor(TEXT_BACKGROUND)));
|
|
strokedItem->setPen(Qt::NoPen);
|
|
|
|
QGraphicsPathItem *textItem = new QGraphicsPathItem(textPath, group);
|
|
textItem->setBrush(QBrush(getColor(tro->color)));
|
|
textItem->setPen(Qt::NoPen);
|
|
|
|
group->setPos(point.x() + dx, point.y() + dy);
|
|
if (!printMode)
|
|
group->setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
|
|
|
if (!parent)
|
|
scene()->addItem(group);
|
|
return group;
|
|
}
|
|
|
|
void ProfileGraphicsView::resizeEvent(QResizeEvent *event)
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
void ProfileGraphicsView::plot_temperature_profile()
|
|
{
|
|
int last = 0;
|
|
|
|
if (!setup_temperature_limits(&gc))
|
|
return;
|
|
|
|
QPointF from;
|
|
QPointF to;
|
|
QColor color = getColor(TEMP_PLOT);
|
|
|
|
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 ProfileGraphicsView::edit_dive_time(const QString& time)
|
|
{
|
|
// this should set the full time of the dive.
|
|
refresh();
|
|
}
|
|
|
|
void ProfileGraphicsView::on_rulerAction()
|
|
{
|
|
rulerEnabled = !rulerEnabled;
|
|
refresh();
|
|
}
|
|
|
|
void ProfileGraphicsView::on_scaleAction()
|
|
{
|
|
prefs.zoomed_plot = !prefs.zoomed_plot;
|
|
refresh();
|
|
}
|
|
|
|
QColor EventItem::getColor(const color_indice_t i)
|
|
{
|
|
return profile_color[i].at((isGrayscale) ? 1 : 0);
|
|
}
|
|
|
|
EventItem::EventItem(struct event *ev, QGraphicsItem* parent, bool grayscale): QGraphicsPixmapItem(parent), ev(ev), isGrayscale(grayscale)
|
|
{
|
|
if (ev->name && (strcmp(ev->name, "bookmark") == 0 || strcmp(ev->name, "heading") == 0)) {
|
|
setPixmap( QPixmap(QString(":flag")).scaled(20, 20, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
} else {
|
|
setPixmap( QPixmap(QString(":warning")).scaled(20, 20, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
void GraphicsTextEditor::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event)
|
|
{
|
|
// Remove the proxy filter so we can focus here.
|
|
MainWindow::instance()->graphics()->setFocusProxy(0);
|
|
setTextInteractionFlags(Qt::TextEditorInteraction | Qt::TextEditable);
|
|
}
|
|
|
|
void GraphicsTextEditor::keyReleaseEvent(QKeyEvent* event)
|
|
{
|
|
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
|
|
setTextInteractionFlags(Qt::NoTextInteraction);
|
|
emit editingFinished( toPlainText() );
|
|
MainWindow::instance()->graphics()->setFocusProxy(MainWindow::instance()->dive_list());
|
|
return;
|
|
}
|
|
emit textChanged( toPlainText() );
|
|
QGraphicsTextItem::keyReleaseEvent(event);
|
|
}
|