#include "profilegraphics.h" #include "mainwindow.h" #include "divelistview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #include 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 itemsAtPos = scene()->items(mapToScene(mapFromGlobal(event->globalPos()))); Q_FOREACH(QGraphicsItem * i, itemsAtPos) { EventItem *item = dynamic_cast(i); if (!item) continue; action = new QAction(&m); action->setText(tr("Remove Event")); action->setData(QVariant::fromValue(item)); // so we know what to remove. connect(action, SIGNAL(triggered(bool)), this, SLOT(removeEvent())); m.addAction(action); action = new QAction(&m); action->setText(tr("Hide similar events")); action->setData(QVariant::fromValue(item)); connect(action, SIGNAL(triggered(bool)), this, SLOT(hideEvents())); m.addAction(action); 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(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(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(sender()); EventItem *item = static_cast(action->data().value()); 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(sender()); EventItem *item = static_cast(action->data().value()); 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); }