summaryrefslogtreecommitdiffstats
path: root/commands
diff options
context:
space:
mode:
authorGravatar Dirk Hohndel <dirk@hohndel.org>2020-04-11 11:03:05 -0700
committerGravatar GitHub <noreply@github.com>2020-04-11 11:03:05 -0700
commit6d187b5f4a3b51043fa3b53b6c73a7e0ec7f0b53 (patch)
tree93b26b7381e565ecc0dc6d93e2d35b61a1cdaca9 /commands
parent2990010ccde56acec980e76ef5671137e87af488 (diff)
parent7dc04b4437c7aa1788f7d85a513a246ce502dea4 (diff)
downloadsubsurface-6d187b5f4a3b51043fa3b53b6c73a7e0ec7f0b53.tar.gz
Merge pull request #2643 from bstoeger/cylinder4
First steps of cylinder-editing undo
Diffstat (limited to 'commands')
-rw-r--r--commands/CMakeLists.txt2
-rw-r--r--commands/command.cpp48
-rw-r--r--commands/command.h17
-rw-r--r--commands/command_base.h9
-rw-r--r--commands/command_edit.cpp238
-rw-r--r--commands/command_edit.h45
-rw-r--r--commands/command_event.cpp210
-rw-r--r--commands/command_event.h104
8 files changed, 669 insertions, 4 deletions
diff --git a/commands/CMakeLists.txt b/commands/CMakeLists.txt
index 4b0c37dc6..ceb2740f0 100644
--- a/commands/CMakeLists.txt
+++ b/commands/CMakeLists.txt
@@ -14,6 +14,8 @@ set(SUBSURFACE_GENERIC_COMMANDS_SRCS
command_edit.h
command_edit_trip.cpp
command_edit_trip.h
+ command_event.cpp
+ command_event.h
)
add_library(subsurface_commands STATIC ${SUBSURFACE_GENERIC_COMMANDS_SRCS})
target_link_libraries(subsurface_commands ${QT_LIBRARIES})
diff --git a/commands/command.cpp b/commands/command.cpp
index 1fb968778..33c866e72 100644
--- a/commands/command.cpp
+++ b/commands/command.cpp
@@ -5,6 +5,7 @@
#include "command_divesite.h"
#include "command_edit.h"
#include "command_edit_trip.h"
+#include "command_event.h"
namespace Command {
@@ -293,6 +294,21 @@ int editWeight(int index, weightsystem_t ws, bool currentDiveOnly)
return execute_edit(new EditWeight(index, ws, currentDiveOnly));
}
+int addCylinder(bool currentDiveOnly)
+{
+ return execute_edit(new AddCylinder(currentDiveOnly));
+}
+
+int removeCylinder(int index, bool currentDiveOnly)
+{
+ return execute_edit(new RemoveCylinder(index, currentDiveOnly));
+}
+
+int editCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentDiveOnly)
+{
+ return execute_edit(new EditCylinder(index, cyl, type, currentDiveOnly));
+}
+
// Trip editing related commands
void editTripLocation(dive_trip *trip, const QString &s)
{
@@ -311,4 +327,36 @@ void editDive(dive *oldDive, dive *newDive, dive_site *createDs, dive_site *chan
}
#endif // SUBSURFACE_MOBILE
+// Event commands
+
+void addEventBookmark(struct dive *d, int dcNr, int seconds)
+{
+ execute(new AddEventBookmark(d, dcNr, seconds));
+}
+
+void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode)
+{
+ execute(new AddEventDivemodeSwitch(d, dcNr, seconds, divemode));
+}
+
+void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2)
+{
+ execute(new AddEventSetpointChange(d, dcNr, seconds, pO2));
+}
+
+void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name)
+{
+ execute(new RenameEvent(d, dcNr, ev, name));
+}
+
+void removeEvent(struct dive *d, int dcNr, struct event *ev)
+{
+ execute(new RemoveEvent(d, dcNr, ev));
+}
+
+void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank)
+{
+ execute(new AddGasSwitch(d, dcNr, seconds, tank));
+}
+
} // namespace Command
diff --git a/commands/command.h b/commands/command.h
index e19d093cb..fc1ddf582 100644
--- a/commands/command.h
+++ b/commands/command.h
@@ -90,6 +90,14 @@ void editProfile(dive *d); // dive computer(s) and cylinder(s) will be reset!
int addWeight(bool currentDiveOnly);
int removeWeight(int index, bool currentDiveOnly);
int editWeight(int index, weightsystem_t ws, bool currentDiveOnly);
+int addCylinder(bool currentDiveOnly);
+int removeCylinder(int index, bool currentDiveOnly);
+enum class EditCylinderType {
+ TYPE,
+ PRESSURE,
+ GASMIX
+};
+int editCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentDiveOnly);
#ifdef SUBSURFACE_MOBILE
// Edits a dive and creates a divesite (if createDs != NULL) or edits a divesite (if changeDs != NULL).
// Takes ownership of newDive and createDs!
@@ -101,6 +109,15 @@ void editDive(dive *oldDive, dive *newDive, dive_site *createDs, dive_site *chan
void editTripLocation(dive_trip *trip, const QString &s);
void editTripNotes(dive_trip *trip, const QString &s);
+// 6) Event commands
+
+void addEventBookmark(struct dive *d, int dcNr, int seconds);
+void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode);
+void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2);
+void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name);
+void removeEvent(struct dive *d, int dcNr, struct event *ev);
+void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank);
+
} // namespace Command
#endif // COMMAND_H
diff --git a/commands/command_base.h b/commands/command_base.h
index ffc6c4e91..be725d6a5 100644
--- a/commands/command_base.h
+++ b/commands/command_base.h
@@ -141,7 +141,7 @@
// We put everything in a namespace, so that we can shorten names without polluting the global namespace
namespace Command {
-// Classes used to automatically call free_dive()/free_trip for owning pointers that go out of scope.
+// Classes used to automatically call the appropriate free_*() function for owning pointers that go out of scope.
struct DiveDeleter {
void operator()(dive *d) { free_dive(d); }
};
@@ -151,11 +151,15 @@ struct TripDeleter {
struct DiveSiteDeleter {
void operator()(dive_site *ds) { free_dive_site(ds); }
};
+struct EventDeleter {
+ void operator()(event *ev) { free(ev); }
+};
-// Owning pointers to dive and dive_trip objects.
+// Owning pointers to dive, dive_trip, dive_site and event objects.
typedef std::unique_ptr<dive, DiveDeleter> OwningDivePtr;
typedef std::unique_ptr<dive_trip, TripDeleter> OwningTripPtr;
typedef std::unique_ptr<dive_site, DiveSiteDeleter> OwningDiveSitePtr;
+typedef std::unique_ptr<event, EventDeleter> OwningEventPtr;
// This is the base class of all commands.
// It defines the Qt-translation functions
@@ -182,4 +186,3 @@ QString getListOfDives(QVector<struct dive *> dives);
} // namespace Command
#endif // COMMAND_BASE_H
-
diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp
index a5eb46bc1..0e1159f6d 100644
--- a/commands/command_edit.cpp
+++ b/commands/command_edit.cpp
@@ -8,6 +8,7 @@
#include "core/subsurface-string.h"
#include "core/tag.h"
#include "qt-models/weightsysteminfomodel.h"
+#include "qt-models/tankinfomodel.h"
#ifdef SUBSURFACE_MOBILE
#include "qt-models/divelocationmodel.h"
#endif
@@ -946,6 +947,7 @@ bool EditWeightBase::workToBeDone()
return !dives.empty();
}
+// ***** Remove Weight *****
RemoveWeight::RemoveWeight(int index, bool currentDiveOnly) :
EditWeightBase(index, currentDiveOnly)
{
@@ -972,6 +974,7 @@ void RemoveWeight::redo()
}
}
+// ***** Edit Weight *****
EditWeight::EditWeight(int index, weightsystem_t wsIn, bool currentDiveOnly) :
EditWeightBase(index, currentDiveOnly),
new_ws(empty_weightsystem)
@@ -1028,6 +1031,241 @@ void EditWeight::undo()
redo();
}
+// ***** Add Cylinder *****
+AddCylinder::AddCylinder(bool currentDiveOnly) :
+ EditDivesBase(currentDiveOnly),
+ cyl(empty_cylinder)
+{
+ if (dives.empty())
+ return;
+ else if (dives.size() == 1)
+ setText(tr("Add cylinder"));
+ else
+ setText(tr("Add cylinder (%n dive(s))", "", dives.size()));
+ cyl = create_new_cylinder(dives[0]);
+}
+
+AddCylinder::~AddCylinder()
+{
+ free_cylinder(cyl);
+}
+
+bool AddCylinder::workToBeDone()
+{
+ return true;
+}
+
+void AddCylinder::undo()
+{
+ for (dive *d: dives) {
+ if (d->cylinders.nr <= 0)
+ continue;
+ remove_cylinder(d, d->cylinders.nr - 1);
+ emit diveListNotifier.cylinderRemoved(d, d->cylinders.nr);
+ invalidate_dive_cache(d); // Ensure that dive is written in git_save()
+ }
+}
+
+void AddCylinder::redo()
+{
+ for (dive *d: dives) {
+ add_cloned_cylinder(&d->cylinders, cyl);
+ emit diveListNotifier.cylinderAdded(d, d->cylinders.nr - 1);
+ invalidate_dive_cache(d); // Ensure that dive is written in git_save()
+ }
+}
+
+static bool same_cylinder_type(const cylinder_t &cyl1, const cylinder_t &cyl2)
+{
+ return same_string(cyl1.type.description, cyl2.type.description) &&
+ cyl1.cylinder_use == cyl2.cylinder_use;
+}
+
+static bool same_cylinder_size(const cylinder_t &cyl1, const cylinder_t &cyl2)
+{
+ return cyl1.type.size.mliter == cyl2.type.size.mliter &&
+ cyl1.type.workingpressure.mbar == cyl2.type.workingpressure.mbar;
+}
+
+static bool same_cylinder_pressure(const cylinder_t &cyl1, const cylinder_t &cyl2)
+{
+ return cyl1.start.mbar == cyl2.start.mbar &&
+ cyl1.end.mbar == cyl2.end.mbar;
+}
+
+// Flags for comparing cylinders
+static const constexpr int SAME_TYPE = 1 << 0;
+static const constexpr int SAME_SIZE = 1 << 1;
+static const constexpr int SAME_PRESS = 1 << 2;
+static const constexpr int SAME_GAS = 1 << 3;
+
+static bool same_cylinder_with_flags(const cylinder_t &cyl1, const cylinder_t &cyl2, int sameCylinderFlags)
+{
+ return (((sameCylinderFlags & SAME_TYPE) == 0 || same_cylinder_type(cyl1, cyl2)) &&
+ ((sameCylinderFlags & SAME_SIZE) == 0 || same_cylinder_size(cyl1, cyl2)) &&
+ ((sameCylinderFlags & SAME_PRESS) == 0 || same_cylinder_pressure(cyl1, cyl2)) &&
+ ((sameCylinderFlags & SAME_GAS) == 0 || same_gasmix(cyl1.gasmix, cyl2.gasmix)));
+}
+
+static int find_cylinder_index(const struct dive *d, const cylinder_t &cyl, int sameCylinderFlags)
+{
+ for (int idx = 0; idx < d->cylinders.nr; ++idx) {
+ if (same_cylinder_with_flags(cyl, d->cylinders.cylinders[idx], sameCylinderFlags))
+ return idx;
+ }
+ return -1;
+}
+
+EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly, int sameCylinderFlags) :
+ EditDivesBase(currentDiveOnly)
+{
+ // Get the old cylinder, bail if index is invalid
+ if (!current || index < 0 || index >= current->cylinders.nr) {
+ dives.clear();
+ return;
+ }
+ const cylinder_t &orig = current->cylinders.cylinders[index];
+
+ std::vector<dive *> divesNew;
+ divesNew.reserve(dives.size());
+ indexes.reserve(dives.size());
+ cyl.reserve(dives.size());
+
+ for (dive *d: dives) {
+ int idx = d == current ? index : find_cylinder_index(d, orig, sameCylinderFlags);
+ if (idx < 0 || (nonProtectedOnly && is_cylinder_prot(d, idx)))
+ continue;
+ divesNew.push_back(d);
+ indexes.push_back(idx);
+ cyl.push_back(clone_cylinder(d->cylinders.cylinders[idx]));
+ }
+ dives = std::move(divesNew);
+}
+
+EditCylinderBase::~EditCylinderBase()
+{
+ for (cylinder_t c: cyl)
+ free_cylinder(c);
+}
+
+bool EditCylinderBase::workToBeDone()
+{
+ return !dives.empty();
+}
+
+// ***** Remove Cylinder *****
+RemoveCylinder::RemoveCylinder(int index, bool currentDiveOnly) :
+ EditCylinderBase(index, currentDiveOnly, true, SAME_TYPE | SAME_PRESS | SAME_GAS)
+{
+ if (dives.size() == 1)
+ setText(tr("Remove cylinder"));
+ else
+ setText(tr("Remove cylinder (%n dive(s))", "", dives.size()));
+}
+
+void RemoveCylinder::undo()
+{
+ for (size_t i = 0; i < dives.size(); ++i) {
+ std::vector<int> mapping = get_cylinder_map_for_add(dives[i]->cylinders.nr, indexes[i]);
+ add_to_cylinder_table(&dives[i]->cylinders, indexes[i], clone_cylinder(cyl[i]));
+ emit diveListNotifier.cylinderAdded(dives[i], indexes[i]);
+ invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save()
+ }
+}
+
+void RemoveCylinder::redo()
+{
+ for (size_t i = 0; i < dives.size(); ++i) {
+ std::vector<int> mapping = get_cylinder_map_for_remove(dives[i]->cylinders.nr, indexes[i]);
+ remove_cylinder(dives[i], indexes[i]);
+ cylinder_renumber(dives[i], &mapping[0]);
+ emit diveListNotifier.cylinderRemoved(dives[i], indexes[i]);
+ invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save()
+ }
+}
+
+static int editCylinderTypeToFlags(EditCylinderType type)
+{
+ switch (type) {
+ default:
+ case EditCylinderType::TYPE:
+ return SAME_TYPE | SAME_SIZE;
+ case EditCylinderType::PRESSURE:
+ return SAME_PRESS;
+ case EditCylinderType::GASMIX:
+ return SAME_GAS;
+ }
+}
+
+// ***** Edit Cylinder *****
+EditCylinder::EditCylinder(int index, cylinder_t cylIn, EditCylinderType typeIn, bool currentDiveOnly) :
+ EditCylinderBase(index, currentDiveOnly, false, editCylinderTypeToFlags(typeIn)),
+ type(typeIn)
+{
+ if (dives.empty())
+ return;
+
+ if (dives.size() == 1)
+ setText(tr("Edit cylinder"));
+ else
+ setText(tr("Edit cylinder (%n dive(s))", "", dives.size()));
+
+ // Try to untranslate the cylinder type
+ QString description = cylIn.type.description;
+ for (int i = 0; i < MAX_TANK_INFO && tank_info[i].name; ++i) {
+ if (gettextFromC::tr(tank_info[i].name) == description) {
+ description = tank_info[i].name;
+ break;
+ }
+ }
+
+ // Update the tank info model
+ TankInfoModel *tim = TankInfoModel::instance();
+ QModelIndexList matches = tim->match(tim->index(0, 0), Qt::DisplayRole, gettextFromC::tr(cylIn.type.description));
+ if (!matches.isEmpty()) {
+ if (cylIn.type.size.mliter != cyl[0].type.size.mliter)
+ tim->setData(tim->index(matches.first().row(), TankInfoModel::ML), cylIn.type.size.mliter);
+ if (cylIn.type.workingpressure.mbar != cyl[0].type.workingpressure.mbar)
+ tim->setData(tim->index(matches.first().row(), TankInfoModel::BAR), cylIn.type.workingpressure.mbar / 1000.0);
+ }
+
+ // The base class copied the cylinders for us, let's edit them
+ for (int i = 0; i < (int)indexes.size(); ++i) {
+ switch (type) {
+ case EditCylinderType::TYPE:
+ free((void *)cyl[i].type.description);
+ cyl[i].type = cylIn.type;
+ cyl[i].type.description = copy_qstring(description);
+ cyl[i].cylinder_use = cylIn.cylinder_use;
+ break;
+ case EditCylinderType::PRESSURE:
+ cyl[i].start.mbar = cylIn.start.mbar;
+ cyl[i].end.mbar = cylIn.end.mbar;
+ break;
+ case EditCylinderType::GASMIX:
+ cyl[i].gasmix = cylIn.gasmix;
+ cyl[i].bestmix_o2 = cylIn.bestmix_o2;
+ cyl[i].bestmix_he = cylIn.bestmix_he;
+ break;
+ }
+ }
+}
+
+void EditCylinder::redo()
+{
+ for (size_t i = 0; i < dives.size(); ++i) {
+ std::swap(dives[i]->cylinders.cylinders[indexes[i]], cyl[i]);
+ emit diveListNotifier.cylinderEdited(dives[i], indexes[i]);
+ invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save()
+ }
+}
+
+// Undo and redo do the same as just the stored value is exchanged
+void EditCylinder::undo()
+{
+ redo();
+}
+
#ifdef SUBSURFACE_MOBILE
EditDive::EditDive(dive *oldDiveIn, dive *newDiveIn, dive_site *createDs, dive_site *editDs, location_t dsLocationIn)
diff --git a/commands/command_edit.h b/commands/command_edit.h
index e99ce2407..ab842a13c 100644
--- a/commands/command_edit.h
+++ b/commands/command_edit.h
@@ -5,6 +5,7 @@
#define COMMAND_EDIT_H
#include "command_base.h"
+#include "command.h" // for EditCylinderType
#include "core/subsurface-qt/divelistnotifier.h"
#include <QVector>
@@ -377,6 +378,49 @@ private:
void redo() override;
};
+class AddCylinder : public EditDivesBase {
+public:
+ AddCylinder(bool currentDiveOnly);
+ ~AddCylinder();
+private:
+ cylinder_t cyl;
+ void undo() override;
+ void redo() override;
+ bool workToBeDone() override;
+};
+
+class EditCylinderBase : public EditDivesBase {
+protected:
+ EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly, int sameCylinderFlags);
+ ~EditCylinderBase();
+
+ std::vector<cylinder_t> cyl;
+ std::vector<int> indexes; // An index for each dive in the dives vector.
+ bool workToBeDone() override;
+};
+
+class RemoveCylinder : public EditCylinderBase {
+public:
+ RemoveCylinder(int index, bool currentDiveOnly);
+private:
+ void undo() override;
+ void redo() override;
+};
+
+// Instead of implementing an undo command for every single field in a cylinder,
+// we only have one and pass an edit "type". We either edit the type, pressure
+// or gasmix fields. This has mostly historical reasons rooted in the way the
+// CylindersModel code works. The model works for undo and also in the planner
+// without undo. Having a single undo-command simplifies the code there.
+class EditCylinder : public EditCylinderBase {
+public:
+ EditCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentDiveOnly); // Clones cylinder
+private:
+ EditCylinderType type;
+ void undo() override;
+ void redo() override;
+};
+
#ifdef SUBSURFACE_MOBILE
// Edit a full dive. This is used on mobile where we don't have per-field granularity.
// It may add or edit a dive site.
@@ -406,5 +450,4 @@ private:
#endif // SUBSURFACE_MOBILE
} // namespace Command
-
#endif
diff --git a/commands/command_event.cpp b/commands/command_event.cpp
new file mode 100644
index 000000000..391dfe0a9
--- /dev/null
+++ b/commands/command_event.cpp
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "command_event.h"
+#include "core/dive.h"
+#include "core/selection.h"
+#include "core/subsurface-qt/divelistnotifier.h"
+#include "core/libdivecomputer.h"
+#include "core/gettextfromc.h"
+#include <QVector>
+
+namespace Command {
+
+EventBase::EventBase(struct dive *dIn, int dcNrIn) :
+ d(dIn), dcNr(dcNrIn)
+{
+}
+
+void EventBase::redo()
+{
+ redoit(); // Call actual function in base class
+ updateDive();
+}
+
+void EventBase::undo()
+{
+ undoit(); // Call actual function in base class
+ updateDive();
+}
+
+void EventBase::updateDive()
+{
+ invalidate_dive_cache(d);
+ emit diveListNotifier.eventsChanged(d);
+ dc_number = dcNr;
+ setSelection({ d }, d);
+}
+
+AddEventBase::AddEventBase(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr),
+ eventToAdd(ev)
+{
+}
+
+bool AddEventBase::workToBeDone()
+{
+ return true;
+}
+
+void AddEventBase::redoit()
+{
+ struct divecomputer *dc = get_dive_dc(d, dcNr);
+ eventToRemove = eventToAdd.get();
+ add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend
+}
+
+void AddEventBase::undoit()
+{
+ struct divecomputer *dc = get_dive_dc(d, dcNr);
+ remove_event_from_dc(dc, eventToRemove);
+ eventToAdd.reset(eventToRemove); // take ownership of event
+ eventToRemove = nullptr;
+}
+
+AddEventBookmark::AddEventBookmark(struct dive *d, int dcNr, int seconds) :
+ AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark"))
+{
+ setText(tr("Add bookmark"));
+}
+
+AddEventDivemodeSwitch::AddEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode) :
+ AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_BOOKMARK, 0, divemode, QT_TRANSLATE_NOOP("gettextFromC", "modechange")))
+{
+ setText(tr("Add dive mode switch to %1").arg(gettextFromC::tr(divemode_text_ui[divemode])));
+}
+
+AddEventSetpointChange::AddEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2) :
+ AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_PO2, 0, pO2.mbar, QT_TRANSLATE_NOOP("gettextFromC", "SP change")))
+{
+ setText(tr("Add set point change")); // TODO: format pO2 value in bar or psi.
+}
+
+RenameEvent::RenameEvent(struct dive *d, int dcNr, struct event *ev, const char *name) : EventBase(d, dcNr),
+ eventToAdd(clone_event_rename(ev, name)),
+ eventToRemove(ev)
+{
+ setText(tr("Rename bookmark to %1").arg(name));
+}
+
+bool RenameEvent::workToBeDone()
+{
+ return true;
+}
+
+void RenameEvent::redoit()
+{
+ struct divecomputer *dc = get_dive_dc(d, dcNr);
+ swap_event(dc, eventToRemove, eventToAdd.get());
+ event *tmp = eventToRemove;
+ eventToRemove = eventToAdd.release();
+ eventToAdd.reset(tmp);
+}
+
+void RenameEvent::undoit()
+{
+ // Undo and redo do the same thing - they simply swap events
+ redoit();
+}
+
+RemoveEvent::RemoveEvent(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr),
+ eventToRemove(ev),
+ cylinder(ev->type == SAMPLE_EVENT_GASCHANGE2 || ev->type == SAMPLE_EVENT_GASCHANGE ?
+ ev->gas.index : -1)
+{
+ setText(tr("Remove %1 event").arg(ev->name));
+}
+
+bool RemoveEvent::workToBeDone()
+{
+ return true;
+}
+
+void RemoveEvent::redoit()
+{
+ struct divecomputer *dc = get_dive_dc(d, dcNr);
+ remove_event_from_dc(dc, eventToRemove);
+ eventToAdd.reset(eventToRemove); // take ownership of event
+ eventToRemove = nullptr;
+}
+
+void RemoveEvent::undoit()
+{
+ struct divecomputer *dc = get_dive_dc(d, dcNr);
+ eventToRemove = eventToAdd.get();
+ add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend
+}
+
+void RemoveEvent::post() const
+{
+ if (cylinder < 0)
+ return;
+
+ fixup_dive(d);
+ emit diveListNotifier.cylinderEdited(d, cylinder);
+
+ // TODO: This is silly we send a DURATION change event so that the statistics are recalculated.
+ // We should instead define a proper DiveField that expresses the change caused by a gas switch.
+ emit diveListNotifier.divesChanged(QVector<dive *>{ d }, DiveField::DURATION | DiveField::DEPTH);
+}
+
+AddGasSwitch::AddGasSwitch(struct dive *d, int dcNr, int seconds, int tank) : EventBase(d, dcNr)
+{
+ // If there is a gas change at this time stamp, remove it before adding the new one.
+ // There shouldn't be more than one gas change per time stamp. Just in case we'll
+ // support that anyway.
+ struct divecomputer *dc = get_dive_dc(d, dcNr);
+ struct event *gasChangeEvent = dc->events;
+ while ((gasChangeEvent = get_next_event_mutable(gasChangeEvent, "gaschange")) != NULL) {
+ if (gasChangeEvent->time.seconds == seconds) {
+ eventsToRemove.push_back(gasChangeEvent);
+ int idx = gasChangeEvent->gas.index;
+ if (std::find(cylinders.begin(), cylinders.end(), idx) == cylinders.end())
+ cylinders.push_back(idx); // cylinders might have changed their status
+ }
+ gasChangeEvent = gasChangeEvent->next;
+ }
+
+ eventsToAdd.emplace_back(create_gas_switch_event(d, dc, seconds, tank));
+}
+
+bool AddGasSwitch::workToBeDone()
+{
+ return true;
+}
+
+void AddGasSwitch::redoit()
+{
+ std::vector<OwningEventPtr> newEventsToAdd;
+ std::vector<event *> newEventsToRemove;
+ newEventsToAdd.reserve(eventsToRemove.size());
+ newEventsToRemove.reserve(eventsToAdd.size());
+ struct divecomputer *dc = get_dive_dc(d, dcNr);
+
+ for (event *ev: eventsToRemove) {
+ remove_event_from_dc(dc, ev);
+ newEventsToAdd.emplace_back(ev); // take ownership of event
+ }
+ for (OwningEventPtr &ev: eventsToAdd) {
+ newEventsToRemove.push_back(ev.get());
+ add_event_to_dc(dc, ev.release()); // return ownership to backend
+ }
+ eventsToAdd = std::move(newEventsToAdd);
+ eventsToRemove = std::move(newEventsToRemove);
+
+ // this means we potentially have a new tank that is being used and needs to be shown
+ fixup_dive(d);
+
+ for (int idx: cylinders)
+ emit diveListNotifier.cylinderEdited(d, idx);
+
+ // TODO: This is silly we send a DURATION change event so that the statistics are recalculated.
+ // We should instead define a proper DiveField that expresses the change caused by a gas switch.
+ emit diveListNotifier.divesChanged(QVector<dive *>{ d }, DiveField::DURATION | DiveField::DEPTH);
+}
+
+void AddGasSwitch::undoit()
+{
+ // Undo and redo do the same thing, as the dives-to-be-added and dives-to-be-removed are exchanged.
+ redoit();
+}
+
+} // namespace Command
diff --git a/commands/command_event.h b/commands/command_event.h
new file mode 100644
index 000000000..b7e67f218
--- /dev/null
+++ b/commands/command_event.h
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+// Note: this header file is used by the undo-machinery and should not be included elsewhere.
+
+#ifndef COMMAND_EVENT_H
+#define COMMAND_EVENT_H
+
+#include "command_base.h"
+
+
+// We put everything in a namespace, so that we can shorten names without polluting the global namespace
+namespace Command {
+
+// Events are a strange thing: they contain there own description which means
+// that on changing the description a new object must be allocated. Moreover,
+// it means that these objects can't be collected in a table.
+// Therefore, the undo commands work on events as they do with dives: using
+// owning pointers. See comments in command_base.h
+
+class EventBase : public Base {
+protected:
+ EventBase(struct dive *d, int dcNr);
+ void undo() override;
+ void redo() override;
+ virtual void redoit() = 0;
+ virtual void undoit() = 0;
+
+ // Note: we store dive and the divecomputer-number instead of a pointer to the divecomputer.
+ // Since one divecomputer is integrated into the dive structure, pointers to divecomputers
+ // are probably not stable.
+ struct dive *d;
+ int dcNr;
+private:
+ void updateDive();
+};
+
+class AddEventBase : public EventBase {
+public:
+ AddEventBase(struct dive *d, int dcNr, struct event *ev); // Takes ownership of event!
+private:
+ bool workToBeDone() override;
+ void undoit() override;
+ void redoit() override;
+
+ OwningEventPtr eventToAdd; // for redo
+ event *eventToRemove; // for undo
+};
+
+class AddEventBookmark : public AddEventBase {
+public:
+ AddEventBookmark(struct dive *d, int dcNr, int seconds);
+};
+
+class AddEventDivemodeSwitch : public AddEventBase {
+public:
+ AddEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode);
+};
+
+class AddEventSetpointChange : public AddEventBase {
+public:
+ AddEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2);
+};
+
+class RenameEvent : public EventBase {
+public:
+ RenameEvent(struct dive *d, int dcNr, struct event *ev, const char *name);
+private:
+ bool workToBeDone() override;
+ void undoit() override;
+ void redoit() override;
+
+ OwningEventPtr eventToAdd; // for undo and redo
+ event *eventToRemove; // for undo and redo
+};
+
+class RemoveEvent : public EventBase {
+public:
+ RemoveEvent(struct dive *d, int dcNr, struct event *ev);
+private:
+ bool workToBeDone() override;
+ void undoit() override;
+ void redoit() override;
+ void post() const; // Called to fix up dives should a gas-change have happened.
+
+ OwningEventPtr eventToAdd; // for undo
+ event *eventToRemove; // for redo
+ int cylinder; // affected cylinder (if removing gas switch). <0: not a gas switch.
+};
+
+class AddGasSwitch : public EventBase {
+public:
+ AddGasSwitch(struct dive *d, int dcNr, int seconds, int tank);
+private:
+ bool workToBeDone() override;
+ void undoit() override;
+ void redoit() override;
+
+ std::vector<int> cylinders; // cylinders that are modified
+ std::vector<OwningEventPtr> eventsToAdd;
+ std::vector<event *> eventsToRemove;
+};
+
+} // namespace Command
+
+#endif // COMMAND_EVENT_H