Undo: implement rudimentary support for undo of dive-splitting

For this, the core functionality of the split_dive() and
split_dive_at_time() functions were split out into new
split_dive_dont_insert() and split_dive_at_time_dont_insert(),
which do not add the new dives to the log. Thus, the undo-command
can take ownership of these dives, without having to remove them
first.

The split-dive functionality is temporarily made desktop-only
until mobile also supports "UndoObjects".

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2018-07-20 20:26:06 +02:00 committed by Dirk Hohndel
parent 12df9faaa2
commit 302f6adb79
6 changed files with 145 additions and 36 deletions

View file

@ -3388,7 +3388,7 @@ struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer
}
// copy_dive(), but retaining the new ID for the copied dive
static struct dive *create_new_copy(struct dive *from)
static struct dive *create_new_copy(const struct dive *from)
{
struct dive *to = alloc_dive();
int id;
@ -3450,9 +3450,11 @@ static void force_fixup_dive(struct dive *d)
/*
* Split a dive that has a surface interval from samples 'a' to 'b'
* into two dives.
* into two dives, but don't add them to the log yet.
* Returns the nr of the old dive or <0 on failure.
* On success, the newly allocated dives are returned in out1 and out2.
*/
static int split_dive_at(struct dive *dive, int a, int b)
static int split_dive_at(const struct dive *dive, int a, int b, struct dive **out1, struct dive **out2)
{
int i, nr;
uint32_t t;
@ -3462,15 +3464,16 @@ static int split_dive_at(struct dive *dive, int a, int b)
/* if we can't find the dive in the dive list, don't bother */
if ((nr = get_divenr(dive)) < 0)
return 0;
return -1;
/* Splitting should leave at least 3 samples per dive */
if (a < 3 || b > dive->dc.samples - 4)
return 0;
return -1;
/* We're not trying to be efficient here.. */
d1 = create_new_copy(dive);
d2 = create_new_copy(dive);
d1->divetrip = d2->divetrip = 0;
/* now unselect the first first segment so we don't keep all
* dives selected by mistake. But do keep the second one selected
@ -3544,15 +3547,6 @@ static int split_dive_at(struct dive *dive, int a, int b)
force_fixup_dive(d1);
force_fixup_dive(d2);
if (dive->divetrip) {
d1->divetrip = d2->divetrip = 0;
add_dive_to_trip(d1, dive->divetrip);
add_dive_to_trip(d2, dive->divetrip);
}
delete_single_dive(nr);
add_single_dive(nr, d1);
/*
* Was the dive numbered? If it was the last dive, then we'll
* increment the dive number for the tail part that we split off.
@ -3564,16 +3558,28 @@ static int split_dive_at(struct dive *dive, int a, int b)
else
d2->number = 0;
}
add_single_dive(nr + 1, d2);
mark_divelist_changed(true);
return 1;
*out1 = d1;
*out2 = d2;
return nr;
}
static void finish_split(int nr, struct dive *old, struct dive *d1, struct dive *d2)
{
if (old->divetrip) {
add_dive_to_trip(d1, old->divetrip);
add_dive_to_trip(d2, old->divetrip);
}
delete_single_dive(nr);
add_single_dive(nr, d1);
add_single_dive(nr + 1, d2);
}
/* in freedive mode we split for as little as 10 seconds on the surface,
* otherwise we use a minute */
static bool should_split(struct divecomputer *dc, int t1, int t2)
static bool should_split(const struct divecomputer *dc, int t1, int t2)
{
int threshold = dc->divemode == FREEDIVE ? 10 : 60;
@ -3590,14 +3596,14 @@ static bool should_split(struct divecomputer *dc, int t1, int t2)
*
* In other words, this is a (simplified) reversal of the dive merging.
*/
int split_dive(struct dive *dive)
int split_dive_dont_insert(const struct dive *dive, struct dive **new1, struct dive **new2)
{
int i;
int at_surface, surface_start;
struct divecomputer *dc;
const struct divecomputer *dc;
if (!dive)
return 0;
return -1;
dc = &dive->dc;
surface_start = 0;
@ -3627,25 +3633,43 @@ int split_dive(struct dive *dive)
if (!should_split(dc, dc->sample[surface_start].time.seconds, sample[-1].time.seconds))
continue;
return split_dive_at(dive, surface_start, i-1);
return split_dive_at(dive, surface_start, i-1, new1, new2);
}
return 0;
return -1;
}
void split_dive_at_time(struct dive *dive, duration_t time)
void split_dive(struct dive *dive)
{
int nr;
struct dive *new1, *new2;
if ((nr = split_dive_dont_insert(dive, &new1, &new2)) >= 0)
finish_split(nr, dive, new1, new2);
}
int split_dive_at_time_dont_insert(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2)
{
int i = 0;
struct sample *sample = dive->dc.sample;
if (!dive)
return;
return -1;
while(sample->time.seconds < time.seconds) {
++sample;
++i;
if (dive->dc.samples == i)
return;
return -1;
}
split_dive_at(dive, i, i - 1);
return split_dive_at(dive, i, i - 1, new1, new2);
}
void split_dive_at_time(struct dive *dive, duration_t time)
{
int nr;
struct dive *new1, *new2;
if ((nr = split_dive_at_time_dont_insert(dive, time, &new1, &new2)) >= 0)
finish_split(nr, dive, new1, new2);
}
/*

View file

@ -569,7 +569,9 @@ extern void fixup_dc_duration(struct divecomputer *dc);
extern int dive_getUniqID();
extern unsigned int dc_airtemp(const struct divecomputer *dc);
extern unsigned int dc_watertemp(const struct divecomputer *dc);
extern int split_dive(struct dive *);
extern int split_dive_dont_insert(const struct dive *dive, struct dive **new1, struct dive **new2);
extern void split_dive(struct dive *);
extern int split_dive_at_time_dont_insert(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2);
extern void split_dive_at_time(struct dive *dive, duration_t time);
extern struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded);
extern struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded);

View file

@ -633,12 +633,16 @@ void DiveListView::splitDives()
int i;
struct dive *dive;
// Let's collect the dives to be split first, so that we don't catch newly inserted dives!
QVector<struct dive *> dives;
for_each_dive (i, dive) {
if (dive->selected)
split_dive(dive);
dives.append(dive);
}
for (struct dive *d: dives) {
UndoSplitDives *undoCommand = new UndoSplitDives(d, duration_t{-1});
MainWindow::instance()->undoStack->push(undoCommand);
}
MainWindow::instance()->refreshProfile();
MainWindow::instance()->refreshDisplay();
}
void DiveListView::renumberDives()

View file

@ -180,6 +180,7 @@ void UndoRemoveDivesFromTrip::undo()
for (auto &pair: divesToAdd)
add_dive_to_trip(pair.first, pair.second);
divesToAdd.clear();
mark_divelist_changed(true);
// Finally, do the UI stuff:
MainWindow::instance()->refreshDisplay();
@ -204,3 +205,58 @@ void UndoRemoveDivesFromTrip::redo()
// Finally, do the UI stuff:
MainWindow::instance()->refreshDisplay();
}
UndoSplitDives::UndoSplitDives(dive *d, duration_t time)
{
setText(gettextFromC::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, reset pointers so that redo() and undo() do nothing
if (idx < 0) {
diveToSplit = nullptr;
divesToUnsplit[0] = divesToUnsplit[1];
return;
}
diveToSplit = d;
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;
}
void UndoSplitDives::redo()
{
if (!diveToSplit)
return;
divesToUnsplit[0] = addDive(splitDives[0]);
divesToUnsplit[1] = addDive(splitDives[1]);
unsplitDive = removeDive(diveToSplit);
mark_divelist_changed(true);
// Finally, do the UI stuff:
MainWindow::instance()->refreshDisplay();
MainWindow::instance()->refreshProfile();
}
void UndoSplitDives::undo()
{
if (!unsplitDive.dive)
return;
// Note: reverse order with respect to redo()
diveToSplit = addDive(unsplitDive);
splitDives[1] = removeDive(divesToUnsplit[1]);
splitDives[0] = removeDive(divesToUnsplit[0]);
mark_divelist_changed(true);
// Finally, do the UI stuff:
MainWindow::instance()->refreshDisplay();
MainWindow::instance()->refreshProfile();
}

View file

@ -227,4 +227,23 @@ private:
std::vector<OwningTripPtr> tripsToAdd;
};
class UndoSplitDives : public QUndoCommand {
public:
// If time is < 0, split at first surface interval
UndoSplitDives(dive *d, duration_t time);
private:
void undo() override;
void redo() override;
// For redo
// For each dive to split, we remove one from and put two dives into the backend
dive *diveToSplit;
DiveToAdd splitDives[2];
// For undo
// For each dive to unsplit, we remove two dives from and add one into the backend
DiveToAdd unsplitDive;
dive *divesToUnsplit[2];
};
#endif // UNDOCOMMANDS_H

View file

@ -23,6 +23,7 @@
#include "desktop-widgets/diveplanner.h"
#include "desktop-widgets/simplewidgets.h"
#include "desktop-widgets/divepicturewidget.h"
#include "desktop-widgets/undocommands.h"
#include "desktop-widgets/mainwindow.h"
#include "core/qthelper.h"
#include "core/gettextfromc.h"
@ -1675,16 +1676,19 @@ void ProfileWidget2::addSetpointChange()
void ProfileWidget2::splitDive()
{
#ifndef SUBSURFACE_MOBILE
// Make sure that this is an actual dive and we're not in add mode
dive *d = get_dive_by_uniq_id(displayed_dive.id);
if (!d)
return;
QAction *action = qobject_cast<QAction *>(sender());
QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint()));
duration_t time;
time.seconds = lrint(timeAxis->valueAt((scenePos)));
split_dive_at_time(&displayed_dive, time);
time.seconds = lrint(timeAxis->valueAt(scenePos));
UndoSplitDives *undoCommand = new UndoSplitDives(d, time);
MainWindow::instance()->undoStack->push(undoCommand);
emit updateDiveInfo(false);
mark_divelist_changed(true);
replot();
MainWindow::instance()->refreshProfile();
MainWindow::instance()->refreshDisplay();
#endif
}
void ProfileWidget2::changeGas()