diff --git a/profile-widget/diveeventitem.cpp b/profile-widget/diveeventitem.cpp index c5848c9ea..38dc5f8de 100644 --- a/profile-widget/diveeventitem.cpp +++ b/profile-widget/diveeventitem.cpp @@ -19,6 +19,8 @@ static int depthAtTime(const plot_info &pi, duration_t time); DiveEventItem::DiveEventItem(const struct dive *d, int idx, const struct event &ev, struct gasmix lastgasmix, const plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent) : DivePixmapItem(parent), + text(setupToolTipString(d, ev, lastgasmix)), + pixmap(setupPixmap(d, ev, lastgasmix, pixmaps)), vAxis(vAxis), hAxis(hAxis), idx(idx), @@ -27,55 +29,42 @@ DiveEventItem::DiveEventItem(const struct dive *d, int idx, const struct event & depth(depthAtTime(pi, ev.time)) { setFlag(ItemIgnoresTransformations); - - setupPixmap(lastgasmix, pixmaps); - setupToolTipString(lastgasmix); + setPixmap(pixmap); recalculatePos(); + + if (ev.type == SAMPLE_EVENT_BOOKMARK) + setOffset(QPointF(0.0, -pixmap.height())); } DiveEventItem::~DiveEventItem() { } -void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps) +QPixmap DiveEventItem::setupPixmap(const struct dive *dive, const struct event &ev, struct gasmix lastgasmix, const DivePixmaps &pixmaps) { event_severity severity = ev.get_severity(); - if (ev.name.empty()) { - setPixmap(pixmaps.warning); - } else if (same_string_caseinsensitive(ev.name.c_str(), "modechange")) { - if (ev.value == 0) - setPixmap(pixmaps.bailout); - else - setPixmap(pixmaps.onCCRLoop); - } else if (ev.type == SAMPLE_EVENT_BOOKMARK) { - setPixmap(pixmaps.bookmark); - setOffset(QPointF(0.0, -pixmap().height())); - } else if (ev.is_gaschange()) { + if (ev.name.empty()) + return pixmaps.warning; + + if (same_string_caseinsensitive(ev.name.c_str(), "modechange")) + return ev.value == 0 ? pixmaps.bailout : pixmaps.onCCRLoop; + + if (ev.type == SAMPLE_EVENT_BOOKMARK) + return pixmaps.bookmark; + + if (ev.is_gaschange()) { struct gasmix mix = dive->get_gasmix_from_event(ev); struct icd_data icd_data; bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data); - if (mix.he.permille) { - if (icd) - setPixmap(pixmaps.gaschangeTrimixICD); - else - setPixmap(pixmaps.gaschangeTrimix); - } else if (gasmix_is_air(mix)) { - if (icd) - setPixmap(pixmaps.gaschangeAirICD); - else - setPixmap(pixmaps.gaschangeAir); - } else if (mix.o2.permille == 1000) { - if (icd) - setPixmap(pixmaps.gaschangeOxygenICD); - else - setPixmap(pixmaps.gaschangeOxygen); - } else { - if (icd) - setPixmap(pixmaps.gaschangeEANICD); - else - setPixmap(pixmaps.gaschangeEAN); - } - } else if ((((ev.flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 1) || + if (mix.he.permille) + return icd ? pixmaps.gaschangeTrimixICD : pixmaps.gaschangeTrimix; + if (gasmix_is_air(mix)) + return icd ? pixmaps.gaschangeAirICD : pixmaps.gaschangeAir; + if (mix.o2.permille == 1000) + return icd ? pixmaps.gaschangeOxygenICD : pixmaps.gaschangeOxygen; + return icd ? pixmaps.gaschangeEANICD : pixmaps.gaschangeEAN; + } + if ((((ev.flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 1) || // those are useless internals of the dive computer same_string_caseinsensitive(ev.name.c_str(), "heading") || (same_string_caseinsensitive(ev.name.c_str(), "SP change") && ev.time.seconds == 0)) { @@ -86,35 +75,35 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix // so set an "almost invisible" pixmap (a narrow but somewhat tall, basically transparent pixmap) // that allows tooltips to work when we don't want to show a specific // pixmap for an event, but want to show the event value in the tooltip - setPixmap(pixmaps.transparent); - } else if (severity == EVENT_SEVERITY_INFO) { - setPixmap(pixmaps.info); - } else if (severity == EVENT_SEVERITY_WARN) { - setPixmap(pixmaps.warning); - } else if (severity == EVENT_SEVERITY_ALARM) { - setPixmap(pixmaps.violation); - } else if (same_string_caseinsensitive(ev.name.c_str(), "violation") || // generic libdivecomputer + return pixmaps.transparent; + } + if (severity == EVENT_SEVERITY_INFO) + return pixmaps.info; + if (severity == EVENT_SEVERITY_WARN) + return pixmaps.warning; + if (severity == EVENT_SEVERITY_ALARM) + return pixmaps.violation; + if (same_string_caseinsensitive(ev.name.c_str(), "violation") || // generic libdivecomputer same_string_caseinsensitive(ev.name.c_str(), "Safety stop violation") || // the rest are from the Uemis downloader same_string_caseinsensitive(ev.name.c_str(), "pO₂ ascend alarm") || same_string_caseinsensitive(ev.name.c_str(), "RGT alert") || same_string_caseinsensitive(ev.name.c_str(), "Dive time alert") || same_string_caseinsensitive(ev.name.c_str(), "Low battery alert") || - same_string_caseinsensitive(ev.name.c_str(), "Speed alarm")) { - setPixmap(pixmaps.violation); - } else if (same_string_caseinsensitive(ev.name.c_str(), "non stop time") || // generic libdivecomputer - same_string_caseinsensitive(ev.name.c_str(), "safety stop") || - same_string_caseinsensitive(ev.name.c_str(), "safety stop (voluntary)") || - same_string_caseinsensitive(ev.name.c_str(), "Tank change suggested") || // Uemis downloader - same_string_caseinsensitive(ev.name.c_str(), "Marker")) { - setPixmap(pixmaps.info); - } else { - // we should do some guessing based on the type / name of the event; - // for now they all get the warning icon - setPixmap(pixmaps.warning); - } + same_string_caseinsensitive(ev.name.c_str(), "Speed alarm")) + return pixmaps.violation; + if (same_string_caseinsensitive(ev.name.c_str(), "non stop time") || // generic libdivecomputer + same_string_caseinsensitive(ev.name.c_str(), "safety stop") || + same_string_caseinsensitive(ev.name.c_str(), "safety stop (voluntary)") || + same_string_caseinsensitive(ev.name.c_str(), "Tank change suggested") || // Uemis downloader + same_string_caseinsensitive(ev.name.c_str(), "Marker")) + return pixmaps.info; + + // we should do some guessing based on the type / name of the event; + // for now they all get the warning icon + return pixmaps.warning; } -void DiveEventItem::setupToolTipString(struct gasmix lastgasmix) +QString DiveEventItem::setupToolTipString(const struct dive *dive, const struct event &ev, struct gasmix lastgasmix) { // we display the event on screen - so translate QString name = gettextFromC::tr(ev.name.c_str()); @@ -158,7 +147,7 @@ void DiveEventItem::setupToolTipString(struct gasmix lastgasmix) name += ev.flags & SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") : ev.flags & SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : ""; } - setToolTip(QString("  ") + name); + return name; } void DiveEventItem::eventVisibilityChanged(const QString&, bool) diff --git a/profile-widget/diveeventitem.h b/profile-widget/diveeventitem.h index 215ae4eb0..ea21181e9 100644 --- a/profile-widget/diveeventitem.h +++ b/profile-widget/diveeventitem.h @@ -23,10 +23,11 @@ public: static bool isInteresting(const struct dive *d, const struct divecomputer *dc, const struct event &ev, const struct plot_info &pi, int firstSecond, int lastSecond); - + const QString text; + const QPixmap pixmap; private: - void setupToolTipString(struct gasmix lastgasmix); - void setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps); + static QString setupToolTipString(const struct dive *d, const struct event &ev, struct gasmix lastgasmix); + static QPixmap setupPixmap(const struct dive *d, const struct event &ev, struct gasmix lastgasmix, const DivePixmaps &pixmaps); void recalculatePos(); DiveCartesianAxis *vAxis; DiveCartesianAxis *hAxis; diff --git a/profile-widget/profilescene.cpp b/profile-widget/profilescene.cpp index bb44ce371..ff6c78fe1 100644 --- a/profile-widget/profilescene.cpp +++ b/profile-widget/profilescene.cpp @@ -615,3 +615,14 @@ int ProfileScene::timeAt(QPointF pos) const { return lrint(timeAxis->valueAt(pos)); } + +std::vector> ProfileScene::eventsAt(QPointF pos) const +{ + std::vector> res; + for (const auto &item: eventItems) { + if (!item->contains(item->mapFromScene(pos))) + continue; + res.emplace_back(item->text, item->pixmap); + } + return res; +} diff --git a/profile-widget/profilescene.h b/profile-widget/profilescene.h index 33488b841..b65aa1a58 100644 --- a/profile-widget/profilescene.h +++ b/profile-widget/profilescene.h @@ -50,6 +50,7 @@ public: double calcZoomPosition(double zoom, double originalPos, double delta); const plot_info &getPlotInfo() const; int timeAt(QPointF pos) const; + std::vector> eventsAt(QPointF pos) const; const struct dive *d; int dc; diff --git a/profile-widget/profileview.cpp b/profile-widget/profileview.cpp index 3033989dd..5ea1cdb30 100644 --- a/profile-widget/profileview.cpp +++ b/profile-widget/profileview.cpp @@ -184,8 +184,6 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) //else //plotPicturesInternal(d, flags & RenderFlags::Instant); - //toolTipItem->refresh(d, mapToScene(mapFromGlobal(QCursor::pos())), currentState == PLAN); - update(); // OK, how long did this take us? Anything above the second is way too long, @@ -201,8 +199,9 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) if (!tooltip) tooltip = createChartItem(dpr); if (prefs.infobox) { + QPoint pos = mapFromGlobal(QCursor::pos()).toPoint(); tooltip->setVisible(true); - tooltip->update(d, dpr, 0, profileScene->getPlotInfo(), flags & RenderFlags::PlanMode); + updateTooltip(pos, flags & RenderFlags::PlanMode); } else { tooltip->setVisible(false); } @@ -296,8 +295,6 @@ void ProfileView::mouseMoveEvent(QMouseEvent *event) if (panning) pan(pos.x(), pos.y()); - //toolTipItem->refresh(d, mapToScene(mapFromGlobal(QCursor::pos())), currentState == PLAN); - //if (currentState == PLAN || currentState == EDIT) { //QRectF rect = profileScene->profileRegion; //auto [miny, maxy] = profileScene->profileYAxis->screenMinMax(); @@ -378,14 +375,15 @@ void ProfileView::hoverMoveEvent(QHoverEvent *event) { if (!profileScene) return; - QPointF pos = event->pos(); - int time = profileScene->timeAt(pos); - bool requires_update = false; - if (tooltip) { - tooltip->update(d, dpr, time, profileScene->getPlotInfo(), false); // TODO: plan mode - requires_update = true; - } - - if (requires_update) + if (tooltip && prefs.infobox) { + updateTooltip(event->pos(), false); // TODO: plan mode update(); + } +} + +void ProfileView::updateTooltip(QPointF pos, bool plannerMode) +{ + int time = profileScene->timeAt(pos); + auto events = profileScene->eventsAt(pos); + tooltip->update(d, dpr, time, profileScene->getPlotInfo(), events, plannerMode); } diff --git a/profile-widget/profileview.h b/profile-widget/profileview.h index 5aaf449bc..8d70a0479 100644 --- a/profile-widget/profileview.h +++ b/profile-widget/profileview.h @@ -74,6 +74,7 @@ private: void mouseReleaseEvent(QMouseEvent *event) override; ChartItemPtr tooltip; + void updateTooltip(QPointF pos, bool plannerMode); // For mobile int getDiveId() const; diff --git a/profile-widget/tooltipitem.cpp b/profile-widget/tooltipitem.cpp index f9dba6783..906c9d572 100644 --- a/profile-widget/tooltipitem.cpp +++ b/profile-widget/tooltipitem.cpp @@ -99,19 +99,13 @@ static QPixmap drawTissues(const plot_info &pInfo, double dpr, int idx, bool inP return tissues.scaled(new_width, new_height); } -void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &pInfo, bool inPlanner) +void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &pInfo, + const std::vector> &events, bool inPlanner) { auto [idx, lines] = get_plot_details_new(d, pInfo, time); QPixmap tissues = drawTissues(pInfo, dpr, idx, inPlanner); - - //TODO: add event tool tips! - //const auto l = scene()->items(pos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, - //scene()->views().first()->transform()); - //for (QGraphicsItem *item: l) { - //if (!item->toolTip().isEmpty()) - //addToolTip(item->toolTip(), QPixmap()); - //} + std::vector event_sizes; width = title.size().width(); @@ -125,12 +119,21 @@ void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &p strings.push_back(std::make_pair(s, w)); } + height = (static_cast(strings.size()) + 1.0) * fontHeight; + + for (auto &[s, pixmap]: events) { + double text_width = fm.size(Qt::TextSingleLine, s).width(); + double total_width = pixmap.width() + text_width; + double h = std::max(static_cast(pixmap.height()), fontHeight); + width = std::max(width, total_width); + height += h; + event_sizes.emplace_back(text_width, h); + } + width += tissues.width(); width += 6.0 * tooltipBorder; - - height = 4.0 * tooltipBorder + title.height() + - std::max((static_cast(strings.size()) + 1.0) * fontHeight, - static_cast(tissues.height())); + height = std::max(height, static_cast(tissues.height())); + height += 4.0 * tooltipBorder + title.height(); ChartRectItem::resize(QSizeF(width, height)); painter->setFont(font); @@ -147,6 +150,15 @@ void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &p painter->drawText(rect, s); y += fontHeight; } + for (size_t i = 0; i < events.size(); ++i) { + QSizeF size = event_sizes[i]; + auto &[s, pixmap] = events[i]; + painter->drawPixmap(lrint(x), lrint(y + (size.height() - pixmap.height())/2.0), pixmap, + 0, 0, pixmap.width(), pixmap.height()); + QRectF rect(x + pixmap.width(), round(y + (size.height() - fontHeight) / 2.0), size.width(), fontHeight); + painter->drawText(rect, s); + y += size.height(); + } setTextureDirty(); } diff --git a/profile-widget/tooltipitem.h b/profile-widget/tooltipitem.h index 6bddae805..5e58529eb 100644 --- a/profile-widget/tooltipitem.h +++ b/profile-widget/tooltipitem.h @@ -13,7 +13,8 @@ struct plot_info; class ToolTipItem : public ChartRectItem { public: ToolTipItem(ChartView &view, double dpr); - void update(const dive *d, double dpr, int time, const plot_info &pInfo, bool inPlanner); + void update(const dive *d, double dpr, int time, const plot_info &pInfo, + const std::vector> &events, bool inPlanner); private: QFont font; QFontMetrics fm;