diff --git a/Subsurface-mobile.pro b/Subsurface-mobile.pro index 4db369411..a4ef4d4b6 100644 --- a/Subsurface-mobile.pro +++ b/Subsurface-mobile.pro @@ -172,6 +172,7 @@ SOURCES += subsurface-mobile-main.cpp \ profile-widget/diveprofileitem.cpp \ profile-widget/profilescene.cpp \ profile-widget/animationfunctions.cpp \ + profile-widget/divepixmapcache.cpp \ profile-widget/divepixmapitem.cpp \ profile-widget/divetooltipitem.cpp \ profile-widget/tankitem.cpp \ @@ -331,6 +332,7 @@ HEADERS += \ profile-widget/animationfunctions.h \ profile-widget/divecartesianaxis.h \ profile-widget/divelineitem.h \ + profile-widget/divepixmapcache.h \ profile-widget/divepixmapitem.h \ profile-widget/diverectitem.h \ profile-widget/divetextitem.h diff --git a/core/qthelper.cpp b/core/qthelper.cpp index 8a560f3d0..a244c1471 100644 --- a/core/qthelper.cpp +++ b/core/qthelper.cpp @@ -1743,4 +1743,3 @@ QImage renderSVGIconWidth(const char *id, int size) svg.render(&painter); return res; } - diff --git a/profile-widget/CMakeLists.txt b/profile-widget/CMakeLists.txt index eb89c3eb3..ba8548088 100644 --- a/profile-widget/CMakeLists.txt +++ b/profile-widget/CMakeLists.txt @@ -8,6 +8,8 @@ set(SUBSURFACE_PROFILE_LIB_SRCS diveeventitem.h divelineitem.cpp divelineitem.h + divepixmapcache.cpp + divepixmapcache.h divepixmapitem.cpp divepixmapitem.h divepercentageitem.cpp diff --git a/profile-widget/diveeventitem.cpp b/profile-widget/diveeventitem.cpp index cf7d0766b..b6a80a64a 100644 --- a/profile-widget/diveeventitem.cpp +++ b/profile-widget/diveeventitem.cpp @@ -1,23 +1,21 @@ // SPDX-License-Identifier: GPL-2.0 #include "profile-widget/diveeventitem.h" -#include "qt-models/diveplotdatamodel.h" #include "profile-widget/divecartesianaxis.h" +#include "profile-widget/divepixmapcache.h" #include "profile-widget/animationfunctions.h" #include "core/event.h" #include "core/format.h" -#include "core/libdivecomputer.h" #include "core/profile.h" #include "core/gettextfromc.h" -#include "core/metrics.h" #include "core/sample.h" #include "core/subsurface-string.h" -#include +#include "qt-models/diveplotdatamodel.h" #define DEPTH_NOT_FOUND (-2342) DiveEventItem::DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix, DivePlotDataModel *model, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, - int speed, double dpr, QGraphicsItem *parent) : DivePixmapItem(parent), + int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent) : DivePixmapItem(parent), vAxis(vAxis), hAxis(hAxis), dataModel(model), @@ -26,7 +24,7 @@ DiveEventItem::DiveEventItem(const struct dive *d, struct event *ev, struct gasm { setFlag(ItemIgnoresTransformations); - setupPixmap(lastgasmix, dpr); + setupPixmap(lastgasmix, pixmaps); setupToolTipString(lastgasmix); recalculatePos(0); @@ -49,62 +47,41 @@ struct event *DiveEventItem::getEventMutable() return ev; } -void DiveEventItem::setupPixmap(struct gasmix lastgasmix, double dpr) +void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps) { - extern int verbose; - const IconMetrics& metrics = defaultIconMetrics(); -#ifndef SUBSURFACE_MOBILE - int sz_bigger = metrics.sz_med + metrics.sz_small; // ex 40px -#else -#if defined(Q_OS_IOS) - // on iOS devices we need to adjust for Device Pixel Ratio - int sz_bigger = metrics.sz_med * metrics.dpr; -#else - // SUBSURFACE_MOBILE, seems a little big from the code, - // but looks fine on device - int sz_bigger = metrics.sz_big + metrics.sz_med; -#endif -#endif - sz_bigger = lrint(sz_bigger * dpr); - int sz_pix = sz_bigger/2; // ex 20px - if (verbose) - qDebug() << __FUNCTION__ << "DPR" << dpr << "metrics" << metrics.sz_med << metrics.sz_small << "sz_bigger" << sz_bigger; - -#define EVENT_PIXMAP(PIX) QPixmap(QString(PIX)).scaled(sz_pix, sz_pix, Qt::KeepAspectRatio, Qt::SmoothTransformation) -#define EVENT_PIXMAP_BIGGER(PIX) QPixmap(QString(PIX)).scaled(sz_bigger, sz_bigger, Qt::KeepAspectRatio, Qt::SmoothTransformation) if (empty_string(ev->name)) { - setPixmap(EVENT_PIXMAP(":status-warning-icon")); + setPixmap(pixmaps.warning); } else if (same_string_caseinsensitive(ev->name, "modechange")) { if (ev->value == 0) - setPixmap(EVENT_PIXMAP(":bailout-icon")); + setPixmap(pixmaps.bailout); else - setPixmap(EVENT_PIXMAP(":onCCRLoop-icon")); + setPixmap(pixmaps.onCCRLoop); } else if (ev->type == SAMPLE_EVENT_BOOKMARK) { - setPixmap(EVENT_PIXMAP(":dive-bookmark-icon")); + setPixmap(pixmaps.bookmark); } else if (event_is_gaschange(ev)) { struct gasmix mix = get_gasmix_from_event(dive, ev); struct icd_data icd_data; bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data); if (mix.he.permille) { if (icd) - setPixmap(EVENT_PIXMAP_BIGGER(":gaschange-trimix-ICD-icon")); + setPixmap(pixmaps.gaschangeTrimixICD); else - setPixmap(EVENT_PIXMAP_BIGGER(":gaschange-trimix-icon")); + setPixmap(pixmaps.gaschangeTrimix); } else if (gasmix_is_air(mix)) { if (icd) - setPixmap(EVENT_PIXMAP_BIGGER(":gaschange-air-ICD-icon")); + setPixmap(pixmaps.gaschangeAirICD); else - setPixmap(EVENT_PIXMAP_BIGGER(":gaschange-air-icon")); + setPixmap(pixmaps.gaschangeAir); } else if (mix.o2.permille == 1000) { if (icd) - setPixmap(EVENT_PIXMAP_BIGGER(":gaschange-oxygen-ICD-icon")); + setPixmap(pixmaps.gaschangeOxygenICD); else - setPixmap(EVENT_PIXMAP_BIGGER(":gaschange-oxygen-icon")); + setPixmap(pixmaps.gaschangeOxygen); } else { if (icd) - setPixmap(EVENT_PIXMAP_BIGGER(":gaschange-ean-ICD-icon")); + setPixmap(pixmaps.gaschangeEANICD); else - setPixmap(EVENT_PIXMAP_BIGGER(":gaschange-ean-icon")); + setPixmap(pixmaps.gaschangeEAN); } #ifdef SAMPLE_FLAGS_SEVERITY_SHIFT } else if ((((ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 1) || @@ -121,16 +98,14 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, double dpr) // 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 - QPixmap transparentPixmap(4, 20); - transparentPixmap.fill(QColor::fromRgbF(1.0, 1.0, 1.0, 0.01)); - setPixmap(transparentPixmap); + setPixmap(pixmaps.transparent); #ifdef SAMPLE_FLAGS_SEVERITY_SHIFT } else if (((ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 2) { - setPixmap(EVENT_PIXMAP(":status-info-icon")); + setPixmap(pixmaps.info); } else if (((ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 3) { - setPixmap(EVENT_PIXMAP(":status-warning-icon")); + setPixmap(pixmaps.warning); } else if (((ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 4) { - setPixmap(EVENT_PIXMAP(":status-violation-icon")); + setPixmap(pixmaps.violation); #endif } else if (same_string_caseinsensitive(ev->name, "violation") || // generic libdivecomputer same_string_caseinsensitive(ev->name, "Safety stop violation") || // the rest are from the Uemis downloader @@ -139,20 +114,18 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, double dpr) same_string_caseinsensitive(ev->name, "Dive time alert") || same_string_caseinsensitive(ev->name, "Low battery alert") || same_string_caseinsensitive(ev->name, "Speed alarm")) { - setPixmap(EVENT_PIXMAP(":status-violation-icon")); + setPixmap(pixmaps.violation); } else if (same_string_caseinsensitive(ev->name, "non stop time") || // generic libdivecomputer same_string_caseinsensitive(ev->name, "safety stop") || same_string_caseinsensitive(ev->name, "safety stop (voluntary)") || same_string_caseinsensitive(ev->name, "Tank change suggested") || // Uemis downloader same_string_caseinsensitive(ev->name, "Marker")) { - setPixmap(EVENT_PIXMAP(":status-info-icon")); + 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(EVENT_PIXMAP(":status-warning-icon")); + setPixmap(pixmaps.warning); } -#undef EVENT_PIXMAP -#undef EVENT_PIXMAP_BIGGER } void DiveEventItem::setupToolTipString(struct gasmix lastgasmix) diff --git a/profile-widget/diveeventitem.h b/profile-widget/diveeventitem.h index fefae9efc..73dbc1fd2 100644 --- a/profile-widget/diveeventitem.h +++ b/profile-widget/diveeventitem.h @@ -5,7 +5,8 @@ #include "divepixmapitem.h" class DiveCartesianAxis; -class DivePlotDataModel; +class DivePixmapCache; +class DivePixmaps; struct event; class DiveEventItem : public DivePixmapItem { @@ -13,7 +14,7 @@ class DiveEventItem : public DivePixmapItem { public: DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix, DivePlotDataModel *model, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, - int speed, double dpr, QGraphicsItem *parent = nullptr); + int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent = nullptr); ~DiveEventItem(); const struct event *getEvent() const; struct event *getEventMutable(); @@ -28,7 +29,7 @@ slots: private: void setupToolTipString(struct gasmix lastgasmix); - void setupPixmap(struct gasmix lastgasmix, double dpr); + void setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps); int depthAtTime(int time); DiveCartesianAxis *vAxis; DiveCartesianAxis *hAxis; diff --git a/profile-widget/divepixmapcache.cpp b/profile-widget/divepixmapcache.cpp new file mode 100644 index 000000000..e553081af --- /dev/null +++ b/profile-widget/divepixmapcache.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "divepixmapcache.h" +#include "core/metrics.h" + +#include + +DivePixmaps::~DivePixmaps() +{ +} + +static QPixmap createPixmap(const char *name, int size) +{ + return QPixmap(QString(name)).scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation); +} + +DivePixmaps::DivePixmaps(int dpr) : dpr(dpr) +{ + extern int verbose; + double dprf = dpr / 100.0; + const IconMetrics &metrics = defaultIconMetrics(); +#ifndef SUBSURFACE_MOBILE + int sz_bigger = metrics.sz_med + metrics.sz_small; // ex 40px +#else +#if defined(Q_OS_IOS) + // on iOS devices we need to adjust for Device Pixel Ratio + int sz_bigger = metrics.sz_med * metrics.dpr; +#else + // SUBSURFACE_MOBILE, seems a little big from the code, + // but looks fine on device + int sz_bigger = metrics.sz_big + metrics.sz_med; +#endif +#endif + sz_bigger = lrint(sz_bigger * dprf); + int sz_pix = sz_bigger / 2; // ex 20px + if (verbose) + qDebug("%s DPR: %f metrics: %d %d sz_bigger: %d", __FUNCTION__, dprf, metrics.sz_med, metrics.sz_small, sz_bigger); + + warning = createPixmap(":status-warning-icon", sz_pix); + info = createPixmap(":status-info-icon", sz_pix); + violation = createPixmap(":status-violation-icon", sz_pix); + bailout = createPixmap(":bailout-icon", sz_pix); + onCCRLoop = createPixmap(":onCCRLoop-icon", sz_pix); + bookmark = createPixmap(":dive-bookmark-icon", sz_pix); + gaschangeTrimixICD = createPixmap(":gaschange-trimix-ICD-icon", sz_bigger); + gaschangeTrimix = createPixmap(":gaschange-trimix-icon", sz_bigger); + gaschangeAirICD = createPixmap(":gaschange-air-ICD-icon", sz_bigger); + gaschangeAir = createPixmap(":gaschange-air-icon", sz_bigger); + gaschangeOxygenICD = createPixmap(":gaschange-oxygen-ICD-icon", sz_bigger); + gaschangeOxygen = createPixmap(":gaschange-oxygen-icon", sz_bigger); + gaschangeEANICD = createPixmap(":gaschange-ean-ICD-icon", sz_bigger); + gaschangeEAN = createPixmap(":gaschange-ean-icon", sz_bigger); + + // The transparen pixmap is a very obscure feature to enable tooltips without showing a pixmap. + // See code in diveeventitem.cpp. This should probably be replaced by a different mechanism. + QPixmap transparentPixmap(lrint(4 * dprf), lrint(20 * dprf)); + transparentPixmap.fill(QColor::fromRgbF(1.0, 1.0, 1.0, 0.01)); +} + +static std::vector> cache; + +// Return a std::shared_ptr<> for reference counting. +// Note that the idiomatic way would be to store std::weak_ptr<>s. +// That would mean that the pixmaps are destroyed when the last user uses +// them. We want to keep them around at least until the next caller! +// Therefore we also std::shared_ptr<>s. +std::shared_ptr getDivePixmaps(double dprIn) +{ + using ptr = std::shared_ptr; + ptr res; + int dpr = lrint(dprIn * 100.0); // Caching on a percent basis should be fine. + auto it = std::find_if(cache.begin(), cache.end(), [dpr](const ptr &p) { return p->dpr == dpr; }); + if (it == cache.end()) { + res = std::make_shared(dpr); + cache.push_back(res); + } else { + res = *it; + } + + // Remove unused items with C++'s wonderful erase/remove idiom. + // If the use_count is one, then the cache has the only reference, so remove the object. + cache.erase(std::remove_if(cache.begin(), cache.end(), + [](const ptr &p) { return p.use_count() <= 1; }), cache.end()); + + return res; +} diff --git a/profile-widget/divepixmapcache.h b/profile-widget/divepixmapcache.h new file mode 100644 index 000000000..74647e752 --- /dev/null +++ b/profile-widget/divepixmapcache.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef DIVEPIXMAPCACHE_H +#define DIVEPIXMAPCACHE_H + +#include + +#include +#include + +// Since (some) pixmaps are rendered from SVG, which may be very slow, +// cache them once per ProfileScene. Different ProfileScenes may have +// different pixmap sizes. Scenes are created rarely (once per print +// job or UI window), so it should be fine to render the pixmaps +// on construction. +struct DivePixmaps { + int dpr; + QPixmap warning; + QPixmap info; + QPixmap violation; + QPixmap bailout; + QPixmap onCCRLoop; + QPixmap bookmark; + QPixmap gaschangeTrimixICD; + QPixmap gaschangeTrimix; + QPixmap gaschangeAirICD; + QPixmap gaschangeAir; + QPixmap gaschangeOxygenICD; + QPixmap gaschangeOxygen; + QPixmap gaschangeEANICD; + QPixmap gaschangeEAN; + QPixmap transparent; + ~DivePixmaps(); + DivePixmaps(int dpr); +}; + +// Note: This is NOT thread safe. Must be called from UI thread! +extern std::shared_ptr getDivePixmaps(double dpr); + +#endif diff --git a/profile-widget/profilescene.cpp b/profile-widget/profilescene.cpp index d8fc9c9d1..a5e9ff2aa 100644 --- a/profile-widget/profilescene.cpp +++ b/profile-widget/profilescene.cpp @@ -3,6 +3,7 @@ #include "diveeventitem.h" #include "divecartesianaxis.h" #include "divepercentageitem.h" +#include "divepixmapcache.h" #include "diveprofileitem.h" #include "divetextitem.h" #include "tankitem.h" @@ -71,7 +72,8 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) : decoModelParameters(new DiveTextItem(dpr, 1.0, Qt::AlignHCenter | Qt::AlignTop, nullptr)), heartBeatItem(createItem(*heartBeatAxis, DivePlotDataModel::HEARTBEAT, 1, dpr)), percentageItem(new DivePercentageItem(*timeAxis, *percentageAxis, dpr)), - tankItem(new TankItem(*timeAxis, dpr)) + tankItem(new TankItem(*timeAxis, dpr)), + pixmaps(getDivePixmaps(dpr)) { init_plot_info(&plotInfo); @@ -499,7 +501,7 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM // BUT events are wanted. #endif DiveEventItem *item = new DiveEventItem(d, event, lastgasmix, dataModel, - timeAxis, profileYAxis, animSpeed, dpr); + timeAxis, profileYAxis, animSpeed, *pixmaps); item->setZValue(2); addItem(item); eventItems.push_back(item); diff --git a/profile-widget/profilescene.h b/profile-widget/profilescene.h index cb8bc8ce4..8d75ec809 100644 --- a/profile-widget/profilescene.h +++ b/profile-widget/profilescene.h @@ -9,6 +9,7 @@ #include #include +#include class DivePlannerPointsModel; class DivePlotDataModel; @@ -24,6 +25,7 @@ class DiveGasPressureItem; class DiveHeartrateItem; class DiveMeanDepthItem; class DivePercentageItem; +class DivePixmaps; class DiveProfileItem; class DiveReportedCeiling; class DiveTemperatureItem; @@ -98,6 +100,7 @@ private: DiveHeartrateItem *heartBeatItem; DivePercentageItem *percentageItem; TankItem *tankItem; + std::shared_ptr pixmaps; }; #endif