summaryrefslogtreecommitdiffstats
path: root/desktop-widgets/undocommands.h
diff options
context:
space:
mode:
Diffstat (limited to 'desktop-widgets/undocommands.h')
-rw-r--r--desktop-widgets/undocommands.h192
1 files changed, 177 insertions, 15 deletions
diff --git a/desktop-widgets/undocommands.h b/desktop-widgets/undocommands.h
index 4e485e5ed..bbc1f05bd 100644
--- a/desktop-widgets/undocommands.h
+++ b/desktop-widgets/undocommands.h
@@ -2,50 +2,212 @@
#ifndef UNDOCOMMANDS_H
#define UNDOCOMMANDS_H
+#include "core/dive.h"
+
#include <QUndoCommand>
-#include <QMap>
+#include <QCoreApplication> // For Q_DECLARE_TR_FUNCTIONS
+#include <QVector>
+#include <memory>
+
+// The classes declared in this file represent units-of-work, which can be exectuted / undone
+// repeatedly. The command objects are collected in a linear list implemented in the QUndoStack class.
+// They contain the information that is necessary to either perform or undo the unit-of-work.
+// The usage is:
+// constructor: generate information that is needed for executing the unit-of-work
+// redo(): performs the unit-of-work and generates the information that is needed for undo()
+// undo(): undos the unit-of-work and regenerates the initial information needed in redo()
+// The needed information is mostly kept in pointers to dives and/or trips, which have to be added
+// or removed.
+// For this to work it is crucial that
+// 1) Pointers to dives and trips remain valid as long as referencing command-objects exist.
+// 2) The dive-table is not resorted, because dives are inserted at given indices.
+//
+// Thus, if a command deletes a dive or a trip, the actual object must not be deleted. Instead,
+// the command object removes pointers to the dive/trip object from the backend and takes ownership.
+// To reverse such a deletion, the object is re-injected into the backend and ownership is given up.
+// Once ownership of a dive is taken, any reference to it was removed from the backend. Thus,
+// subsequent redo()/undo() actions cannot access this object and integrity of the data is ensured.
+//
+// As an example, consider the following course of events: Dive 1 is renumbered and deleted, dive 2
+// is added and renumbered. The undo list looks like this (---> non-owning, ***> owning pointers,
+// ===> next item in list)
+//
+// Undo-List
+// +-----------------+ +---------------+ +------------+ +-----------------+
+// | Renumber dive 1 |====>| Delete dive 1 |====>| Add dive 2 |====>| Renumber dive 2 |
+// +------------------ +---------------+ +------------+ +-----------------+
+// | * | |
+// | +--------+ * | +--------+ |
+// +----->| Dive 1 |<****** +--->| Dive 2 |<------+
+// +--------+ +--------+
+// ^
+// +---------+ *
+// | Backend |****************
+// +---------+
+// Two points of note:
+// 1) Every dive is owned by either the backend or exactly one command object.
+// 2) All references to dive 1 are *before* the owner "delete dive 2", thus the pointer is always valid.
+// 3) References by the backend are *always* owning.
+//
+// The user undos the last two commands. The situation now looks like this:
+//
+//
+// Undo-List Redo-List
+// +-----------------+ +---------------+ +------------+ +-----------------+
+// | Renumber dive 1 |====>| Delete dive 1 | | Add dive 2 |<====| Renumber dive 2 |
+// +------------------ +---------------+ +------------+ +-----------------+
+// | * * |
+// | +--------+ * * +--------+ |
+// +----->| Dive 1 |<****** ****>| Dive 2 |<------+
+// +--------+ +--------+
+//
+// +---------+
+// | Backend |
+// +---------+
+// Again:
+// 1) Every dive is owned by either the backend (here none) or exactly one command object.
+// 2) All references to dive 1 are *before* the owner "delete dive 1", thus the pointer is always valid.
+// 3) All references to dive 2 are *after* the owner "add dive 2", thus the pointer is always valid.
+//
+// The user undos one more command:
+//
+// Undo-List Redo-List
+// +-----------------+ +---------------+ +------------+ +-----------------+
+// | Renumber dive 1 | | Delete dive 1 |<====| Add dive 2 |<====| Renumber dive 2 |
+// +------------------ +---------------+ +------------+ +-----------------+
+// | | * |
+// | +--------+ | * +--------+ |
+// +----->| Dive 1 |<-----+ ****>| Dive 2 |<------+
+// +--------+ +--------+
+// ^
+// * +---------+
+// ***************| Backend |
+// +---------+
+// Same points as above.
+// The user now adds a dive 3. The redo list will be deleted:
+//
+// Undo-List
+// +-----------------+ +------------+
+// | Renumber dive 1 |=============================================>| Add dive 3 |
+// +------------------ +------------+
+// | |
+// | +--------+ +--------+ |
+// +----->| Dive 1 | | Dive 3 |<---+
+// +--------+ +--------+
+// ^ ^
+// * +---------+ *
+// ***************| Backend |****************
+// +---------+
+// Note:
+// 1) Dive 2 was deleted with the "add dive 2" command, because that was the owner.
+// 2) Dive 1 was not deleted, because it is owned by the backend.
+//
+// To take ownership of dives/trips, the OnwingDivePtr and OwningTripPtr types are used. These
+// are simply derived from std::unique_ptr and therefore use well-established semantics.
+// Expressed in C-terms: std::unique_ptr<T> is exactly the same as T* with the following
+// twists:
+// 1) default-initialized to NULL.
+// 2) if it goes out of scope (local scope or containing object destroyed), it does:
+// if (ptr) free_function(ptr);
+// whereby free_function can be configured (defaults to delete ptr).
+// 3) assignment between two std::unique_ptr<T> compiles only if the source is reset (to NULL).
+// (hence the name - there's a *unique* owner).
+// While this sounds trivial, experience shows that this distinctly simplifies memory-management
+// (it's not necessary to manually delete all vector items in the destructur, etc).
+// Note that Qt's own implementation (QScoperPointer) is not up to the job, because it doesn't implement
+// move-semantics and Qt's containers are incompatible, owing to COW semantics.
+//
+// Usage:
+// OwningDivePtr dPtr; // Initialize to null-state: not owning any dive.
+// OwningDivePtr dPtr(dive); // Take ownership of dive (which is of type struct dive *).
+// // If dPtr goes out of scope, the dive will be freed with free_dive().
+// struct dive *d = dPtr.release(); // Give up ownership of dive. dPtr is reset to null.
+// struct dive *d = d.get(); // Get pointer dive, but don't release ownership.
+// dPtr.reset(dive2); // Delete currently owned dive with free_dive() and get ownership of dive2.
+// dPtr.reset(); // Delete currently owned dive and reset to null.
+// dPtr2 = dPtr1; // Fails to compile.
+// dPtr2 = std::move(dPtr1); // dPtr2 takes ownership, dPtr1 is reset to null.
+// OwningDivePtr fun();
+// dPtr1 = fun(); // Compiles. Simply put: the compiler knows that the result of fun() will
+// // be trashed and therefore can be moved-from.
+// std::vector<OwningDivePtr> v: // Define an empty vector of owning pointers.
+// v.emplace_back(dive); // Take ownership of dive and add at end of vector
+// // If the vector goes out of scope, all dives will be freed with free_dive().
+// v.clear(v); // Reset the vector to zero length. If the elements weren't release()d,
+// // the pointed-to dives are freed with free_dive()
+
+// Classes used to automatically call free_dive()/free_trip for owning pointers that go out of scope.
+struct DiveDeleter {
+ void operator()(dive *d) { free_dive(d); }
+};
+struct TripDeleter {
+ void operator()(dive_trip *t) { free_trip(t); }
+};
+
+// Owning pointers to dive and dive_trip objects.
+typedef std::unique_ptr<dive, DiveDeleter> OwningDivePtr;
+typedef std::unique_ptr<dive_trip, TripDeleter> OwningTripPtr;
class UndoDeleteDive : public QUndoCommand {
+ Q_DECLARE_TR_FUNCTIONS(Command)
public:
- UndoDeleteDive(QList<struct dive*> deletedDives);
+ UndoDeleteDive(const QVector<dive *> &divesToDelete);
+private:
void undo() override;
void redo() override;
-private:
- QList<struct dive*> diveList;
- QList<struct dive_trip*> tripList;
+ // For redo
+ QVector<struct dive*> divesToDelete;
+
+ // For undo
+ struct DiveToAdd {
+ OwningDivePtr dive; // Dive to add
+ dive_trip *trip; // Trip, may be null
+ int idx; // Position in divelist
+ };
+ std::vector<OwningTripPtr> tripsToAdd;
+ std::vector<DiveToAdd> divesToAdd;
};
class UndoShiftTime : public QUndoCommand {
+ Q_DECLARE_TR_FUNCTIONS(Command)
public:
- UndoShiftTime(QList<int> changedDives, int amount);
+ UndoShiftTime(QVector<int> changedDives, int amount);
+private:
void undo() override;
void redo() override;
-private:
- QList<int> diveList;
+ // For redo and undo
+ QVector<int> diveList;
int timeChanged;
};
class UndoRenumberDives : public QUndoCommand {
+ Q_DECLARE_TR_FUNCTIONS(Command)
public:
- UndoRenumberDives(QMap<int, QPair<int, int> > originalNumbers);
+ UndoRenumberDives(const QVector<QPair<int, int>> &divesToRenumber);
+private:
void undo() override;
void redo() override;
-private:
- QMap<int,QPair<int, int> > oldNumbers;
+ // For redo and undo: pairs of dive-id / new number
+ QVector<QPair<int, int>> divesToRenumber;
};
class UndoRemoveDivesFromTrip : public QUndoCommand {
+ Q_DECLARE_TR_FUNCTIONS(Command)
public:
- UndoRemoveDivesFromTrip(QMap<struct dive*, dive_trip*> removedDives);
+ UndoRemoveDivesFromTrip(const QVector<dive *> &divesToRemove);
+private:
void undo() override;
void redo() override;
-private:
- QMap<struct dive*, dive_trip*> divesToUndo;
- QList<struct dive_trip*> tripList;
+ // For redo
+ QVector<dive *> divesToRemove;
+
+ // For undo
+ std::vector<std::pair<dive *, dive_trip *>> divesToAdd;
+ std::vector<OwningTripPtr> tripsToAdd;
};
#endif // UNDOCOMMANDS_H