summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2018-07-19 22:35:25 +0200
committerGravatar Dirk Hohndel <dirk@hohndel.org>2018-10-11 16:22:27 -0700
commit12df9faaa2037b5155ebb84a7f6f6102491a0091 (patch)
tree33dcf0e400f669d088f1b20652e9c0c6f69aae2b
parent61467ea0d59b04f141a68452ee16c70760421d72 (diff)
downloadsubsurface-12df9faaa2037b5155ebb84a7f6f6102491a0091.tar.gz
Undo: implement undo of manual dive-creation
Play manual addition of dives via an UndoCommand. Since this does in large parts the same thing as undo/redo of dive deletion (just the other way round and only a single instead of multiple dive), factor out the functions that add/delete dives and take care of trips. The UI-interaction is just mindless copy&paste and will have to be adapted. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
-rw-r--r--core/dive.h1
-rw-r--r--core/divelist.c44
-rw-r--r--core/divelist.h1
-rw-r--r--desktop-widgets/command_divelist.cpp904
-rw-r--r--desktop-widgets/tab-widgets/maintab.cpp43
-rw-r--r--desktop-widgets/undocommands.cpp106
-rw-r--r--desktop-widgets/undocommands.h29
7 files changed, 1064 insertions, 64 deletions
diff --git a/core/dive.h b/core/dive.h
index f80412a03..2d3edbc5c 100644
--- a/core/dive.h
+++ b/core/dive.h
@@ -418,6 +418,7 @@ extern void add_dive_to_trip(struct dive *, dive_trip_t *);
struct dive *unregister_dive(int idx);
extern void delete_single_dive(int idx);
+extern int dive_get_insertion_index(struct dive *dive);
extern void add_single_dive(int idx, struct dive *dive);
extern void insert_trip(dive_trip_t **trip);
diff --git a/core/divelist.c b/core/divelist.c
index 07c9ea87d..a25927e90 100644
--- a/core/divelist.c
+++ b/core/divelist.c
@@ -987,25 +987,29 @@ struct dive **grow_dive_table(struct dive_table *table)
return dives;
}
+/* get the index where we want to insert the dive so that everything stays
+ * ordered reverse-chronologically */
+int dive_get_insertion_index(struct dive *dive)
+{
+ for (int i = 0; i < dive_table.nr; i++) {
+ if (dive->when <= dive_table.dives[i]->when)
+ return i;
+ }
+ return dive_table.nr;
+}
+
+/* add a dive at the given index. if the index is negative, the dive will
+ * be added according to reverse chronological order */
void add_single_dive(int idx, struct dive *dive)
{
int i;
+ if (idx < 0)
+ idx = dive_get_insertion_index(dive);
grow_dive_table(&dive_table);
dive_table.nr++;
if (dive->selected)
amount_selected++;
- if (idx < 0) {
- // convert an idx of -1 so we do insert-in-chronological-order
- idx = dive_table.nr - 1;
- for (int i = 0; i < dive_table.nr - 1; i++) {
- if (dive->when <= dive_table.dives[i]->when) {
- idx = i;
- break;
- }
- }
- }
-
for (i = idx; i < dive_table.nr; i++) {
struct dive *tmp = dive_table.dives[i];
dive_table.dives[i] = dive;
@@ -1453,6 +1457,24 @@ void process_imported_dives(struct dive_table *import_table, bool prefer_importe
mark_divelist_changed(true);
}
+/* return the number a dive gets when inserted at the given index.
+ * this function is supposed to be called *before* a dive was added.
+ * this returns:
+ * - 1 for an empty log
+ * - last_nr+1 for addition at end of log (if last dive had a number)
+ * - 0 for all other cases
+ */
+int get_dive_nr_at_idx(int idx)
+{
+ if (dive_table.nr == 0)
+ return 1;
+ if (idx >= dive_table.nr) {
+ struct dive *last_dive = get_dive(dive_table.nr - 1);
+ return last_dive->number ? last_dive->number + 1 : 0;
+ }
+ return 0;
+}
+
void set_dive_nr_for_current_dive()
{
if (dive_table.nr == 1)
diff --git a/core/divelist.h b/core/divelist.h
index 3687b0881..2b2ee07a5 100644
--- a/core/divelist.h
+++ b/core/divelist.h
@@ -42,6 +42,7 @@ extern void find_new_trip_start_time(dive_trip_t *trip);
extern struct dive *first_selected_dive();
extern struct dive *last_selected_dive();
extern bool is_trip_before_after(const struct dive *dive, bool before);
+extern int get_dive_nr_at_idx(int idx);
extern void set_dive_nr_for_current_dive();
extern timestamp_t get_surface_interval(timestamp_t when);
extern void delete_dive_from_table(struct dive_table *table, int idx);
diff --git a/desktop-widgets/command_divelist.cpp b/desktop-widgets/command_divelist.cpp
new file mode 100644
index 000000000..6f638b2a6
--- /dev/null
+++ b/desktop-widgets/command_divelist.cpp
@@ -0,0 +1,904 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "command_divelist.h"
+#include "desktop-widgets/mainwindow.h"
+#include "desktop-widgets/divelistview.h"
+#include "core/divelist.h"
+#include "core/display.h" // for amount_selected
+#include "core/subsurface-qt/DiveListNotifier.h"
+#include "qt-models/filtermodels.h"
+
+namespace Command {
+
+// Generally, signals are sent in batches per trip. To avoid writing the same loop
+// again and again, this template takes a vector of trip / dive pairs, sorts it
+// by trip and then calls a function-object with trip and a QVector of dives in that trip.
+// Input parameters:
+// - dives: a vector of trip,dive pairs, which will be sorted and processed in batches by trip.
+// - action: a function object, taking a trip-pointer and a QVector of dives, which will be called for each batch.
+template<typename Function>
+void processByTrip(std::vector<std::pair<dive_trip *, dive *>> &dives, Function action)
+{
+ // Use std::tie for lexicographical sorting of trip, then start-time
+ std::sort(dives.begin(), dives.end(),
+ [](const std::pair<dive_trip *, dive *> &e1, const std::pair<dive_trip *, dive *> &e2)
+ { return std::tie(e1.first, e1.second->when) < std::tie(e2.first, e2.second->when); });
+
+ // Then, process the dives in batches by trip
+ size_t i, j; // Begin and end of batch
+ for (i = 0; i < dives.size(); i = j) {
+ dive_trip *trip = dives[i].first;
+ for (j = i + 1; j < dives.size() && dives[j].first == trip; ++j)
+ ; // pass
+ // Copy dives into a QVector. Some sort of "range_view" would be ideal, but Qt doesn't work this way.
+ QVector<dive *> divesInTrip(j - i);
+ for (size_t k = i; k < j; ++k)
+ divesInTrip[k - i] = dives[k].second;
+
+ // Finally, emit the signal
+ action(trip, divesInTrip);
+ }
+}
+
+
+// This helper function removes a dive, takes ownership of the dive and adds it to a DiveToAdd structure.
+// It is crucial that dives are added in reverse order of deletion, so the the indices are correctly
+// set and that the trips are added before they are used!
+DiveToAdd DiveListBase::removeDive(struct dive *d)
+{
+ // If the dive to be removed is selected, we will inform the frontend
+ // later via a signal that the dive changed.
+ if (d->selected)
+ selectionChanged = true;
+
+ // If the dive was the current dive, reset the current dive. The calling
+ // command is responsible of finding a new dive.
+ if (d == current_dive) {
+ selectionChanged = true; // Should have been set above, as current dive is always selected.
+ current_dive = nullptr;
+ }
+
+ DiveToAdd res;
+ res.idx = get_divenr(d);
+ if (res.idx < 0)
+ qWarning() << "Deletion of unknown dive!";
+
+ // remove dive from trip - if this is the last dive in the trip
+ // remove the whole trip.
+ res.trip = unregister_dive_from_trip(d, false);
+ if (res.trip && res.trip->nrdives == 0) {
+ unregister_trip(res.trip); // Remove trip from backend
+ res.tripToAdd.reset(res.trip); // Take ownership of trip
+ }
+
+ res.dive.reset(unregister_dive(res.idx)); // Remove dive from backend
+
+ return res;
+}
+
+// This helper function adds a dive and returns ownership to the backend. It may also add a dive trip.
+// It is crucial that dives are added in reverse order of deletion (see comment above)!
+// Returns pointer to added dive (which is owned by the backend!)
+dive *DiveListBase::addDive(DiveToAdd &d)
+{
+ if (d.tripToAdd)
+ insert_trip_dont_merge(d.tripToAdd.release()); // Return ownership to backend
+ if (d.trip)
+ add_dive_to_trip(d.dive.get(), d.trip);
+ dive *res = d.dive.release(); // Give up ownership of dive
+
+ // Set the filter flag according to current filter settings
+ bool show = MultiFilterSortModel::instance()->showDive(res);
+ res->hidden_by_filter = !show;
+
+ add_single_dive(d.idx, res); // Return ownership to backend
+
+ // If the dive to be removed is selected, we will inform the frontend
+ // later via a signal that the dive changed.
+ if (res->selected)
+ selectionChanged = true;
+
+ return res;
+}
+
+// This helper function calls removeDive() on a list of dives to be removed and
+// returns a vector of corresponding DiveToAdd objects, which can later be readded.
+// The passed in vector is cleared.
+std::vector<DiveToAdd> DiveListBase::removeDives(std::vector<dive *> &divesToDelete)
+{
+ std::vector<DiveToAdd> res;
+ res.reserve(divesToDelete.size());
+
+ // First, tell the filters that dives are removed. This could
+ // be done later using the emitted signals, but we do this here
+ // for symmetry with addDives()
+ MultiFilterSortModel::instance()->divesDeleted(QVector<dive *>::fromStdVector(divesToDelete));
+
+ for (dive *d: divesToDelete)
+ res.push_back(removeDive(d));
+ divesToDelete.clear();
+
+ // We send one dives-deleted signal per trip (see comments in DiveListNotifier.h).
+ // Therefore, collect all dives in an array and sort by trip.
+ std::vector<std::pair<dive_trip *, dive *>> dives;
+ dives.reserve(res.size());
+ for (const DiveToAdd &entry: res)
+ dives.push_back({ entry.trip, entry.dive.get() });
+
+ // Send signals.
+ processByTrip(dives, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) {
+ // Now, let's check if this trip is supposed to be deleted, by checking if it was marked
+ // as "add it". We could be smarter here, but let's just check the whole array for brevity.
+ bool deleteTrip = trip &&
+ std::find_if(res.begin(), res.end(), [trip](const DiveToAdd &entry)
+ { return entry.tripToAdd.get() == trip; }) != res.end();
+ emit diveListNotifier.divesDeleted(trip, deleteTrip, divesInTrip);
+ });
+ return res;
+}
+
+// This helper function is the counterpart fo removeDives(): it calls addDive() on a list
+// of dives to be (re)added and returns a vector of the added dives. It does this in reverse
+// order, so that trips are created appropriately and indexing is correct.
+// The passed in vector is cleared.
+std::vector<dive *> DiveListBase::addDives(std::vector<DiveToAdd> &divesToAdd)
+{
+ std::vector<dive *> res;
+ res.resize(divesToAdd.size());
+
+ // First, tell the filters that new dives are added. We do this here
+ // instead of later by signals, so that the filter can set the
+ // checkboxes of the new rows to its liking. The added dives will
+ // then appear in the correct shown/hidden state.
+ QVector<dive *> divesForFilter;
+ for (const DiveToAdd &entry: divesToAdd)
+ divesForFilter.push_back(entry.dive.get());
+ MultiFilterSortModel::instance()->divesAdded(divesForFilter);
+
+ // At the end of the function, to send the proper dives-added signals,
+ // we the the list of added trips. Create this list now.
+ std::vector<dive_trip *> addedTrips;
+ for (const DiveToAdd &entry: divesToAdd) {
+ if (entry.tripToAdd)
+ addedTrips.push_back(entry.tripToAdd.get());
+ }
+
+ // Now, add the dives
+ // Note: the idiomatic STL-way would be std::transform, but let's use a loop since
+ // that is closer to classical C-style.
+ auto it2 = res.rbegin();
+ for (auto it = divesToAdd.rbegin(); it != divesToAdd.rend(); ++it, ++it2)
+ *it2 = addDive(*it);
+ divesToAdd.clear();
+
+ // We send one dives-deleted signal per trip (see comments in DiveListNotifier.h).
+ // Therefore, collect all dives in a array and sort by trip.
+ std::vector<std::pair<dive_trip *, dive *>> dives;
+ dives.reserve(res.size());
+ for (dive *d: res)
+ dives.push_back({ d->divetrip, d });
+
+ // Send signals.
+ processByTrip(dives, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) {
+ // Now, let's check if this trip is supposed to be created, by checking if it was marked
+ // as "add it". We could be smarter here, but let's just check the whole array for brevity.
+ bool createTrip = trip && std::find(addedTrips.begin(), addedTrips.end(), trip) != addedTrips.end();
+ // Finally, emit the signal
+ emit diveListNotifier.divesAdded(trip, createTrip, divesInTrip);
+ });
+ return res;
+}
+
+// This helper function renumbers dives according to an array of id/number pairs.
+// The old numbers are stored in the array, thus calling this function twice has no effect.
+// TODO: switch from uniq-id to indexes once all divelist-actions are controlled by undo-able commands
+static void renumberDives(QVector<QPair<dive *, int>> &divesToRenumber)
+{
+ for (auto &pair: divesToRenumber) {
+ dive *d = pair.first;
+ if (!d)
+ continue;
+ std::swap(d->number, pair.second);
+ }
+
+ // Emit changed signals per trip.
+ // First, collect all dives and sort by trip
+ std::vector<std::pair<dive_trip *, dive *>> dives;
+ dives.reserve(divesToRenumber.size());
+ for (const auto &pair: divesToRenumber) {
+ dive *d = pair.first;
+ dives.push_back({ d->divetrip, d });
+ }
+
+ // Send signals.
+ processByTrip(dives, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) {
+ emit diveListNotifier.divesChanged(trip, divesInTrip);
+ });
+}
+
+// This helper function moves a dive to a trip. The old trip is recorded in the
+// passed-in structure. This means that calling the function twice on the same
+// object is a no-op concerning the dive. If the old trip was deleted from the
+// core, an owning pointer to the removed trip is returned, otherwise a null pointer.
+static OwningTripPtr moveDiveToTrip(DiveToTrip &diveToTrip)
+{
+ // Firstly, check if we move to the same trip and bail if this is a no-op.
+ if (diveToTrip.trip == diveToTrip.dive->divetrip)
+ return {};
+
+ // Remove from old trip
+ OwningTripPtr res;
+
+ // Remove dive from trip - if this is the last dive in the trip, remove the whole trip.
+ dive_trip *trip = unregister_dive_from_trip(diveToTrip.dive, false);
+ if (trip && trip->nrdives == 0) {
+ unregister_trip(trip); // Remove trip from backend
+ res.reset(trip);
+ }
+
+ // Store old trip and get new trip we should associate this dive with
+ std::swap(trip, diveToTrip.trip);
+ add_dive_to_trip(diveToTrip.dive, trip);
+ return res;
+}
+
+// This helper function moves a set of dives between trips using the
+// moveDiveToTrip function. Before doing so, it adds the necessary trips to
+// the core. Trips that are removed from the core because they are empty
+// are recorded in the passed in struct. The vectors of trips and dives
+// are reversed. Thus, calling the function twice on the same object is
+// a no-op.
+static void moveDivesBetweenTrips(DivesToTrip &dives)
+{
+ // We collect an array of created trips so that we can instruct
+ // the model to create a new entry
+ std::vector<dive_trip *> createdTrips;
+ createdTrips.reserve(dives.tripsToAdd.size());
+
+ // First, bring back the trip(s)
+ for (OwningTripPtr &trip: dives.tripsToAdd) {
+ dive_trip *t = trip.release(); // Give up ownership
+ createdTrips.push_back(t);
+ insert_trip_dont_merge(t); // Return ownership to backend
+ }
+ dives.tripsToAdd.clear();
+
+ for (DiveToTrip &dive: dives.divesToMove) {
+ OwningTripPtr tripToAdd = moveDiveToTrip(dive);
+ // register trips that we'll have to readd
+ if (tripToAdd)
+ dives.tripsToAdd.push_back(std::move(tripToAdd));
+ }
+
+ // We send one signal per from-trip/to-trip pair.
+ // First, collect all dives in a struct and sort by from-trip/to-trip.
+ struct DiveMoved {
+ dive_trip *from;
+ dive_trip *to;
+ dive *d;
+ };
+ std::vector<DiveMoved> divesMoved;
+ divesMoved.reserve(dives.divesToMove.size());
+ for (const DiveToTrip &entry: dives.divesToMove)
+ divesMoved.push_back({ entry.trip, entry.dive->divetrip, entry.dive });
+
+ // Sort lexicographically by from-trip, to-trip and by start-time.
+ // Use std::tie() for lexicographical sorting.
+ std::sort(divesMoved.begin(), divesMoved.end(), [] ( const DiveMoved &d1, const DiveMoved &d2)
+ { return std::tie(d1.from, d1.to, d1.d->when) < std::tie(d2.from, d2.to, d2.d->when); });
+
+ // Now, process the dives in batches by trip
+ // TODO: this is a bit different from the cases above, so we don't use the processByTrip template,
+ // but repeat the loop here. We might think about generalizing the template, if more of such
+ // "special cases" appear.
+ size_t i, j; // Begin and end of batch
+ for (i = 0; i < divesMoved.size(); i = j) {
+ dive_trip *from = divesMoved[i].from;
+ dive_trip *to = divesMoved[i].to;
+ for (j = i + 1; j < divesMoved.size() && divesMoved[j].from == from && divesMoved[j].to == to; ++j)
+ ; // pass
+ // Copy dives into a QVector. Some sort of "range_view" would be ideal, but Qt doesn't work this way.
+ QVector<dive *> divesInTrip(j - i);
+ for (size_t k = i; k < j; ++k)
+ divesInTrip[k - i] = divesMoved[k].d;
+
+ // Check if the from-trip was deleted: If yes, it was recorded in the tripsToAdd structure
+ bool deleteFrom = from &&
+ std::find_if(dives.tripsToAdd.begin(), dives.tripsToAdd.end(),
+ [from](const OwningTripPtr &trip) { return trip.get() == from; }) != dives.tripsToAdd.end();
+ // Check if the to-trip has to be created. For this purpose, we saved an array of trips to be created.
+ bool createTo = false;
+ if (to) {
+ // Check if the element is there...
+ auto it = std::find(createdTrips.begin(), createdTrips.end(), to);
+
+ // ...if it is - remove it as we don't want the model to create the trip twice!
+ if (it != createdTrips.end()) {
+ createTo = true;
+ // erase/remove would be more performant, but this is irrelevant in the big scheme of things.
+ createdTrips.erase(it);
+ }
+ }
+
+ // Finally, emit the signal
+ emit diveListNotifier.divesMovedBetweenTrips(from, to, deleteFrom, createTo, divesInTrip);
+ }
+
+ // Reverse the tripsToAdd and the divesToAdd, so that on undo/redo the operations
+ // will be performed in reverse order.
+ std::reverse(dives.tripsToAdd.begin(), dives.tripsToAdd.end());
+ std::reverse(dives.divesToMove.begin(), dives.divesToMove.end());
+}
+
+// When we initialize the command we don't have to roll-back any selection change
+DiveListBase::DiveListBase() : firstExecution(true)
+{
+}
+
+// Turn current selection into a vector.
+// TODO: This could be made much more efficient if we kept a sorted list of selected dives!
+static std::vector<dive *> getDiveSelection()
+{
+ std::vector<dive *> res;
+ res.reserve(amount_selected);
+
+ int i;
+ dive *d;
+ for_each_dive(i, d) {
+ if (d->selected)
+ res.push_back(d);
+ }
+ return res;
+}
+
+void DiveListBase::initWork()
+{
+ selectionChanged = false;
+}
+
+void DiveListBase::finishWork()
+{
+ if (selectionChanged) // If the selection changed -> tell the frontend
+ emit diveListNotifier.selectionChanged();
+}
+
+// Set the current dive either from a list of selected dives,
+// or a newly selected dive. In both cases, try to select the
+// dive that is newer that is newer than the given date.
+// This mimics the old behavior when the current dive changed.
+static void setClosestCurrentDive(timestamp_t when, const std::vector<dive *> &selection)
+{
+ // Start from back until we get the first dive that is before
+ // the supposed-to-be selected dive. (Note: this mimics the
+ // old behavior when the current dive changed).
+ for (auto it = selection.rbegin(); it < selection.rend(); ++it) {
+ if ((*it)->when > when && !(*it)->hidden_by_filter) {
+ current_dive = *it;
+ return;
+ }
+ }
+
+ // We didn't find a more recent selected dive -> try to
+ // find *any* visible selected dive.
+ for (dive *d: selection) {
+ if (!d->hidden_by_filter) {
+ current_dive = d;
+ return;
+ }
+ }
+
+ // No selected dive is visible! Take the closest dive. Note, this might
+ // return null, but that just means unsetting the current dive (as no
+ // dive is visible anyway).
+ current_dive = find_next_visible_dive(when);
+}
+
+// Rese the selection to the dives of the "selection" vector and send the appropriate signals.
+// Set the current dive to "currentDive". "currentDive" must be an element of "selection" (or
+// null if "seletion" is empty).
+void DiveListBase::restoreSelection(const std::vector<dive *> &selection, dive *currentDive)
+{
+ // To do so, generate vectors of dives to be selected and deselected.
+ // We send signals batched by trip, so keep track of trip/dive pairs.
+ std::vector<std::pair<dive_trip *, dive *>> divesToSelect;
+ std::vector<std::pair<dive_trip *, dive *>> divesToDeselect;
+
+ // TODO: We might want to keep track of selected dives in a more efficient way!
+ int i;
+ dive *d;
+ amount_selected = 0; // We recalculate amount_selected
+ for_each_dive(i, d) {
+ // We only modify dives that are currently visible.
+ if (d->hidden_by_filter) {
+ d->selected = false; // Note, not necessary, just to be sure
+ // that we get amount_selected right
+ continue;
+ }
+
+ // Search the dive in the list of selected dives.
+ // TODO: By sorting the list in the same way as the backend, this could be made more efficient.
+ bool newState = std::find(selection.begin(), selection.end(), d) != selection.end();
+
+ // TODO: Instead of using select_dive() and deselect_dive(), we set selected directly.
+ // The reason is that deselect() automatically sets a new current dive, which we
+ // don't want, as we set it later anyway.
+ // There is other parts of the C++ code that touches the innards directly, but
+ // ultimately this should be pushed down to C.
+ if (newState && !d->selected) {
+ d->selected = true;
+ ++amount_selected;
+ divesToSelect.push_back({ d->divetrip, d });
+ } else if (!newState && d->selected) {
+ d->selected = false;
+ divesToDeselect.push_back({ d->divetrip, d });
+ }
+ }
+
+ // Send the select and deselect signals
+ processByTrip(divesToSelect, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) {
+ emit diveListNotifier.divesSelected(trip, divesInTrip);
+ });
+ processByTrip(divesToDeselect, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) {
+ emit diveListNotifier.divesDeselected(trip, divesInTrip);
+ });
+
+ bool currentDiveChanged = false;
+ if (current_dive != currentDive) {
+ currentDiveChanged = true;
+
+ // We cannot simply change the currentd dive to the given dive.
+ // It might be hidden by a filter and thus not be selected.
+ if (currentDive->selected)
+ // Current dive is visible and selected. Excellent.
+ current_dive = currentDive;
+ else
+ // Current not visible -> find a different dive.
+ setClosestCurrentDive(currentDive->when, selection);
+ emit diveListNotifier.currentDiveChanged();
+ }
+
+ // If anything changed (selection or current dive), send a final signal.
+ if (!divesToSelect.empty() || !divesToDeselect.empty() || currentDiveChanged)
+ selectionChanged = true;
+}
+
+void DiveListBase::undo()
+{
+ auto marker = diveListNotifier.enterCommand();
+ initWork();
+ undoit();
+ finishWork();
+}
+
+void DiveListBase::redo()
+{
+ auto marker = diveListNotifier.enterCommand();
+ initWork();
+ redoit();
+ finishWork();
+}
+
+AddDive::AddDive(dive *d, bool autogroup, bool newNumber)
+{
+ setText(tr("add dive"));
+ // By convention, d is "displayed dive" and can be overwritten.
+ d->maxdepth.mm = 0;
+ d->dc.maxdepth.mm = 0;
+ fixup_dive(d);
+
+ // Get an owning pointer to a copied or moved dive
+ // Note: if move is true, this destroys the old dive!
+ OwningDivePtr divePtr(clone_dive(d));
+ divePtr->selected = false; // If we clone a planned dive, it might have been selected.
+ // We have to clear the flag, as selections will be managed
+ // on dive-addition.
+
+ // If we alloc a new-trip for autogrouping, get an owning pointer to it.
+ OwningTripPtr allocTrip;
+ dive_trip *trip = divePtr->divetrip;
+ // We have to delete the pointer-to-trip, because this would prevent the core from adding to the trip
+ // and we would get the count-of-dives in the trip wrong. Yes, that's all horribly subtle!
+ divePtr->divetrip = nullptr;
+ if (!trip && autogroup) {
+ bool alloc;
+ trip = get_trip_for_new_dive(divePtr.get(), &alloc);
+ if (alloc)
+ allocTrip.reset(trip);
+ }
+
+ int idx = dive_get_insertion_index(divePtr.get());
+ if (newNumber)
+ divePtr->number = get_dive_nr_at_idx(idx);
+
+ divesToAdd.push_back({ std::move(divePtr), std::move(allocTrip), trip, idx });
+}
+
+bool AddDive::workToBeDone()
+{
+ return true;
+}
+
+void AddDive::redoit()
+{
+ // Remember selection so that we can undo it
+ selection = getDiveSelection();
+ currentDive = current_dive;
+
+ divesToRemove = addDives(divesToAdd);
+ mark_divelist_changed(true);
+
+ // Select the newly added dive
+ restoreSelection(divesToRemove, divesToRemove[0]);
+
+ // Exit from edit mode, but don't recalculate dive list
+ // TODO: Remove edit mode
+ MainWindow::instance()->refreshDisplay(false);
+}
+
+void AddDive::undoit()
+{
+ // Simply remove the dive that was previously added...
+ divesToAdd = removeDives(divesToRemove);
+
+ // ...and restore the selection
+ restoreSelection(selection, currentDive);
+
+ // Exit from edit mode, but don't recalculate dive list
+ // TODO: Remove edit mode
+ MainWindow::instance()->refreshDisplay(false);
+}
+
+DeleteDive::DeleteDive(const QVector<struct dive*> &divesToDeleteIn) : divesToDelete(divesToDeleteIn.toStdVector())
+{
+ setText(tr("delete %n dive(s)", "", divesToDelete.size()));
+}
+
+bool DeleteDive::workToBeDone()
+{
+ return !divesToDelete.empty();
+}
+
+void DeleteDive::undoit()
+{
+ divesToDelete = addDives(divesToAdd);
+ mark_divelist_changed(true);
+
+ // Select all re-added dives and make the first one current
+ dive *currentDive = !divesToDelete.empty() ? divesToDelete[0] : nullptr;
+ restoreSelection(divesToDelete, currentDive);
+}
+
+void DeleteDive::redoit()
+{
+ divesToAdd = removeDives(divesToDelete);
+ mark_divelist_changed(true);
+
+ // Deselect all dives and select dive that was close to the first deleted dive
+ dive *newCurrent = nullptr;
+ if (!divesToAdd.empty()) {
+ timestamp_t when = divesToAdd[0].dive->when;
+ newCurrent = find_next_visible_dive(when);
+ }
+ if (newCurrent)
+ restoreSelection(std::vector<dive *>{ newCurrent }, newCurrent);
+ else
+ restoreSelection(std::vector<dive *>(), nullptr);
+}
+
+
+ShiftTime::ShiftTime(const QVector<dive *> &changedDives, int amount)
+ : diveList(changedDives), timeChanged(amount)
+{
+ setText(tr("shift time of %n dives", "", changedDives.count()));
+}
+
+void ShiftTime::redoit()
+{
+ for (dive *d: diveList)
+ d->when -= timeChanged;
+
+ // Changing times may have unsorted the dive table
+ sort_table(&dive_table);
+
+ // We send one dives-deleted signal per trip (see comments in DiveListNotifier.h).
+ // Therefore, collect all dives in a array and sort by trip.
+ std::vector<std::pair<dive_trip *, dive *>> dives;
+ dives.reserve(diveList.size());
+ for (dive *d: diveList)
+ dives.push_back({ d->divetrip, d });
+
+ // Send signals.
+ processByTrip(dives, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) {
+ emit diveListNotifier.divesTimeChanged(trip, timeChanged, divesInTrip);
+ });
+
+ // Negate the time-shift so that the next call does the reverse
+ timeChanged = -timeChanged;
+
+ mark_divelist_changed(true);
+}
+
+bool ShiftTime::workToBeDone()
+{
+ return !diveList.isEmpty();
+}
+
+void ShiftTime::undoit()
+{
+ // Same as redoit(), since after redoit() we reversed the timeOffset
+ redoit();
+}
+
+
+RenumberDives::RenumberDives(const QVector<QPair<dive *, int>> &divesToRenumberIn) : divesToRenumber(divesToRenumberIn)
+{
+ setText(tr("renumber %n dive(s)", "", divesToRenumber.count()));
+}
+
+void RenumberDives::undoit()
+{
+ renumberDives(divesToRenumber);
+ mark_divelist_changed(true);
+}
+
+bool RenumberDives::workToBeDone()
+{
+ return !divesToRenumber.isEmpty();
+}
+
+void RenumberDives::redoit()
+{
+ // Redo and undo do the same thing!
+ undoit();
+}
+
+bool TripBase::workToBeDone()
+{
+ return !divesToMove.divesToMove.empty();
+}
+
+void TripBase::redoit()
+{
+ moveDivesBetweenTrips(divesToMove);
+
+ mark_divelist_changed(true);
+}
+
+void TripBase::undoit()
+{
+ // Redo and undo do the same thing!
+ redoit();
+}
+
+RemoveDivesFromTrip::RemoveDivesFromTrip(const QVector<dive *> &divesToRemove)
+{
+ setText(tr("remove %n dive(s) from trip", "", divesToRemove.size()));
+ divesToMove.divesToMove.reserve(divesToRemove.size());
+ for (dive *d: divesToRemove)
+ divesToMove.divesToMove.push_back( {d, nullptr} );
+}
+
+RemoveAutogenTrips::RemoveAutogenTrips()
+{
+ setText(tr("remove autogenerated trips"));
+ // TODO: don't touch core-innards directly
+ int i;
+ struct dive *dive;
+ for_each_dive(i, dive) {
+ if (dive->divetrip && dive->divetrip->autogen)
+ divesToMove.divesToMove.push_back( {dive, nullptr} );
+ }
+}
+
+AddDivesToTrip::AddDivesToTrip(const QVector<dive *> &divesToAddIn, dive_trip *trip)
+{
+ setText(tr("add %n dives to trip", "", divesToAddIn.size()));
+ for (dive *d: divesToAddIn)
+ divesToMove.divesToMove.push_back( {d, trip} );
+}
+
+CreateTrip::CreateTrip(const QVector<dive *> &divesToAddIn)
+{
+ setText(tr("create trip"));
+
+ if (divesToAddIn.isEmpty())
+ return;
+
+ dive_trip *trip = create_trip_from_dive(divesToAddIn[0]);
+ divesToMove.tripsToAdd.emplace_back(trip);
+ for (dive *d: divesToAddIn)
+ divesToMove.divesToMove.push_back( {d, trip} );
+}
+
+AutogroupDives::AutogroupDives()
+{
+ setText(tr("autogroup dives"));
+
+ dive_trip *trip;
+ bool alloc;
+ int from, to;
+ for(int i = 0; (trip = get_dives_to_autogroup(i, &from, &to, &alloc)) != NULL; i = to) {
+ // If this is an allocated trip, take ownership
+ if (alloc)
+ divesToMove.tripsToAdd.emplace_back(trip);
+ for (int j = from; j < to; ++j)
+ divesToMove.divesToMove.push_back( { get_dive(j), trip } );
+ }
+}
+
+MergeTrips::MergeTrips(dive_trip *trip1, dive_trip *trip2)
+{
+ if (trip1 == trip2)
+ return;
+ dive_trip *newTrip = combine_trips_create(trip1, trip2);
+ divesToMove.tripsToAdd.emplace_back(newTrip);
+ for (dive *d = trip1->dives; d; d = d->next)
+ divesToMove.divesToMove.push_back( { d, newTrip } );
+ for (dive *d = trip2->dives; d; d = d->next)
+ divesToMove.divesToMove.push_back( { d, newTrip } );
+}
+
+SplitDives::SplitDives(dive *d, duration_t time)
+{
+ setText(tr("split dive"));
+
+ // Split the dive
+ dive *new1, *new2;
+ int idx = time.seconds < 0 ?
+ split_dive_dont_insert(d, &new1, &new2) :
+ split_dive_at_time_dont_insert(d, time, &new1, &new2);
+
+ // If this didn't work, simply return. Empty arrays indicate that nothing is to be done.
+ if (idx < 0)
+ return;
+
+ // Currently, the core code selects the dive -> this is not what we want, as
+ // we manually manage the selection post-command.
+ // TODO: Reset selection in core.
+ new1->selected = false;
+ new2->selected = false;
+
+ diveToSplit.push_back(d);
+ splitDives.resize(2);
+ splitDives[0].dive.reset(new1);
+ splitDives[0].trip = d->divetrip;
+ splitDives[0].idx = idx;
+ splitDives[1].dive.reset(new2);
+ splitDives[1].trip = d->divetrip;
+ splitDives[1].idx = idx + 1;
+}
+
+bool SplitDives::workToBeDone()
+{
+ return !diveToSplit.empty();
+}
+
+void SplitDives::redoit()
+{
+ divesToUnsplit = addDives(splitDives);
+ unsplitDive = removeDives(diveToSplit);
+ mark_divelist_changed(true);
+
+ // Select split dives and make first dive current
+ restoreSelection(divesToUnsplit, divesToUnsplit[0]);
+}
+
+void SplitDives::undoit()
+{
+ // Note: reverse order with respect to redoit()
+ diveToSplit = addDives(unsplitDive);
+ splitDives = removeDives(divesToUnsplit);
+ mark_divelist_changed(true);
+
+ // Select unsplit dive and make it current
+ restoreSelection(diveToSplit, diveToSplit[0] );
+}
+
+MergeDives::MergeDives(const QVector <dive *> &dives)
+{
+ setText(tr("merge dive"));
+
+ // Just a safety check - if there's not two or more dives - do nothing
+ // The caller should have made sure that this doesn't happen.
+ if (dives.count() < 2) {
+ qWarning() << "Merging less than two dives";
+ return;
+ }
+
+ dive_trip *preferred_trip;
+ OwningDivePtr d(merge_dives(dives[0], dives[1], dives[1]->when - dives[0]->when, false, &preferred_trip));
+
+ // Currently, the core code selects the dive -> this is not what we want, as
+ // we manually manage the selection post-command.
+ // TODO: Remove selection code from core.
+ d->selected = false;
+
+ // Set the preferred dive trip, so that for subsequent merges the better trip can be selected
+ d->divetrip = preferred_trip;
+ for (int i = 2; i < dives.count(); ++i) {
+ d.reset(merge_dives(d.get(), dives[i], dives[i]->when - d->when, false, &preferred_trip));
+ // Set the preferred dive trip, so that for subsequent merges the better trip can be selected
+ d->divetrip = preferred_trip;
+ }
+
+ // We got our preferred trip, so now the reference can be deleted from the newly generated dive
+ d->divetrip = nullptr;
+
+ // The merged dive gets the number of the first dive
+ d->number = dives[0]->number;
+
+ // We will only renumber the remaining dives if the joined dives are consecutive.
+ // Otherwise all bets are off concerning what the user wanted and doing nothing seems
+ // like the best option.
+ int idx = get_divenr(dives[0]);
+ int num = dives.count();
+ if (idx < 0 || idx + num > dive_table.nr) {
+ // It was the callers responsibility to pass only known dives.
+ // Something is seriously wrong - give up.
+ qWarning() << "Merging unknown dives";
+ return;
+ }
+ // std::equal compares two ranges. The parameters are (begin_range1, end_range1, begin_range2).
+ // Here, we can compare C-arrays, because QVector guarantees contiguous storage.
+ if (std::equal(&dives[0], &dives[0] + num, &dive_table.dives[idx]) &&
+ dives[0]->number && dives.last()->number && dives[0]->number < dives.last()->number) {
+ // We have a consecutive set of dives. Rename all following dives according to the
+ // number of erased dives. This considers that there might be missing numbers.
+ // Comment copied from core/divelist.c:
+ // So if you had a dive list 1 3 6 7 8, and you
+ // merge 1 and 3, the resulting numbered list will
+ // be 1 4 5 6, because we assume that there were
+ // some missing dives (originally dives 4 and 5),
+ // that now will still be missing (dives 2 and 3
+ // in the renumbered world).
+ //
+ // Obviously the normal case is that everything is
+ // consecutive, and the difference will be 1, so the
+ // above example is not supposed to be normal.
+ int diff = dives.last()->number - dives[0]->number;
+ divesToRenumber.reserve(dive_table.nr - idx - num);
+ int previousnr = dives[0]->number;
+ for (int i = idx + num; i < dive_table.nr; ++i) {
+ int newnr = dive_table.dives[i]->number - diff;
+
+ // Stop renumbering if stuff isn't in order (see also core/divelist.c)
+ if (newnr <= previousnr)
+ break;
+ divesToRenumber.append(QPair<dive *,int>(dive_table.dives[i], newnr));
+ previousnr = newnr;
+ }
+ }
+
+ mergedDive.resize(1);
+ mergedDive[0].dive = std::move(d);
+ mergedDive[0].idx = get_divenr(dives[0]);
+ mergedDive[0].trip = preferred_trip;
+ divesToMerge = dives.toStdVector();
+}
+
+bool MergeDives::workToBeDone()
+{
+ return !mergedDive.empty();
+}
+
+void MergeDives::redoit()
+{
+ renumberDives(divesToRenumber);
+ diveToUnmerge = addDives(mergedDive);
+ unmergedDives = removeDives(divesToMerge);
+
+ // Select merged dive and make it current
+ restoreSelection(diveToUnmerge, diveToUnmerge[0]);
+}
+
+void MergeDives::undoit()
+{
+ divesToMerge = addDives(unmergedDives);
+ mergedDive = removeDives(diveToUnmerge);
+ renumberDives(divesToRenumber);
+
+ // Select unmerged dives and make first one current
+ restoreSelection(divesToMerge, divesToMerge[0]);
+}
+
+} // namespace Command
diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp
index 766d32414..e5974a834 100644
--- a/desktop-widgets/tab-widgets/maintab.cpp
+++ b/desktop-widgets/tab-widgets/maintab.cpp
@@ -26,6 +26,7 @@
#include "core/subsurface-string.h"
#include "core/gettextfromc.h"
#include "desktop-widgets/locationinformation.h"
+#include "desktop-widgets/undocommands.h"
#include "TabDiveExtraInfo.h"
#include "TabDiveInformation.h"
@@ -788,23 +789,30 @@ void MainTab::acceptChanges()
hideMessage();
ui.equipmentTab->setEnabled(true);
if (editMode == ADD) {
- // We need to add the dive we just created to the dive list and select it.
- // Easy, right?
- struct dive *added_dive = clone_dive(&displayed_dive);
- record_dive(added_dive);
- addedId = added_dive->id;
// make sure that the dive site is handled as well
- updateDiveSite(ui.location->currDiveSiteUuid(), added_dive);
+ updateDiveSite(ui.location->currDiveSiteUuid(), &displayed_dive);
- // unselect everything as far as the UI is concerned and select the new
- // dive - we'll have to undo/redo this later after we resort the dive_table
- // but we need the dive selected for the middle part of this function - this
- // way we can reuse the code used for editing dives
- MainWindow::instance()->dive_list()->unselectDives();
- selected_dive = get_divenr(added_dive);
- amount_selected = 1;
- // finally, make sure we get the tags
- saveTags();
+ UndoAddDive *undoCommand = new UndoAddDive(&displayed_dive);
+ MainWindow::instance()->undoStack->push(undoCommand);
+
+ editMode = NONE;
+ MainWindow::instance()->exitEditState();
+ cylindersModel->changed = false;
+ weightModel->changed = false;
+ MainWindow::instance()->setEnabledToolbar(true);
+ acceptingEdit = false;
+ ui.editDiveSiteButton->setEnabled(!ui.location->text().isEmpty());
+ emit addDiveFinished();
+ MainWindow::instance()->dive_list()->reload(DiveTripModel::CURRENT, true);
+ DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING);
+ int scrolledBy = MainWindow::instance()->dive_list()->verticalScrollBar()->sliderPosition();
+ MainWindow::instance()->dive_list()->verticalScrollBar()->setSliderPosition(scrolledBy);
+ MainWindow::instance()->dive_list()->setFocus();
+ resetPallete();
+ saveTags(QVector<dive *>{ &displayed_dive });
+ displayed_dive.divetrip = nullptr; // Should not be necessary, just in case!
+ Command::addDive(&displayed_dive, autogroup, true);
+ return;
} else if (MainWindow::instance() && MainWindow::instance()->dive_list()->selectedTrips().count() == 1) {
/* now figure out if things have changed */
if (displayedTrip.notes && !same_string(displayedTrip.notes, currentTrip->notes)) {
@@ -957,7 +965,7 @@ void MainTab::acceptChanges()
current_dive->divetrip->when = current_dive->when;
find_new_trip_start_time(current_dive->divetrip);
}
- if (editMode == ADD || editMode == MANUALLY_ADDED_DIVE) {
+ if (editMode == MANUALLY_ADDED_DIVE) {
// we just added or edited the dive, let fixup_dive() make
// sure we get the max. depth right
current_dive->maxdepth.mm = current_dc->maxdepth.mm = 0;
@@ -969,7 +977,7 @@ void MainTab::acceptChanges()
}
int scrolledBy = MainWindow::instance()->dive_list()->verticalScrollBar()->sliderPosition();
resetPallete();
- if (editMode == ADD || editMode == MANUALLY_ADDED_DIVE) {
+ if (editMode == MANUALLY_ADDED_DIVE) {
// since a newly added dive could be in the middle of the dive_table we need
// to resort the dive list and make sure the newly added dive gets selected again
sort_table(&dive_table);
@@ -980,7 +988,6 @@ void MainTab::acceptChanges()
editMode = NONE;
MainWindow::instance()->refreshDisplay();
MainWindow::instance()->graphics()->replot();
- emit addDiveFinished();
} else {
editMode = NONE;
if (do_replot)
diff --git a/desktop-widgets/undocommands.cpp b/desktop-widgets/undocommands.cpp
index d58ac7a4b..668a84f9e 100644
--- a/desktop-widgets/undocommands.cpp
+++ b/desktop-widgets/undocommands.cpp
@@ -1,10 +1,83 @@
// SPDX-License-Identifier: GPL-2.0
#include "desktop-widgets/undocommands.h"
#include "desktop-widgets/mainwindow.h"
+#include "desktop-widgets/divelistview.h"
#include "core/divelist.h"
#include "core/subsurface-string.h"
#include "core/gettextfromc.h"
+// This helper function removes a dive, takes ownership of the dive and adds it to a DiveToAdd structure.
+// It is crucial that dives are added in reverse order of deletion, so the the indices are correctly
+// set and that the trips are added before they are used!
+static DiveToAdd removeDive(struct dive *d)
+{
+ DiveToAdd res;
+ res.idx = get_divenr(d);
+ if (res.idx < 0)
+ qWarning() << "Deletion of unknown dive!";
+
+ // remove dive from trip - if this is the last dive in the trip
+ // remove the whole trip.
+ res.trip = unregister_dive_from_trip(d, false);
+ if (res.trip && res.trip->nrdives == 0) {
+ unregister_trip(res.trip); // Remove trip from backend
+ res.tripToAdd.reset(res.trip); // Take ownership of trip
+ }
+
+ res.dive.reset(unregister_dive(res.idx)); // Remove dive from backend
+ return res;
+}
+
+// This helper function adds a dive and returns ownership to the backend. It may also add a dive trip.
+// It is crucial that dives are added in reverse order of deletion (see comment above)!
+// Returns pointer to added dive (which is owned by the backend!)
+static dive *addDive(DiveToAdd &d)
+{
+ if (d.tripToAdd) {
+ dive_trip *t = d.tripToAdd.release(); // Give up ownership of trip
+ insert_trip(&t); // Return ownership to backend
+ }
+ if (d.trip)
+ add_dive_to_trip(d.dive.get(), d.trip);
+ dive *res = d.dive.release(); // Give up ownership of dive
+ add_single_dive(d.idx, res); // Return ownership to backend
+ return res;
+}
+
+UndoAddDive::UndoAddDive(dive *d)
+{
+ setText(gettextFromC::tr("add dive"));
+ // TODO: handle tags
+ //saveTags();
+ d->maxdepth.mm = 0;
+ fixup_dive(d);
+ diveToAdd.trip = d->divetrip;
+ d->divetrip = nullptr;
+ diveToAdd.idx = dive_get_insertion_index(d);
+ d->number = get_dive_nr_at_idx(diveToAdd.idx);
+ diveToAdd.dive.reset(clone_dive(d));
+}
+
+void UndoAddDive::redo()
+{
+ diveToRemove = addDive(diveToAdd);
+ mark_divelist_changed(true);
+
+ // Finally, do the UI stuff:
+ MainWindow::instance()->dive_list()->unselectDives();
+ MainWindow::instance()->dive_list()->selectDive(diveToAdd.idx, true);
+ MainWindow::instance()->refreshDisplay();
+}
+
+void UndoAddDive::undo()
+{
+ // Simply remove the dive that was previously added
+ diveToAdd = removeDive(diveToRemove);
+
+ // Finally, do the UI stuff:
+ MainWindow::instance()->refreshDisplay();
+}
+
UndoDeleteDive::UndoDeleteDive(const QVector<struct dive*> &divesToDeleteIn) : divesToDelete(divesToDeleteIn)
{
setText(tr("delete %n dive(s)", "", divesToDelete.size()));
@@ -12,19 +85,9 @@ UndoDeleteDive::UndoDeleteDive(const QVector<struct dive*> &divesToDeleteIn) : d
void UndoDeleteDive::undo()
{
- // first bring back the trip(s)
- for (auto &trip: tripsToAdd) {
- dive_trip *t = trip.release(); // Give up ownership
- insert_trip(&t); // Return ownership to backend
- }
- tripsToAdd.clear();
+ for (auto it = divesToAdd.rbegin(); it != divesToAdd.rend(); ++it)
+ divesToDelete.append(addDive(*it));
- for (DiveToAdd &d: divesToAdd) {
- if (d.trip)
- add_dive_to_trip(d.dive.get(), d.trip);
- divesToDelete.append(d.dive.get()); // Delete dive on redo
- add_single_dive(d.idx, d.dive.release()); // Return ownership to backend
- }
mark_divelist_changed(true);
divesToAdd.clear();
@@ -34,24 +97,9 @@ void UndoDeleteDive::undo()
void UndoDeleteDive::redo()
{
- for (dive *d: divesToDelete) {
- int idx = get_divenr(d);
- if (idx < 0) {
- qWarning() << "Deletion of unknown dive!";
- continue;
- }
- // remove dive from trip - if this is the last dive in the trip
- // remove the whole trip.
- dive_trip *trip = unregister_dive_from_trip(d, false);
- if (trip && trip->nrdives == 0) {
- unregister_trip(trip); // Remove trip from backend
- tripsToAdd.emplace_back(trip); // Take ownership of trip
- }
+ for (dive *d: divesToDelete)
+ divesToAdd.push_back(removeDive(d));
- unregister_dive(idx); // Remove dive from backend
- divesToAdd.push_back({ OwningDivePtr(d), trip, idx });
- // Take ownership for dive
- }
divesToDelete.clear();
mark_divelist_changed(true);
diff --git a/desktop-widgets/undocommands.h b/desktop-widgets/undocommands.h
index bbc1f05bd..4c4073454 100644
--- a/desktop-widgets/undocommands.h
+++ b/desktop-widgets/undocommands.h
@@ -148,6 +148,29 @@ struct TripDeleter {
typedef std::unique_ptr<dive, DiveDeleter> OwningDivePtr;
typedef std::unique_ptr<dive_trip, TripDeleter> OwningTripPtr;
+// This helper structure describes a dive that we want to add.
+// Potentially it also adds a trip (if deletion of the dive resulted in deletion of the trip)
+struct DiveToAdd {
+ OwningDivePtr dive; // Dive to add
+ OwningTripPtr tripToAdd; // Not-null if we also have to add a dive
+ dive_trip *trip; // Trip the dive belongs to, may be null
+ int idx; // Position in divelist
+};
+
+class UndoAddDive : public QUndoCommand {
+public:
+ UndoAddDive(dive *dive); // Warning: old dive will be erased (moved in C++-speak)!
+private:
+ void undo() override;
+ void redo() override;
+
+ // For redo
+ DiveToAdd diveToAdd;
+
+ // For undo
+ dive *diveToRemove;
+};
+
class UndoDeleteDive : public QUndoCommand {
Q_DECLARE_TR_FUNCTIONS(Command)
public:
@@ -159,12 +182,6 @@ private:
// 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;
};