core: turn event-list of divecomputer into std::vector<>

This is a rather long commit, because it refactors lots of the event
code from pointer to value semantics: pointers to entries in an
std::vector<> are not stable, so better use indexes.

To step through the event-list at diven time stamps, add *_loop classes,
which encapsulate state that had to be manually handled before by
the caller. I'm not happy about the interface, but it tries to
mirror the one we had before.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2024-05-25 08:16:57 +02:00 committed by bstoeger
parent 8ddc960fa0
commit 27dbdd35c6
36 changed files with 644 additions and 821 deletions

View file

@ -16,14 +16,15 @@
static int depthAtTime(const plot_info &pi, duration_t time);
DiveEventItem::DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix,
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),
vAxis(vAxis),
hAxis(hAxis),
idx(idx),
ev(ev),
dive(d),
depth(depthAtTime(pi, ev->time))
depth(depthAtTime(pi, ev.time))
{
setFlag(ItemIgnoresTransformations);
@ -36,27 +37,17 @@ DiveEventItem::~DiveEventItem()
{
}
const struct event *DiveEventItem::getEvent() const
{
return ev;
}
struct event *DiveEventItem::getEventMutable()
{
return ev;
}
void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps)
{
event_severity severity = get_event_severity(ev);
if (ev->name.empty()) {
if (ev.name.empty()) {
setPixmap(pixmaps.warning);
} else if (same_string_caseinsensitive(ev->name.c_str(), "modechange")) {
if (ev->value == 0)
} 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) {
} else if (ev.type == SAMPLE_EVENT_BOOKMARK) {
setPixmap(pixmaps.bookmark);
setOffset(QPointF(0.0, -pixmap().height()));
} else if (event_is_gaschange(ev)) {
@ -84,10 +75,10 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix
else
setPixmap(pixmaps.gaschangeEAN);
}
} else if ((((ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 1) ||
} else 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)) {
same_string_caseinsensitive(ev.name.c_str(), "heading") ||
(same_string_caseinsensitive(ev.name.c_str(), "SP change") && ev.time.seconds == 0)) {
// 2 cases:
// a) some dive computers have heading in every sample
// b) at t=0 we might have an "SP change" to indicate dive type
@ -102,19 +93,19 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix
setPixmap(pixmaps.warning);
} else if (severity == EVENT_SEVERITY_ALARM) {
setPixmap(pixmaps.violation);
} else 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")) {
} else 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")) {
} 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;
@ -126,9 +117,9 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix
void DiveEventItem::setupToolTipString(struct gasmix lastgasmix)
{
// we display the event on screen - so translate
QString name = gettextFromC::tr(ev->name.c_str());
int value = ev->value;
int type = ev->type;
QString name = gettextFromC::tr(ev.name.c_str());
int value = ev.value;
int type = ev.type;
if (event_is_gaschange(ev)) {
struct icd_data icd_data;
@ -137,8 +128,8 @@ void DiveEventItem::setupToolTipString(struct gasmix lastgasmix)
name += gasname(mix);
/* Do we have an explicit cylinder index? Show it. */
if (ev->gas.index >= 0)
name += tr(" (cyl. %1)").arg(ev->gas.index + 1);
if (ev.gas.index >= 0)
name += tr(" (cyl. %1)").arg(ev.gas.index + 1);
bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data);
if (icd_data.dHe < 0) {
name += qasprintf_loc("\n%s %s:%+.3g%% %s:%+.3g%%%s%+.3g%%",
@ -147,25 +138,25 @@ void DiveEventItem::setupToolTipString(struct gasmix lastgasmix)
qPrintable(tr("ΔN₂")), icd_data.dN2 / 10.0,
icd ? ">" : "<", lrint(-icd_data.dHe / 5.0) / 10.0);
}
} else if (ev->name == "modechange") {
name += QString(": %1").arg(gettextFromC::tr(divemode_text_ui[ev->value]));
} else if (ev.name == "modechange") {
name += QString(": %1").arg(gettextFromC::tr(divemode_text_ui[ev.value]));
} else if (value) {
if (type == SAMPLE_EVENT_PO2 && ev->name == "SP change") {
if (type == SAMPLE_EVENT_PO2 && ev.name == "SP change") {
name += QString(": %1bar").arg((double)value / 1000, 0, 'f', 1);
} else if (type == SAMPLE_EVENT_CEILING && ev->name == "planned waypoint above ceiling") {
} else if (type == SAMPLE_EVENT_CEILING && ev.name == "planned waypoint above ceiling") {
const char *depth_unit;
double depth_value = get_depth_units(value*1000, NULL, &depth_unit);
name += QString(": %1%2").arg((int) round(depth_value)).arg(depth_unit);
} else {
name += QString(": %1").arg(value);
}
} else if (type == SAMPLE_EVENT_PO2 && ev->name == "SP change") {
} else if (type == SAMPLE_EVENT_PO2 && ev.name == "SP change") {
// this is a bad idea - we are abusing an existing event type that is supposed to
// warn of high or low pO₂ and are turning it into a setpoint change event
name += ":\n" + tr("Manual switch to OC");
} else {
name += ev->flags & SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") :
ev->flags & SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : "";
name += ev.flags & SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") :
ev.flags & SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : "";
}
setToolTip(QString("<img height=\"16\" src=\":status-warning-icon\">&nbsp; ") + name);
}
@ -188,31 +179,31 @@ static int depthAtTime(const plot_info &pi, duration_t time)
}
bool DiveEventItem::isInteresting(const struct dive *d, const struct divecomputer *dc,
const struct event *ev, const plot_info &pi,
const struct event &ev, const plot_info &pi,
int firstSecond, int lastSecond)
{
/*
* Ignore items outside of plot range
*/
if (ev->time.seconds < firstSecond || ev->time.seconds >= lastSecond)
if (ev.time.seconds < firstSecond || ev.time.seconds >= lastSecond)
return false;
/*
* Some gas change events are special. Some dive computers just tell us the initial gas this way.
* Don't bother showing those
*/
if (ev->name == "gaschange" &&
(ev->time.seconds == 0 ||
(!dc->samples.empty() && ev->time.seconds == dc->samples[0].time.seconds) ||
depthAtTime(pi, ev->time) < SURFACE_THRESHOLD))
if (ev.name == "gaschange" &&
(ev.time.seconds == 0 ||
(!dc->samples.empty() && ev.time.seconds == dc->samples[0].time.seconds) ||
depthAtTime(pi, ev.time) < SURFACE_THRESHOLD))
return false;
/*
* Some divecomputers give "surface" events that just aren't interesting.
* Like at the beginning or very end of a dive. Well, duh.
*/
if (ev->name == "surface") {
int time = ev->time.seconds;
if (ev.name == "surface") {
int time = ev.time.seconds;
if (time <= 30 || time + 30 >= (int)dc->duration.seconds)
return false;
}
@ -221,15 +212,12 @@ bool DiveEventItem::isInteresting(const struct dive *d, const struct divecompute
void DiveEventItem::recalculatePos()
{
if (!ev)
return;
if (depth == DEPTH_NOT_FOUND) {
hide();
return;
}
setVisible(!ev->hidden && !is_event_type_hidden(ev));
double x = hAxis->posAtValue(ev->time.seconds);
setVisible(!ev.hidden && !is_event_type_hidden(&ev));
double x = hAxis->posAtValue(ev.time.seconds);
double y = vAxis->posAtValue(depth);
setPos(x, y);
}

View file

@ -3,6 +3,7 @@
#define DIVEEVENTITEM_H
#include "divepixmapitem.h"
#include "core/event.h"
class DiveCartesianAxis;
class DivePixmaps;
@ -12,17 +13,15 @@ struct plot_info;
class DiveEventItem : public DivePixmapItem {
Q_OBJECT
public:
DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix,
DiveEventItem(const struct dive *d, int idx, const struct event &ev, struct gasmix lastgasmix,
const struct plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis,
int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent = nullptr);
~DiveEventItem();
const struct event *getEvent() const;
struct event *getEventMutable();
void eventVisibilityChanged(const QString &eventName, bool visible);
void setVerticalAxis(DiveCartesianAxis *axis, int speed);
void setHorizontalAxis(DiveCartesianAxis *axis);
static bool isInteresting(const struct dive *d, const struct divecomputer *dc,
const struct event *ev, const struct plot_info &pi,
const struct event &ev, const struct plot_info &pi,
int firstSecond, int lastSecond);
private:
@ -31,7 +30,9 @@ private:
void recalculatePos();
DiveCartesianAxis *vAxis;
DiveCartesianAxis *hAxis;
struct event *ev;
public:
int idx;
struct event ev;
const struct dive *dive;
int depth;
};

View file

@ -2,6 +2,7 @@
#include "divepercentageitem.h"
#include "divecartesianaxis.h"
#include "core/dive.h"
#include "core/event.h"
#include "core/profile.h"
#include <array>
@ -105,7 +106,7 @@ void DivePercentageItem::replot(const dive *d, const struct divecomputer *dc, co
int x = 0;
QRgb *scanline = (QRgb *)img.scanLine(line);
QRgb color = 0;
const struct event *ev = NULL;
gasmix_loop loop(*d, *dc);
for (int i = 0; i < pi.nr; i++) {
const plot_data &item = pi.entry[i];
int sec = item.sec;
@ -114,7 +115,7 @@ void DivePercentageItem::replot(const dive *d, const struct divecomputer *dc, co
continue;
double value = item.percentages[tissue];
struct gasmix gasmix = get_gasmix(d, dc, sec, &ev, gasmix_air);
struct gasmix gasmix = loop.next(sec);
int inert = get_n2(gasmix) + get_he(gasmix);
color = colorScale(value, inert);
if (nextX >= width)

View file

@ -14,6 +14,7 @@
#include "core/pref.h"
#include "core/profile.h"
#include "core/qthelper.h" // for decoMode()
#include "core/range.h"
#include "core/subsurface-float.h"
#include "core/subsurface-string.h"
#include "core/settings/qPrefDisplay.h"
@ -550,23 +551,20 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
// while all other items are up there on the constructor.
qDeleteAll(eventItems);
eventItems.clear();
struct event *event = currentdc->events;
struct gasmix lastgasmix = get_gasmix_at_time(d, currentdc, duration_t{1});
struct gasmix lastgasmix = get_gasmix_at_time(*d, *currentdc, duration_t{1});
while (event) {
for (auto [idx, event]: enumerated_range(currentdc->events)) {
// if print mode is selected only draw headings, SP change, gas events or bookmark event
if (printMode) {
if (event->name.empty() ||
!(event->name == "heading" ||
(event->name == "SP change" && event->time.seconds == 0) ||
if (event.name.empty() ||
!(event.name == "heading" ||
(event.name == "SP change" && event.time.seconds == 0) ||
event_is_gaschange(event) ||
event->type == SAMPLE_EVENT_BOOKMARK)) {
event = event->next;
event.type == SAMPLE_EVENT_BOOKMARK))
continue;
}
}
if (DiveEventItem::isInteresting(d, currentdc, event, plotInfo, firstSecond, lastSecond)) {
auto item = new DiveEventItem(d, event, lastgasmix, plotInfo,
auto item = new DiveEventItem(d, idx, event, lastgasmix, plotInfo,
timeAxis, profileYAxis, animSpeed, *pixmaps);
item->setZValue(2);
addItem(item);
@ -574,7 +572,6 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
}
if (event_is_gaschange(event))
lastgasmix = get_gasmix_from_event(d, event);
event = event->next;
}
QString dcText = QString::fromStdString(get_dc_nickname(currentdc));

View file

@ -559,8 +559,8 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
DiveEventItem *item = dynamic_cast<DiveEventItem *>(sceneItem);
// Add or edit Gas Change
if (d && item && event_is_gaschange(item->getEvent())) {
int eventTime = item->getEvent()->time.seconds;
if (d && item && event_is_gaschange(item->ev)) {
int eventTime = item->ev.time.seconds;
QMenu *gasChange = m.addMenu(tr("Edit Gas Change"));
for (int i = 0; i < d->cylinders.nr; i++) {
const cylinder_t *cylinder = get_cylinder(d, i);
@ -579,10 +579,9 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
m.addAction(tr("Add setpoint change"), [this, seconds]() { ProfileWidget2::addSetpointChange(seconds); });
m.addAction(tr("Add bookmark"), [this, seconds]() { addBookmark(seconds); });
m.addAction(tr("Split dive into two"), [this, seconds]() { splitDive(seconds); });
const struct event *ev = NULL;
enum divemode_t divemode = UNDEF_COMP_TYPE;
get_current_divemode(get_dive_dc_const(d, dc), seconds, &ev, &divemode);
divemode_loop loop(*get_dive_dc_const(d, dc));
divemode_t divemode = loop.next(seconds);
QMenu *changeMode = m.addMenu(tr("Change divemode"));
if (divemode != OC)
changeMode->addAction(gettextFromC::tr(divemode_text_ui[OC]),
@ -595,23 +594,22 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
[this, seconds](){ addDivemodeSwitch(seconds, PSCR); });
if (DiveEventItem *item = dynamic_cast<DiveEventItem *>(sceneItem)) {
const struct event *dcEvent = item->getEvent();
m.addAction(tr("Remove event"), [this,item] { removeEvent(item); });
m.addAction(tr("Hide event"), [this, item] { hideEvent(item); });
m.addAction(tr("Hide events of type '%1'").arg(event_type_name(dcEvent)),
m.addAction(tr("Hide events of type '%1'").arg(event_type_name(item->ev)),
[this, item] { hideEventType(item); });
if (dcEvent->type == SAMPLE_EVENT_BOOKMARK)
if (item->ev.type == SAMPLE_EVENT_BOOKMARK)
m.addAction(tr("Edit name"), [this, item] { editName(item); });
#if 0 // TODO::: FINISH OR DISABLE
QPointF scenePos = mapToScene(event->pos());
int idx = getEntryFromPos(scenePos);
// this shows how to figure out if we should ask the user if they want adjust interpolated pressures
// at either side of a gas change
if (dcEvent->type == SAMPLE_EVENT_GASCHANGE || dcEvent->type == SAMPLE_EVENT_GASCHANGE2) {
if (item->ev->type == SAMPLE_EVENT_GASCHANGE || item->ev->type == SAMPLE_EVENT_GASCHANGE2) {
int gasChangeIdx = idx;
while (gasChangeIdx > 0) {
--gasChangeIdx;
if (plotInfo.entry[gasChangeIdx].sec <= dcEvent->time.seconds)
if (plotInfo.entry[gasChangeIdx].sec <= item->ev->time.seconds)
break;
}
const struct plot_data &gasChangeEntry = plotInfo.entry[newGasIdx];
@ -650,8 +648,9 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
}
m2->addAction(tr("All event types"), this, &ProfileWidget2::unhideEventTypes);
}
if (std::any_of(profileScene->eventItems.begin(), profileScene->eventItems.end(),
[] (const DiveEventItem *item) { return item->getEvent()->hidden; }))
const struct divecomputer *currentdc = get_dive_dc_const(d, dc);
if (currentdc && std::any_of(currentdc->events.begin(), currentdc->events.end(),
[] (auto &ev) { return ev.hidden; }))
m.addAction(tr("Unhide individually hidden events of this dive"), this, &ProfileWidget2::unhideEvents);
m.exec(event->globalPos());
}
@ -690,16 +689,18 @@ void ProfileWidget2::renameCurrentDC()
void ProfileWidget2::hideEvent(DiveEventItem *item)
{
item->getEventMutable()->hidden = true;
struct divecomputer *currentdc = get_dive_dc(mutable_dive(), dc);
int idx = item->idx;
if (!currentdc || idx < 0 || static_cast<size_t>(idx) >= currentdc->events.size())
return;
currentdc->events[idx].hidden = true;
item->hide();
}
void ProfileWidget2::hideEventType(DiveEventItem *item)
{
const struct event *event = item->getEvent();
if (!event->name.empty()) {
hide_event_type(event);
if (!item->ev.name.empty()) {
hide_event_type(&item->ev);
replot();
}
@ -707,10 +708,13 @@ void ProfileWidget2::hideEventType(DiveEventItem *item)
void ProfileWidget2::unhideEvents()
{
for (DiveEventItem *item: profileScene->eventItems) {
item->getEventMutable()->hidden = false;
struct divecomputer *currentdc = get_dive_dc(mutable_dive(), dc);
if (!currentdc)
return;
for (auto &ev: currentdc->events)
ev.hidden = false;
for (DiveEventItem *item: profileScene->eventItems)
item->show();
}
}
void ProfileWidget2::unhideEventTypes()
@ -722,15 +726,12 @@ void ProfileWidget2::unhideEventTypes()
void ProfileWidget2::removeEvent(DiveEventItem *item)
{
struct event *event = item->getEventMutable();
if (!event || !d)
return;
const struct event &ev = item->ev;
if (QMessageBox::question(this, TITLE_OR_TEXT(
tr("Remove the selected event?"),
tr("%1 @ %2:%3").arg(QString::fromStdString(event->name)).arg(event->time.seconds / 60).arg(event->time.seconds % 60, 2, 10, QChar('0'))),
tr("%1 @ %2:%3").arg(QString::fromStdString(ev.name)).arg(ev.time.seconds / 60).arg(ev.time.seconds % 60, 2, 10, QChar('0'))),
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok)
Command::removeEvent(mutable_dive(), dc, event);
Command::removeEvent(mutable_dive(), dc, item->idx);
}
void ProfileWidget2::addBookmark(int seconds)
@ -781,13 +782,12 @@ void ProfileWidget2::changeGas(int index, int newCylinderId)
void ProfileWidget2::editName(DiveEventItem *item)
{
struct event *event = item->getEventMutable();
if (!event || !d)
if (!d)
return;
bool ok;
QString newName = QInputDialog::getText(this, tr("Edit name of bookmark"),
tr("Custom name:"), QLineEdit::Normal,
event->name.c_str(), &ok);
item->ev.name.c_str(), &ok);
if (ok && !newName.isEmpty()) {
if (newName.length() > 22) { //longer names will display as garbage.
QMessageBox lengthWarning;
@ -795,7 +795,7 @@ void ProfileWidget2::editName(DiveEventItem *item)
lengthWarning.exec();
return;
}
Command::renameEvent(mutable_dive(), dc, event, qPrintable(newName));
Command::renameEvent(mutable_dive(), dc, item->idx, newName.toStdString());
}
}

View file

@ -80,17 +80,19 @@ void TankItem::setData(const struct dive *d, const struct divecomputer *dc, int
return;
// start with the first gasmix and at the start of the plotted range
const struct event *ev = NULL;
struct gasmix gasmix = gasmix_air;
gasmix = get_gasmix(d, dc, plotStartTime, &ev, gasmix);
event_loop loop("gaschange");
struct gasmix gasmix = gasmix_invalid;
const struct event *ev;
while ((ev = loop.next(*dc)) != nullptr && ev->time.seconds <= plotStartTime)
gasmix = get_gasmix_from_event(d, *ev);
// work through all the gas changes and add the rectangle for each gas while it was used
int startTime = plotStartTime;
while (ev && (int)ev->time.seconds < plotEndTime) {
createBar(startTime, ev->time.seconds, gasmix);
startTime = ev->time.seconds;
gasmix = get_gasmix_from_event(d, ev);
ev = get_next_event(ev->next, "gaschange");
gasmix = get_gasmix_from_event(d, *ev);
ev = loop.next(*dc);
}
createBar(startTime, plotEndTime, gasmix);
}