diff options
author | Berthold Stoeger <bstoeger@mail.tuwien.ac.at> | 2018-07-21 18:28:33 +0200 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2018-10-11 16:22:27 -0700 |
commit | 014c04f8bd30740e7711f3b3a01619fd27b5b613 (patch) | |
tree | 0f7e505cc36e377bed4e24fb6f94adc93db22fb2 /desktop-widgets | |
parent | 302f6adb79681da3fe53336f1e4c7525f46fd47d (diff) | |
download | subsurface-014c04f8bd30740e7711f3b3a01619fd27b5b613.tar.gz |
Undo: implement rudimentary support for undo of dive-merging
For this, an output-parameter was added to the backend merge_dives()
function. When non-zero, instead of adding the merged dive to
the preferred trip, the preferred trip is returned to the caller.
Since the new UndoObject, just like the delete-dives UndoObject,
needs to remove/readd a set of dives, the corresponding functionality
was split-off in a helper function.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Diffstat (limited to 'desktop-widgets')
-rw-r--r-- | desktop-widgets/divelistview.cpp | 33 | ||||
-rw-r--r-- | desktop-widgets/tab-widgets/maintab.cpp | 3 | ||||
-rw-r--r-- | desktop-widgets/undocommands.cpp | 163 | ||||
-rw-r--r-- | desktop-widgets/undocommands.h | 23 |
4 files changed, 194 insertions, 28 deletions
diff --git a/desktop-widgets/divelistview.cpp b/desktop-widgets/divelistview.cpp index 938a4e3e5..e6cbbc78f 100644 --- a/desktop-widgets/divelistview.cpp +++ b/desktop-widgets/divelistview.cpp @@ -611,21 +611,32 @@ static bool can_merge(const struct dive *a, const struct dive *b, enum asked_use void DiveListView::mergeDives() { int i; - struct dive *dive, *maindive = NULL; + struct dive *d; enum asked_user have_asked = NOTYET; - for_each_dive (i, dive) { - if (dive->selected) { - if (!can_merge(maindive, dive, &have_asked)) { - maindive = dive; - } else { - maindive = merge_two_dives(maindive, dive); - i--; // otherwise we skip a dive in the freshly changed list - } + // Collect a vector of batches of dives to merge (i.e. a vector of vector of dives) + QVector<QVector<dive *>> merge_batches; + QVector<dive *> current_batch; + for_each_dive (i, d) { + if (!d->selected) + continue; + if (current_batch.empty()) { + current_batch.append(d); + } else if (can_merge(current_batch.back(), d, &have_asked)) { + current_batch.append(d); + } else { + if (current_batch.count() > 1) + merge_batches.append(current_batch); + current_batch.clear(); } } - MainWindow::instance()->refreshProfile(); - MainWindow::instance()->refreshDisplay(); + if (current_batch.count() > 1) + merge_batches.append(current_batch); + + for (const QVector<dive *> &batch: merge_batches) { + UndoMergeDives *undoCommand = new UndoMergeDives(batch); + MainWindow::instance()->undoStack->push(undoCommand); + } } void DiveListView::splitDives() diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index e5974a834..66ed33c0d 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -809,9 +809,8 @@ void MainTab::acceptChanges() MainWindow::instance()->dive_list()->verticalScrollBar()->setSliderPosition(scrolledBy); MainWindow::instance()->dive_list()->setFocus(); resetPallete(); - saveTags(QVector<dive *>{ &displayed_dive }); + saveTags(); 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 */ diff --git a/desktop-widgets/undocommands.cpp b/desktop-widgets/undocommands.cpp index 45c796af8..9a56b4e9d 100644 --- a/desktop-widgets/undocommands.cpp +++ b/desktop-widgets/undocommands.cpp @@ -44,6 +44,50 @@ static dive *addDive(DiveToAdd &d) 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. +static std::vector<DiveToAdd> removeDives(std::vector<dive *> &divesToDelete) +{ + std::vector<DiveToAdd> res; + res.reserve(divesToDelete.size()); + + for (dive *d: divesToDelete) + res.push_back(removeDive(d)); + divesToDelete.clear(); + + 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. +static std::vector<dive *> addDives(std::vector<DiveToAdd> &divesToAdd) +{ + std::vector<dive *> res; + res.reserve(divesToAdd.size()); + + for (auto it = divesToAdd.rbegin(); it != divesToAdd.rend(); ++it) + res.push_back(addDive(*it)); + divesToAdd.clear(); + + 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 "UndoCommands". +static void renumberDives(QVector<QPair<int, int>> &divesToRenumber) +{ + for (auto &pair: divesToRenumber) { + dive *d = get_dive_by_uniq_id(pair.first); + if (!d) + continue; + std::swap(d->number, pair.second); + } +} + UndoAddDive::UndoAddDive(dive *d) { setText(gettextFromC::tr("add dive")); @@ -78,18 +122,16 @@ void UndoAddDive::undo() MainWindow::instance()->refreshDisplay(); } -UndoDeleteDive::UndoDeleteDive(const QVector<struct dive*> &divesToDeleteIn) : divesToDelete(divesToDeleteIn) +UndoDeleteDive::UndoDeleteDive(const QVector<struct dive*> &divesToDeleteIn) : divesToDelete(divesToDeleteIn.toStdVector()) { setText(tr("delete %n dive(s)", "", divesToDelete.size())); } void UndoDeleteDive::undo() { - for (auto it = divesToAdd.rbegin(); it != divesToAdd.rend(); ++it) - divesToDelete.append(addDive(*it)); + divesToDelete = addDives(divesToAdd); mark_divelist_changed(true); - divesToAdd.clear(); // Finally, do the UI stuff: MainWindow::instance()->refreshDisplay(); @@ -97,10 +139,7 @@ void UndoDeleteDive::undo() void UndoDeleteDive::redo() { - for (dive *d: divesToDelete) - divesToAdd.push_back(removeDive(d)); - - divesToDelete.clear(); + divesToAdd = removeDives(divesToDelete); mark_divelist_changed(true); // Finally, do the UI stuff: @@ -145,12 +184,7 @@ UndoRenumberDives::UndoRenumberDives(const QVector<QPair<int, int>> &divesToRenu void UndoRenumberDives::undo() { - for (auto &pair: divesToRenumber) { - dive *d = get_dive_by_uniq_id(pair.first); - if (!d) - continue; - std::swap(d->number, pair.second); - } + renumberDives(divesToRenumber); mark_divelist_changed(true); // Finally, do the UI stuff: @@ -260,3 +294,104 @@ void UndoSplitDives::undo() MainWindow::instance()->refreshDisplay(); MainWindow::instance()->refreshProfile(); } + +UndoMergeDives::UndoMergeDives(const QVector <dive *> &dives) +{ + setText(gettextFromC::tr("merge dive")); + + // We start in redo mode + diveToUnmerge = nullptr; + + // 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)); + + // 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<int,int>(dive_table.dives[i]->id, newnr)); + previousnr = newnr; + } + } + + mergedDive.dive = std::move(d); + mergedDive.idx = get_divenr(dives[0]); + mergedDive.trip = preferred_trip; + divesToMerge = dives.toStdVector(); +} + +void UndoMergeDives::redo() +{ + renumberDives(divesToRenumber); + diveToUnmerge = addDive(mergedDive); + unmergedDives = removeDives(divesToMerge); + + // Finally, do the UI stuff: + MainWindow::instance()->refreshDisplay(); + MainWindow::instance()->refreshProfile(); +} + +void UndoMergeDives::undo() +{ + divesToMerge = addDives(unmergedDives); + mergedDive = removeDive(diveToUnmerge); + renumberDives(divesToRenumber); + + // Finally, do the UI stuff: + MainWindow::instance()->refreshDisplay(); + MainWindow::instance()->refreshProfile(); +} diff --git a/desktop-widgets/undocommands.h b/desktop-widgets/undocommands.h index c30ae5fae..128c62e51 100644 --- a/desktop-widgets/undocommands.h +++ b/desktop-widgets/undocommands.h @@ -180,7 +180,7 @@ private: void redo() override; // For redo - QVector<struct dive*> divesToDelete; + std::vector<struct dive*> divesToDelete; std::vector<OwningTripPtr> tripsToAdd; std::vector<DiveToAdd> divesToAdd; @@ -246,4 +246,25 @@ private: dive *divesToUnsplit[2]; }; +class UndoMergeDives : public QUndoCommand { +public: + UndoMergeDives(const QVector<dive *> &dives); +private: + void undo() override; + void redo() override; + + // For redo + // Add one and remove a batch of dives + DiveToAdd mergedDive; + std::vector<dive *> divesToMerge; + + // For undo + // Remove one and add a batch of dives + dive *diveToUnmerge; + std::vector<DiveToAdd> unmergedDives; + + // For undo and redo + QVector<QPair<int, int>> divesToRenumber; +}; + #endif // UNDOCOMMANDS_H |