Undo: implement split-out of dive computer

Allow splitting out a dive computer into a distinct dive. This
is realized by generating a base class from SplitDive.

This turned out to be more cumbersome than expected: we don't
know a-priori which of the split dives will come first. Since
the undo-command saves the indices where the dives will be insert,
these have to be calculated. This is an premature optimization,
which makes more pain than necessary. Let's remove it and
simply determine the insertion index when executing the command.

Original code by Linus Torvalds <torvalds@linux-foundation.org>.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Berthold Stoeger 2019-03-31 10:20:13 +02:00 committed by Dirk Hohndel
parent 8c9e5becb2
commit 145f70aab5
8 changed files with 169 additions and 38 deletions

View file

@ -596,7 +596,7 @@ void clear_dive(struct dive *d)
/* make a true copy that is independent of the source dive; /* make a true copy that is independent of the source dive;
* all data structures are duplicated, so the copy can be modified without * all data structures are duplicated, so the copy can be modified without
* any impact on the source */ * any impact on the source */
void copy_dive(const struct dive *s, struct dive *d) static void copy_dive_nodc(const struct dive *s, struct dive *d)
{ {
clear_dive(d); clear_dive(d);
/* simply copy things over, but then make actual copies of the /* simply copy things over, but then make actual copies of the
@ -614,12 +614,24 @@ void copy_dive(const struct dive *s, struct dive *d)
d->weightsystem[i].description = copy_string(s->weightsystem[i].description); d->weightsystem[i].description = copy_string(s->weightsystem[i].description);
STRUCTURED_LIST_COPY(struct picture, s->picture_list, d->picture_list, copy_pl); STRUCTURED_LIST_COPY(struct picture, s->picture_list, d->picture_list, copy_pl);
STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl); STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl);
}
void copy_dive(const struct dive *s, struct dive *d)
{
copy_dive_nodc(s, d);
// Copy the first dc explicitly, then the list of subsequent dc's // Copy the first dc explicitly, then the list of subsequent dc's
copy_dc(&s->dc, &d->dc); copy_dc(&s->dc, &d->dc);
STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc); STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc);
} }
static void copy_dive_onedc(const struct dive *s, const struct divecomputer *sdc, struct dive *d)
{
copy_dive_nodc(s, d);
copy_dc(sdc, &d->dc);
d->dc.next = NULL;
}
/* make a clone of the source dive and clean out the source dive; /* make a clone of the source dive and clean out the source dive;
* this is specifically so we can create a dive in the displayed_dive and then * this is specifically so we can create a dive in the displayed_dive and then
* add it to the divelist. * add it to the divelist.
@ -3497,6 +3509,7 @@ static void force_fixup_dive(struct dive *d)
* Split a dive that has a surface interval from samples 'a' to 'b' * Split a dive that has a surface interval from samples 'a' to 'b'
* into two dives, but don't add them to the log yet. * into two dives, but don't add them to the log yet.
* Returns the nr of the old dive or <0 on failure. * Returns the nr of the old dive or <0 on failure.
* Moreover, on failure both output dives are set to NULL.
* On success, the newly allocated dives are returned in out1 and out2. * On success, the newly allocated dives are returned in out1 and out2.
*/ */
static int split_dive_at(const struct dive *dive, int a, int b, struct dive **out1, struct dive **out2) static int split_dive_at(const struct dive *dive, int a, int b, struct dive **out1, struct dive **out2)
@ -3507,6 +3520,8 @@ static int split_dive_at(const struct dive *dive, int a, int b, struct dive **ou
struct divecomputer *dc1, *dc2; struct divecomputer *dc1, *dc2;
struct event *event, **evp; struct event *event, **evp;
*out1 = *out2 = NULL;
/* if we can't find the dive in the dive list, don't bother */ /* if we can't find the dive in the dive list, don't bother */
if ((nr = get_divenr(dive)) < 0) if ((nr = get_divenr(dive)) < 0)
return -1; return -1;
@ -4076,30 +4091,74 @@ unsigned int count_divecomputers(void)
return ret; return ret;
} }
/* always acts on the current dive */ static void delete_divecomputer(struct dive *d, int num)
void delete_current_divecomputer(void)
{ {
struct divecomputer *dc = current_dc; int i;
if (dc == &current_dive->dc) { /* Refuse to delete the last dive computer */
if (!d->dc.next)
return;
if (num == 0) {
/* remove the first one, so copy the second one in place of the first and free the second one /* remove the first one, so copy the second one in place of the first and free the second one
* be careful about freeing the no longer needed structures - since we copy things around we can't use free_dc()*/ * be careful about freeing the no longer needed structures - since we copy things around we can't use free_dc()*/
struct divecomputer *fdc = dc->next; struct divecomputer *fdc = d->dc.next;
free_dc_contents(dc); free_dc_contents(&d->dc);
memcpy(dc, fdc, sizeof(struct divecomputer)); memcpy(&d->dc, fdc, sizeof(struct divecomputer));
free(fdc); free(fdc);
} else { } else {
struct divecomputer *pdc = &current_dive->dc; struct divecomputer *pdc = &d->dc;
while (pdc->next != dc && pdc->next) for (i = 0; i < num - 1 && pdc; i++)
pdc = pdc->next; pdc = pdc->next;
if (pdc->next == dc) { if (pdc->next) {
struct divecomputer *dc = pdc->next;
pdc->next = dc->next; pdc->next = dc->next;
free_dc(dc); free_dc(dc);
} }
} }
if (dc_number == count_divecomputers())
/* If this is the currently displayed dive, we might have to adjust
* the currently displayed dive computer. */
if (d == current_dive && dc_number >= count_divecomputers())
dc_number--; dc_number--;
invalidate_dive_cache(current_dive); invalidate_dive_cache(d);
}
/* always acts on the current dive */
void delete_current_divecomputer(void)
{
delete_divecomputer(current_dive, dc_number);
}
/*
* This splits the dive src by dive computer. The first output dive has all
* dive computers except num, the second only dive computer num.
* The dives will not be associated with a trip.
* On error, both output parameters are set to NULL.
*/
void split_divecomputer(const struct dive *src, int num, struct dive **out1, struct dive **out2)
{
struct divecomputer *srcdc = get_dive_dc(current_dive, dc_number);
if (src && srcdc) {
// Copy the dive, but only using the selected dive computer
*out2 = alloc_dive();
copy_dive_onedc(src, srcdc, *out2);
// This will also make fixup_dive() to allocate a new dive id...
(*out2)->id = 0;
fixup_dive(*out2);
// Copy the dive with all dive computers
*out1 = create_new_copy(src);
// .. and then delete the split-out dive computer
delete_divecomputer(*out1, num);
(*out1)->divetrip = (*out2)->divetrip = NULL;
} else {
*out1 = *out2 = NULL;
}
} }
/* helper function to make it easier to work with our structures /* helper function to make it easier to work with our structures

View file

@ -440,6 +440,7 @@ extern timestamp_t dive_endtime(const struct dive *dive);
extern void make_first_dc(void); extern void make_first_dc(void);
extern unsigned int count_divecomputers(void); extern unsigned int count_divecomputers(void);
extern void delete_current_divecomputer(void); extern void delete_current_divecomputer(void);
void split_divecomputer(const struct dive *src, int num, struct dive **out1, struct dive **out2);
/* /*
* Iterate over each dive, with the first parameter being the index * Iterate over each dive, with the first parameter being the index

View file

@ -66,6 +66,11 @@ void splitDives(dive *d, duration_t time)
execute(new SplitDives(d, time)); execute(new SplitDives(d, time));
} }
void splitDiveComputer(dive *d, int dc_num)
{
execute(new SplitDiveComputer(d, dc_num));
}
void mergeDives(const QVector <dive *> &dives) void mergeDives(const QVector <dive *> &dives)
{ {
execute(new MergeDives(dives)); execute(new MergeDives(dives));

View file

@ -32,6 +32,7 @@ void createTrip(const QVector<dive *> &divesToAddIn);
void autogroupDives(); void autogroupDives();
void mergeTrips(dive_trip *trip1, dive_trip *trip2); void mergeTrips(dive_trip *trip1, dive_trip *trip2);
void splitDives(dive *d, duration_t time); void splitDives(dive *d, duration_t time);
void splitDiveComputer(dive *d, int dc_num);
void mergeDives(const QVector <dive *> &dives); void mergeDives(const QVector <dive *> &dives);
} // namespace Command } // namespace Command

View file

@ -8,6 +8,8 @@
#include "core/subsurface-qt/DiveListNotifier.h" #include "core/subsurface-qt/DiveListNotifier.h"
#include "qt-models/filtermodels.h" #include "qt-models/filtermodels.h"
#include <array>
namespace Command { namespace Command {
// Generally, signals are sent in batches per trip. To avoid writing the same loop // Generally, signals are sent in batches per trip. To avoid writing the same loop
@ -822,42 +824,51 @@ MergeTrips::MergeTrips(dive_trip *trip1, dive_trip *trip2)
divesToMove.divesToMove.push_back( { trip2->dives.dives[i], newTrip } ); divesToMove.divesToMove.push_back( { trip2->dives.dives[i], newTrip } );
} }
SplitDives::SplitDives(dive *d, duration_t time) // std::array<dive *, 2> is the same as struct *dive[2], with the fundamental
// difference that it can be returned from functions. Thus, this constructor
// can be chained with the result of a function.
SplitDivesBase::SplitDivesBase(dive *d, std::array<dive *, 2> newDives)
{ {
setText(tr("split dive")); // If either of the new dives is null, simply return. Empty arrays indicate that nothing is to be done.
if (!newDives[0] || !newDives[1])
// Split the dive
dive *new1, *new2;
int idx = time.seconds < 0 ?
split_dive(d, &new1, &new2) :
split_dive_at_time(d, time, &new1, &new2);
// If this didn't work, simply return. Empty arrays indicate that nothing is to be done.
if (idx < 0)
return; return;
// Currently, the core code selects the dive -> this is not what we want, as // Currently, the core code selects the dive -> this is not what we want, as
// we manually manage the selection post-command. // we manually manage the selection post-command.
// TODO: Reset selection in core. // TODO: Reset selection in core.
new1->selected = false; newDives[0]->selected = false;
new2->selected = false; newDives[1]->selected = false;
// Getting the insertion indexes correct is actually not easy, as we don't know
// which of the dives will land first when splitting out dive computers!
// TODO: We really should think about not storing the insertion index in the undo
// command, but calculating it on the fly on execution.
int idx_old = get_divenr(d);
int idx1 = dive_table_get_insertion_index(&dive_table, newDives[0]);
int idx2 = dive_table_get_insertion_index(&dive_table, newDives[1]);
if (idx1 > idx_old)
--idx1;
if (idx2 > idx_old)
--idx2;
if (idx1 == idx2 && dive_less_than(newDives[0], newDives[1]))
++idx2;
diveToSplit.push_back(d); diveToSplit.push_back(d);
splitDives.dives.resize(2); splitDives.dives.resize(2);
splitDives.dives[0].dive.reset(new1); splitDives.dives[0].dive.reset(newDives[0]);
splitDives.dives[0].trip = d->divetrip; splitDives.dives[0].trip = d->divetrip;
splitDives.dives[0].idx = idx; splitDives.dives[0].idx = idx1;
splitDives.dives[1].dive.reset(new2); splitDives.dives[1].dive.reset(newDives[1]);
splitDives.dives[1].trip = d->divetrip; splitDives.dives[1].trip = d->divetrip;
splitDives.dives[1].idx = idx + 1; splitDives.dives[1].idx = idx2;
} }
bool SplitDives::workToBeDone() bool SplitDivesBase::workToBeDone()
{ {
return !diveToSplit.empty(); return !diveToSplit.empty();
} }
void SplitDives::redoit() void SplitDivesBase::redoit()
{ {
divesToUnsplit = addDives(splitDives); divesToUnsplit = addDives(splitDives);
unsplitDive = removeDives(diveToSplit); unsplitDive = removeDives(diveToSplit);
@ -867,7 +878,7 @@ void SplitDives::redoit()
restoreSelection(divesToUnsplit, divesToUnsplit[0]); restoreSelection(divesToUnsplit, divesToUnsplit[0]);
} }
void SplitDives::undoit() void SplitDivesBase::undoit()
{ {
// Note: reverse order with respect to redoit() // Note: reverse order with respect to redoit()
diveToSplit = addDives(unsplitDive); diveToSplit = addDives(unsplitDive);
@ -878,6 +889,41 @@ void SplitDives::undoit()
restoreSelection(diveToSplit, diveToSplit[0] ); restoreSelection(diveToSplit, diveToSplit[0] );
} }
static std::array<dive *, 2> doSplitDives(const dive *d, duration_t time)
{
// Split the dive
dive *new1, *new2;
if (time.seconds < 0)
split_dive(d, &new1, &new2);
else
split_dive_at_time(d, time, &new1, &new2);
return { new1, new2 };
}
SplitDives::SplitDives(dive *d, duration_t time) : SplitDivesBase(d, doSplitDives(d, time))
{
setText(tr("split dive"));
}
static std::array<dive *, 2> splitDiveComputer(const dive *d, int dc_num)
{
// Refuse to do anything if the dive has only one dive computer.
// Yes, this should have been checked by the UI, but let's just make sure.
if (!d->dc.next)
return { nullptr, nullptr};
dive *new1, *new2;
split_divecomputer(d, dc_num, &new1, &new2);
return { new1, new2 };
}
SplitDiveComputer::SplitDiveComputer(dive *d, int dc_num) : SplitDivesBase(d, splitDiveComputer(d, dc_num))
{
setText(tr("split dive computer"));
}
MergeDives::MergeDives(const QVector <dive *> &dives) MergeDives::MergeDives(const QVector <dive *> &dives)
{ {
setText(tr("merge dive")); setText(tr("merge dive"));

View file

@ -185,10 +185,9 @@ struct MergeTrips : public TripBase {
MergeTrips(dive_trip *trip1, dive_trip *trip2); MergeTrips(dive_trip *trip1, dive_trip *trip2);
}; };
class SplitDives : public DiveListBase { class SplitDivesBase : public DiveListBase {
public: protected:
// If time is < 0, split at first surface interval SplitDivesBase(dive *old, std::array<dive *, 2> newDives);
SplitDives(dive *d, duration_t time);
private: private:
void undoit() override; void undoit() override;
void redoit() override; void redoit() override;
@ -209,6 +208,18 @@ private:
std::vector<dive *> divesToUnsplit; std::vector<dive *> divesToUnsplit;
}; };
class SplitDives : public SplitDivesBase {
public:
// If time is < 0, split at first surface interval
SplitDives(dive *d, duration_t time);
};
class SplitDiveComputer : public SplitDivesBase {
public:
// If time is < 0, split at first surface interval
SplitDiveComputer(dive *d, int dc_num);
};
class MergeDives : public DiveListBase { class MergeDives : public DiveListBase {
public: public:
MergeDives(const QVector<dive *> &dives); MergeDives(const QVector<dive *> &dives);

View file

@ -1436,8 +1436,10 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
// create menu to show when right clicking on dive computer name // create menu to show when right clicking on dive computer name
if (dc_number > 0) if (dc_number > 0)
m.addAction(tr("Make first dive computer"), this, SLOT(makeFirstDC())); m.addAction(tr("Make first dive computer"), this, SLOT(makeFirstDC()));
if (count_divecomputers() > 1) if (count_divecomputers() > 1) {
m.addAction(tr("Delete this dive computer"), this, SLOT(deleteCurrentDC())); m.addAction(tr("Delete this dive computer"), this, SLOT(deleteCurrentDC()));
m.addAction(tr("Split this dive computer into own dive"), this, SLOT(splitCurrentDC()));
}
m.exec(event->globalPos()); m.exec(event->globalPos());
// don't show the regular profile context menu // don't show the regular profile context menu
return; return;
@ -1581,6 +1583,11 @@ void ProfileWidget2::deleteCurrentDC()
emit refreshDisplay(true); emit refreshDisplay(true);
} }
void ProfileWidget2::splitCurrentDC()
{
Command::splitDiveComputer(current_dive, dc_number);
}
void ProfileWidget2::makeFirstDC() void ProfileWidget2::makeFirstDC()
{ {
make_first_dc(); make_first_dc();

View file

@ -126,6 +126,7 @@ slots: // Necessary to call from QAction's signals.
void editName(); void editName();
void makeFirstDC(); void makeFirstDC();
void deleteCurrentDC(); void deleteCurrentDC();
void splitCurrentDC();
void pointInserted(const QModelIndex &parent, int start, int end); void pointInserted(const QModelIndex &parent, int start, int end);
void pointsRemoved(const QModelIndex &, int start, int end); void pointsRemoved(const QModelIndex &, int start, int end);
void updateThumbnail(QString filename, QImage thumbnail, duration_t duration); void updateThumbnail(QString filename, QImage thumbnail, duration_t duration);