summaryrefslogtreecommitdiffstats
path: root/desktop-widgets/command_divelist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'desktop-widgets/command_divelist.cpp')
-rw-r--r--desktop-widgets/command_divelist.cpp271
1 files changed, 228 insertions, 43 deletions
diff --git a/desktop-widgets/command_divelist.cpp b/desktop-widgets/command_divelist.cpp
index a75004bea..9cf61eff9 100644
--- a/desktop-widgets/command_divelist.cpp
+++ b/desktop-widgets/command_divelist.cpp
@@ -4,6 +4,7 @@
#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"
namespace Command {
@@ -42,8 +43,20 @@ void processByTrip(std::vector<std::pair<dive_trip *, dive *>> &dives, Function
// 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 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)
@@ -65,7 +78,7 @@ static DiveToAdd removeDive(struct dive *d)
// 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)
+dive *DiveListBase::addDive(DiveToAdd &d)
{
if (d.tripToAdd)
insert_trip_dont_merge(d.tripToAdd.release()); // Return ownership to backend
@@ -74,13 +87,18 @@ static dive *addDive(DiveToAdd &d)
dive *res = d.dive.release(); // Give up ownership of dive
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.
-static std::vector<DiveToAdd> removeDives(std::vector<dive *> &divesToDelete)
+std::vector<DiveToAdd> DiveListBase::removeDives(std::vector<dive *> &divesToDelete)
{
std::vector<DiveToAdd> res;
res.reserve(divesToDelete.size());
@@ -90,7 +108,7 @@ static std::vector<DiveToAdd> removeDives(std::vector<dive *> &divesToDelete)
divesToDelete.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.
+ // 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)
@@ -112,7 +130,7 @@ static std::vector<DiveToAdd> removeDives(std::vector<dive *> &divesToDelete)
// 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 *> DiveListBase::addDives(std::vector<DiveToAdd> &divesToAdd)
{
std::vector<dive *> res;
res.reserve(divesToAdd.size());
@@ -289,6 +307,154 @@ static void moveDivesBetweenTrips(DivesToTrip &dives)
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)
{
setText(tr("add dive"));
@@ -321,30 +487,31 @@ bool AddDive::workToBeDone()
return true;
}
-void AddDive::redo()
+void AddDive::redoit()
{
- auto marker = diveListNotifier.enterCommand();
+ // Remember selection so that we can undo it
+ selection = getDiveSelection();
+ currentDive = current_dive;
- int idx = divesToAdd[0].idx;
divesToRemove = addDives(divesToAdd);
mark_divelist_changed(true);
- // Finally, do the UI stuff:
- MainWindow::instance()->dive_list()->unselectDives();
- MainWindow::instance()->dive_list()->selectDive(idx, 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::undo()
+void AddDive::undoit()
{
- auto marker = diveListNotifier.enterCommand();
-
- // Simply remove the dive that was previously added
+ // 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);
@@ -360,18 +527,31 @@ bool DeleteDive::workToBeDone()
return !divesToDelete.empty();
}
-void DeleteDive::undo()
+void DeleteDive::undoit()
{
- auto marker = diveListNotifier.enterCommand();
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::redo()
+void DeleteDive::redoit()
{
- auto marker = diveListNotifier.enterCommand();
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);
}
@@ -381,9 +561,8 @@ ShiftTime::ShiftTime(const QVector<dive *> &changedDives, int amount)
setText(tr("shift time of %n dives", "", changedDives.count()));
}
-void ShiftTime::redo()
+void ShiftTime::redoit()
{
- auto marker = diveListNotifier.enterCommand();
for (dive *d: diveList)
d->when -= timeChanged;
@@ -413,10 +592,10 @@ bool ShiftTime::workToBeDone()
return !diveList.isEmpty();
}
-void ShiftTime::undo()
+void ShiftTime::undoit()
{
- // Same as redo(), since after redo() we reversed the timeOffset
- redo();
+ // Same as redoit(), since after redoit() we reversed the timeOffset
+ redoit();
}
@@ -425,9 +604,8 @@ RenumberDives::RenumberDives(const QVector<QPair<dive *, int>> &divesToRenumberI
setText(tr("renumber %n dive(s)", "", divesToRenumber.count()));
}
-void RenumberDives::undo()
+void RenumberDives::undoit()
{
- auto marker = diveListNotifier.enterCommand();
renumberDives(divesToRenumber);
mark_divelist_changed(true);
}
@@ -437,10 +615,10 @@ bool RenumberDives::workToBeDone()
return !divesToRenumber.isEmpty();
}
-void RenumberDives::redo()
+void RenumberDives::redoit()
{
// Redo and undo do the same thing!
- undo();
+ undoit();
}
bool TripBase::workToBeDone()
@@ -448,18 +626,17 @@ bool TripBase::workToBeDone()
return !divesToMove.divesToMove.empty();
}
-void TripBase::redo()
+void TripBase::redoit()
{
- auto marker = diveListNotifier.enterCommand();
moveDivesBetweenTrips(divesToMove);
mark_divelist_changed(true);
}
-void TripBase::undo()
+void TripBase::undoit()
{
// Redo and undo do the same thing!
- redo();
+ redoit();
}
RemoveDivesFromTrip::RemoveDivesFromTrip(const QVector<dive *> &divesToRemove)
@@ -540,7 +717,7 @@ SplitDives::SplitDives(dive *d, duration_t time)
split_dive_dont_insert(d, &new1, &new2) :
split_dive_at_time_dont_insert(d, time, &new1, &new2);
- // If this didn't work, reset pointers so that redo() and undo() do nothing
+ // If this didn't work, reset pointers as a mark that nothing is to be done.
if (idx < 0) {
diveToSplit = nullptr;
divesToUnsplit[0] = divesToUnsplit[1];
@@ -561,23 +738,27 @@ bool SplitDives::workToBeDone()
return !!diveToSplit;
}
-void SplitDives::redo()
+void SplitDives::redoit()
{
- auto marker = diveListNotifier.enterCommand();
divesToUnsplit[0] = addDive(splitDives[0]);
divesToUnsplit[1] = addDive(splitDives[1]);
unsplitDive = removeDive(diveToSplit);
mark_divelist_changed(true);
+
+ // Select split dives and make first dive current
+ restoreSelection(std::vector<dive *>{ divesToUnsplit[0], divesToUnsplit[1] }, divesToUnsplit[0]);
}
-void SplitDives::undo()
+void SplitDives::undoit()
{
- // Note: reverse order with respect to redo()
- auto marker = diveListNotifier.enterCommand();
+ // Note: reverse order with respect to redoit()
diveToSplit = addDive(unsplitDive);
splitDives[1] = removeDive(divesToUnsplit[1]);
splitDives[0] = removeDive(divesToUnsplit[0]);
mark_divelist_changed(true);
+
+ // Select unsplit dive and make it current
+ restoreSelection(std::vector<dive *>{ diveToSplit }, diveToSplit);
}
MergeDives::MergeDives(const QVector <dive *> &dives)
@@ -664,20 +845,24 @@ bool MergeDives::workToBeDone()
return !!mergedDive.dive;
}
-void MergeDives::redo()
+void MergeDives::redoit()
{
- auto marker = diveListNotifier.enterCommand();
renumberDives(divesToRenumber);
diveToUnmerge = addDive(mergedDive);
unmergedDives = removeDives(divesToMerge);
+
+ // Select merged dive and make it current
+ restoreSelection(std::vector<dive *>{ diveToUnmerge }, diveToUnmerge);
}
-void MergeDives::undo()
+void MergeDives::undoit()
{
- auto marker = diveListNotifier.enterCommand();
divesToMerge = addDives(unmergedDives);
mergedDive = removeDive(diveToUnmerge);
renumberDives(divesToRenumber);
+
+ // Select unmerged dives and make first one current
+ restoreSelection(divesToMerge, divesToMerge[0]);
}
} // namespace Command