mirror of
https://github.com/subsurface/subsurface.git
synced 2024-12-01 06:30:26 +00:00
7c9f46acd2
Instead of using fixed size arrays, use a new cylinder_table structure. The code copies the weightsystem code, but is significantly more complex because cylinders are such an integral part of the core. Two functions to access the cylinders were added: get_cylinder() and get_or_create_cylinder() The former does a simple array access and supposes that the cylinder exists. The latter is used by the parser(s) and if a cylinder with the given id does not exist, cylinders up to that id are generated. One point will make C programmers cringe: the cylinder structure is passed by value. This is due to the way the table-macros work. A refactoring of the table macros is planned. It has to be noted that the size of a cylinder_t is 64 bytes, i.e. 8 long words on a 64-bit architecture, so passing on the stack is probably not even significantly slower than passing as reference. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
879 lines
20 KiB
C++
879 lines
20 KiB
C++
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include "command_edit.h"
|
|
#include "command_private.h"
|
|
#include "core/divelist.h"
|
|
#include "core/qthelper.h" // for copy_qstring
|
|
#include "core/subsurface-string.h"
|
|
#include "core/tag.h"
|
|
#include "desktop-widgets/mapwidget.h" // TODO: Replace desktop-dependency by signal
|
|
|
|
namespace Command {
|
|
|
|
static std::vector<dive *> getDives(bool currentDiveOnly)
|
|
{
|
|
if (currentDiveOnly)
|
|
return current_dive ? std::vector<dive *> { current_dive }
|
|
: std::vector<dive *> { };
|
|
|
|
std::vector<dive *> res;
|
|
struct dive *d;
|
|
int i;
|
|
for_each_dive (i, d) {
|
|
if (d->selected)
|
|
res.push_back(d);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
EditDivesBase::EditDivesBase(bool currentDiveOnly) :
|
|
dives(getDives(currentDiveOnly)),
|
|
selectedDives(getDiveSelection()),
|
|
current(current_dive)
|
|
{
|
|
}
|
|
|
|
EditDivesBase::EditDivesBase(dive *d) :
|
|
dives({ d }),
|
|
selectedDives(getDiveSelection()),
|
|
current(current_dive)
|
|
{
|
|
}
|
|
|
|
int EditDivesBase::numDives() const
|
|
{
|
|
return dives.size();
|
|
}
|
|
|
|
template<typename T>
|
|
EditBase<T>::EditBase(T newValue, bool currentDiveOnly) :
|
|
EditDivesBase(currentDiveOnly),
|
|
value(std::move(newValue))
|
|
{
|
|
}
|
|
|
|
template<typename T>
|
|
EditBase<T>::EditBase(T newValue, dive *d) :
|
|
EditDivesBase(d),
|
|
value(std::move(newValue))
|
|
{
|
|
}
|
|
|
|
// This is quite hackish: we can't use virtual functions in the constructor and
|
|
// therefore can't initialize the list of dives [the values of the dives are
|
|
// accessed by virtual functions]. Therefore, we (mis)use the fact that workToBeDone()
|
|
// is called exactly once before adding the Command to the system and perform this here.
|
|
// To be more explicit about this, we might think about renaming workToBeDone() to init().
|
|
template<typename T>
|
|
bool EditBase<T>::workToBeDone()
|
|
{
|
|
// First, let's fetch the old value, i.e. the value of the current dive.
|
|
// If no current dive exists, bail.
|
|
if (!current)
|
|
return false;
|
|
old = data(current);
|
|
|
|
// If there is no change - do nothing.
|
|
if (old == value)
|
|
return false;
|
|
|
|
std::vector<dive *> divesNew;
|
|
divesNew.reserve(dives.size());
|
|
for (dive *d: dives) {
|
|
if (data(d) == old)
|
|
divesNew.push_back(d);
|
|
}
|
|
dives = std::move(divesNew);
|
|
|
|
// Create a text for the menu entry. In the case of multiple dives add the number
|
|
size_t num_dives = dives.size();
|
|
if (num_dives > 0)
|
|
//: remove the part in parentheses for %n = 1
|
|
setText(tr("Edit %1 (%n dive(s))", "", num_dives).arg(fieldName()));
|
|
|
|
return num_dives > 0;
|
|
}
|
|
|
|
template<typename T>
|
|
void EditBase<T>::undo()
|
|
{
|
|
if (dives.empty()) {
|
|
qWarning("Edit command called with empty dives list (shouldn't happen)");
|
|
return;
|
|
}
|
|
|
|
for (dive *d: dives) {
|
|
set(d, value);
|
|
invalidate_dive_cache(d); // Ensure that dive is written in git_save()
|
|
}
|
|
|
|
std::swap(old, value);
|
|
|
|
// Send signals.
|
|
DiveField id = fieldId();
|
|
emit diveListNotifier.divesChanged(QVector<dive *>::fromStdVector(dives), id);
|
|
|
|
setSelection(selectedDives, current);
|
|
}
|
|
|
|
// We have to manually instantiate the constructors of the EditBase class,
|
|
// because the base class is never constructed and the derived classes
|
|
// don't have their own constructor. They simply delegate to the base
|
|
// class by virtue of a "using" declaration.
|
|
template
|
|
EditBase<QString>::EditBase(QString newValue, bool currentDiveOnly);
|
|
template
|
|
EditBase<int>::EditBase(int newValue, bool currentDiveOnly);
|
|
template
|
|
EditBase<struct dive_site *>::EditBase(struct dive_site *newValue, bool currentDiveOnly);
|
|
|
|
// Undo and redo do the same as just the stored value is exchanged
|
|
template<typename T>
|
|
void EditBase<T>::redo()
|
|
{
|
|
// Note: here, we explicitly call the undo function of EditBase<T> and don't do
|
|
// virtual dispatch. Thus, derived classes can call this redo function without
|
|
// having their own undo() function called.
|
|
EditBase<T>::undo();
|
|
}
|
|
|
|
// Implementation of virtual functions
|
|
|
|
// ***** Notes *****
|
|
void EditNotes::set(struct dive *d, QString s) const
|
|
{
|
|
free(d->notes);
|
|
d->notes = strdup(qPrintable(s));
|
|
}
|
|
|
|
QString EditNotes::data(struct dive *d) const
|
|
{
|
|
return QString(d->notes);
|
|
}
|
|
|
|
QString EditNotes::fieldName() const
|
|
{
|
|
return tr("notes");
|
|
}
|
|
|
|
DiveField EditNotes::fieldId() const
|
|
{
|
|
return DiveField::NOTES;
|
|
}
|
|
|
|
// ***** Suit *****
|
|
void EditSuit::set(struct dive *d, QString s) const
|
|
{
|
|
free(d->suit);
|
|
d->suit = strdup(qPrintable(s));
|
|
}
|
|
|
|
QString EditSuit::data(struct dive *d) const
|
|
{
|
|
return QString(d->suit);
|
|
}
|
|
|
|
QString EditSuit::fieldName() const
|
|
{
|
|
return tr("suit");
|
|
}
|
|
|
|
DiveField EditSuit::fieldId() const
|
|
{
|
|
return DiveField::SUIT;
|
|
}
|
|
|
|
// ***** Rating *****
|
|
void EditRating::set(struct dive *d, int value) const
|
|
{
|
|
d->rating = value;
|
|
}
|
|
|
|
int EditRating::data(struct dive *d) const
|
|
{
|
|
return d->rating;
|
|
}
|
|
|
|
QString EditRating::fieldName() const
|
|
{
|
|
return tr("rating");
|
|
}
|
|
|
|
DiveField EditRating::fieldId() const
|
|
{
|
|
return DiveField::RATING;
|
|
}
|
|
|
|
// ***** Visibility *****
|
|
void EditVisibility::set(struct dive *d, int value) const
|
|
{
|
|
d->visibility = value;
|
|
}
|
|
|
|
int EditVisibility::data(struct dive *d) const
|
|
{
|
|
return d->visibility;
|
|
}
|
|
|
|
QString EditVisibility::fieldName() const
|
|
{
|
|
return tr("visibility");
|
|
}
|
|
|
|
DiveField EditVisibility::fieldId() const
|
|
{
|
|
return DiveField::VISIBILITY;
|
|
}
|
|
|
|
// ***** Air Temperature *****
|
|
void EditAirTemp::set(struct dive *d, int value) const
|
|
{
|
|
d->airtemp.mkelvin = value > 0 ? (uint32_t)value : 0u;
|
|
}
|
|
|
|
int EditAirTemp::data(struct dive *d) const
|
|
{
|
|
return (int)d->airtemp.mkelvin;
|
|
}
|
|
|
|
QString EditAirTemp::fieldName() const
|
|
{
|
|
return tr("air temperature");
|
|
}
|
|
|
|
DiveField EditAirTemp::fieldId() const
|
|
{
|
|
return DiveField::AIR_TEMP;
|
|
}
|
|
|
|
// ***** Water Temperature *****
|
|
void EditWaterTemp::set(struct dive *d, int value) const
|
|
{
|
|
d->watertemp.mkelvin = value > 0 ? (uint32_t)value : 0u;
|
|
|
|
// re-populate the temperatures - easiest way to do this is by calling fixup_dive
|
|
fixup_dive(d);
|
|
}
|
|
|
|
int EditWaterTemp::data(struct dive *d) const
|
|
{
|
|
return (int)d->watertemp.mkelvin;
|
|
}
|
|
|
|
QString EditWaterTemp::fieldName() const
|
|
{
|
|
return tr("water temperature");
|
|
}
|
|
|
|
DiveField EditWaterTemp::fieldId() const
|
|
{
|
|
return DiveField::WATER_TEMP;
|
|
}
|
|
|
|
// ***** Atmospheric pressure *****
|
|
void EditAtmPress::set(struct dive *d, int value) const
|
|
{
|
|
d->surface_pressure.mbar = value > 0 ? (uint32_t)value : 0u;
|
|
}
|
|
|
|
int EditAtmPress::data(struct dive *d) const
|
|
{
|
|
return (int)d->surface_pressure.mbar;
|
|
}
|
|
|
|
QString EditAtmPress::fieldName() const
|
|
{
|
|
return tr("Atm. pressure");
|
|
}
|
|
|
|
DiveField EditAtmPress::fieldId() const
|
|
{
|
|
return DiveField::ATM_PRESS;
|
|
}
|
|
|
|
// ***** Duration *****
|
|
void EditDuration::set(struct dive *d, int value) const
|
|
{
|
|
d->dc.duration.seconds = value;
|
|
d->duration = d->dc.duration;
|
|
d->dc.meandepth.mm = 0;
|
|
d->dc.samples = 0;
|
|
}
|
|
|
|
int EditDuration::data(struct dive *d) const
|
|
{
|
|
return d->duration.seconds;
|
|
}
|
|
|
|
QString EditDuration::fieldName() const
|
|
{
|
|
return tr("duration");
|
|
}
|
|
|
|
DiveField EditDuration::fieldId() const
|
|
{
|
|
return DiveField::DURATION;
|
|
}
|
|
|
|
// ***** Depth *****
|
|
void EditDepth::set(struct dive *d, int value) const
|
|
{
|
|
d->dc.maxdepth.mm = value;
|
|
d->maxdepth = d->dc.maxdepth;
|
|
d->dc.meandepth.mm = 0;
|
|
d->dc.samples = 0;
|
|
}
|
|
|
|
int EditDepth::data(struct dive *d) const
|
|
{
|
|
return d->maxdepth.mm;
|
|
}
|
|
|
|
QString EditDepth::fieldName() const
|
|
{
|
|
return tr("depth");
|
|
}
|
|
|
|
DiveField EditDepth::fieldId() const
|
|
{
|
|
return DiveField::DEPTH;
|
|
}
|
|
|
|
// ***** DiveSite *****
|
|
void EditDiveSite::set(struct dive *d, struct dive_site *dive_site) const
|
|
{
|
|
unregister_dive_from_dive_site(d);
|
|
add_dive_to_dive_site(d, dive_site);
|
|
}
|
|
|
|
struct dive_site *EditDiveSite::data(struct dive *d) const
|
|
{
|
|
return d->dive_site;
|
|
}
|
|
|
|
QString EditDiveSite::fieldName() const
|
|
{
|
|
return tr("dive site");
|
|
}
|
|
|
|
DiveField EditDiveSite::fieldId() const
|
|
{
|
|
return DiveField::DIVESITE;
|
|
}
|
|
|
|
void EditDiveSite::undo()
|
|
{
|
|
// Do the normal undo thing, then send dive site changed signals
|
|
EditBase<dive_site *>::undo();
|
|
if (value)
|
|
emit diveListNotifier.diveSiteDivesChanged(value);
|
|
if (old)
|
|
emit diveListNotifier.diveSiteDivesChanged(old);
|
|
}
|
|
|
|
void EditDiveSite::redo()
|
|
{
|
|
EditDiveSite::undo(); // Undo and redo do the same
|
|
}
|
|
|
|
static struct dive_site *createDiveSite(const QString &name)
|
|
{
|
|
struct dive_site *ds = alloc_dive_site();
|
|
struct dive_site *old = current_dive ? current_dive->dive_site : nullptr;
|
|
if (old) {
|
|
copy_dive_site(old, ds);
|
|
free(ds->name); // Free name, as we will overwrite it with our own version
|
|
}
|
|
|
|
// If the current dive has a location, use that as location for the new dive site
|
|
if (current_dive) {
|
|
location_t loc = dive_get_gps_location(current_dive);
|
|
if (has_location(&loc))
|
|
ds->location = loc;
|
|
}
|
|
|
|
ds->name = copy_qstring(name);
|
|
return ds;
|
|
}
|
|
|
|
EditDiveSiteNew::EditDiveSiteNew(const QString &newName, bool currentDiveOnly) :
|
|
EditDiveSite(createDiveSite(newName), currentDiveOnly),
|
|
diveSiteToAdd(value),
|
|
diveSiteToRemove(nullptr)
|
|
{
|
|
}
|
|
|
|
void EditDiveSiteNew::undo()
|
|
{
|
|
EditDiveSite::undo();
|
|
int idx = unregister_dive_site(diveSiteToRemove);
|
|
diveSiteToAdd.reset(diveSiteToRemove);
|
|
emit diveListNotifier.diveSiteDeleted(diveSiteToRemove, idx); // Inform frontend of removed dive site.
|
|
diveSiteToRemove = nullptr;
|
|
}
|
|
|
|
void EditDiveSiteNew::redo()
|
|
{
|
|
diveSiteToRemove = diveSiteToAdd.get();
|
|
int idx = register_dive_site(diveSiteToAdd.release()); // Return ownership to backend.
|
|
emit diveListNotifier.diveSiteAdded(diveSiteToRemove, idx); // Inform frontend of new dive site.
|
|
EditDiveSite::redo();
|
|
}
|
|
|
|
// ***** Mode *****
|
|
// Editing the dive mode has very peculiar semantics for historic reasons:
|
|
// Since the dive-mode depends on the dive computer, the i-th dive computer
|
|
// of each dive will be edited. If the dive has less than i dive computers,
|
|
// the default dive computer will be edited.
|
|
// The index "i" will be stored as an additional payload with the command.
|
|
// Thus, we need an explicit constructor. Since the actual handling is done
|
|
// by the base class, which knows nothing about this index, it will not be
|
|
// sent via signals. Currently this is not needed. Should it turn out to
|
|
// become necessary, then we might either
|
|
// - Not derive EditMode from EditBase.
|
|
// - Change the semantics of the mode-editing.
|
|
// The future will tell.
|
|
EditMode::EditMode(int indexIn, int newValue, bool currentDiveOnly)
|
|
: EditBase(newValue, currentDiveOnly), index(indexIn)
|
|
{
|
|
}
|
|
|
|
void EditMode::set(struct dive *d, int i) const
|
|
{
|
|
get_dive_dc(d, index)->divemode = (enum divemode_t)i;
|
|
update_setpoint_events(d, get_dive_dc(d, index));
|
|
}
|
|
|
|
int EditMode::data(struct dive *d) const
|
|
{
|
|
return get_dive_dc(d, index)->divemode;
|
|
}
|
|
|
|
QString EditMode::fieldName() const
|
|
{
|
|
return tr("dive mode");
|
|
}
|
|
|
|
DiveField EditMode::fieldId() const
|
|
{
|
|
return DiveField::MODE;
|
|
}
|
|
|
|
// ***** Number *****
|
|
void EditNumber::set(struct dive *d, int number) const
|
|
{
|
|
d->number = number;
|
|
}
|
|
|
|
int EditNumber::data(struct dive *d) const
|
|
{
|
|
return d->number;
|
|
}
|
|
|
|
QString EditNumber::fieldName() const
|
|
{
|
|
return tr("number");
|
|
}
|
|
|
|
DiveField EditNumber::fieldId() const
|
|
{
|
|
return DiveField::NR;
|
|
}
|
|
|
|
|
|
// ***** Tag based commands *****
|
|
EditTagsBase::EditTagsBase(const QStringList &newListIn, bool currentDiveOnly) :
|
|
EditDivesBase(currentDiveOnly),
|
|
newList(newListIn)
|
|
{
|
|
}
|
|
|
|
// Two helper functions: returns true if first list contains any tag or
|
|
// misses any tag of second list.
|
|
static bool containsAny(const QStringList &tags1, const QStringList &tags2)
|
|
{
|
|
return std::any_of(tags2.begin(), tags2.end(), [&tags1](const QString &tag)
|
|
{ return tags1.contains(tag); });
|
|
}
|
|
|
|
static bool missesAny(const QStringList &tags1, const QStringList &tags2)
|
|
{
|
|
return std::any_of(tags2.begin(), tags2.end(), [&tags1](const QString &tag)
|
|
{ return !tags1.contains(tag); });
|
|
}
|
|
|
|
// This is quite hackish: we can't use virtual functions in the constructor and
|
|
// therefore can't initialize the list of dives [the values of the dives are
|
|
// accessed by virtual functions]. Therefore, we (mis)use the fact that workToBeDone()
|
|
// is called exactly once before adding the Command to the system and perform this here.
|
|
// To be more explicit about this, we might think about renaming workToBeDone() to init().
|
|
bool EditTagsBase::workToBeDone()
|
|
{
|
|
// changing the tags on multiple dives is semantically strange - what's the right thing to do?
|
|
// here's what I think... add the tags that were added to the displayed dive and remove the tags
|
|
// that were removed from it
|
|
|
|
// If there is no current dive, bail.
|
|
if (!current)
|
|
return false;
|
|
|
|
// Calculate tags to add and tags to remove
|
|
QStringList oldList = data(current);
|
|
for (const QString &s: newList) {
|
|
if (!oldList.contains(s))
|
|
tagsToAdd.push_back(s);
|
|
}
|
|
for (const QString &s: oldList) {
|
|
if (!newList.contains(s))
|
|
tagsToRemove.push_back(s);
|
|
}
|
|
|
|
// Now search for all dives that either
|
|
// - miss a tag to be added
|
|
// - have a tag to be removed
|
|
std::vector<dive *> divesNew;
|
|
divesNew.reserve(dives.size());
|
|
for (dive *d: dives) {
|
|
QStringList tags = data(d);
|
|
if (missesAny(tags, tagsToAdd) || containsAny(tags, tagsToRemove))
|
|
divesNew.push_back(d);
|
|
}
|
|
dives = std::move(divesNew);
|
|
|
|
// Create a text for the menu entry. In the case of multiple dives add the number
|
|
size_t num_dives = dives.size();
|
|
if (num_dives > 0)
|
|
//: remove the part in parentheses for %n = 1
|
|
setText(tr("Edit %1 (%n dive(s))", "", num_dives).arg(fieldName()));
|
|
|
|
return num_dives != 0;
|
|
}
|
|
|
|
void EditTagsBase::undo()
|
|
{
|
|
if (dives.empty()) {
|
|
qWarning("Edit command called with empty dives list (shouldn't happen)");
|
|
return;
|
|
}
|
|
|
|
for (dive *d: dives) {
|
|
QStringList tags = data(d);
|
|
for (const QString &tag: tagsToRemove)
|
|
tags.removeAll(tag);
|
|
for (const QString &tag: tagsToAdd) {
|
|
if (!tags.contains(tag))
|
|
tags.push_back(tag);
|
|
}
|
|
invalidate_dive_cache(d); // Ensure that dive is written in git_save()
|
|
set(d, tags);
|
|
}
|
|
|
|
std::swap(tagsToAdd, tagsToRemove);
|
|
|
|
// Send signals.
|
|
DiveField id = fieldId();
|
|
emit diveListNotifier.divesChanged(QVector<dive *>::fromStdVector(dives), id);
|
|
|
|
setSelection(selectedDives, current);
|
|
}
|
|
|
|
// Undo and redo do the same as just the stored value is exchanged
|
|
void EditTagsBase::redo()
|
|
{
|
|
undo();
|
|
}
|
|
|
|
// ***** Tags *****
|
|
QStringList EditTags::data(struct dive *d) const
|
|
{
|
|
QStringList res;
|
|
for (const struct tag_entry *tag = d->tag_list; tag; tag = tag->next)
|
|
res.push_back(tag->tag->name);
|
|
return res;
|
|
}
|
|
|
|
void EditTags::set(struct dive *d, const QStringList &v) const
|
|
{
|
|
taglist_free(d->tag_list);
|
|
d->tag_list = NULL;
|
|
for (const QString &tag: v)
|
|
taglist_add_tag(&d->tag_list, qPrintable(tag));
|
|
taglist_cleanup(&d->tag_list);
|
|
}
|
|
|
|
QString EditTags::fieldName() const
|
|
{
|
|
return tr("tags");
|
|
}
|
|
|
|
DiveField EditTags::fieldId() const
|
|
{
|
|
return DiveField::TAGS;
|
|
}
|
|
|
|
// String list helper
|
|
static QStringList stringToList(const QString &s)
|
|
{
|
|
QStringList res = s.split(",", QString::SkipEmptyParts);
|
|
for (QString &str: res)
|
|
str = str.trimmed();
|
|
return res;
|
|
}
|
|
|
|
// ***** Buddies *****
|
|
QStringList EditBuddies::data(struct dive *d) const
|
|
{
|
|
return stringToList(d->buddy);
|
|
}
|
|
|
|
void EditBuddies::set(struct dive *d, const QStringList &v) const
|
|
{
|
|
QString text = v.join(", ");
|
|
free(d->buddy);
|
|
d->buddy = copy_qstring(text);
|
|
}
|
|
|
|
QString EditBuddies::fieldName() const
|
|
{
|
|
return tr("buddies");
|
|
}
|
|
|
|
DiveField EditBuddies::fieldId() const
|
|
{
|
|
return DiveField::BUDDY;
|
|
}
|
|
|
|
// ***** DiveMaster *****
|
|
QStringList EditDiveMaster::data(struct dive *d) const
|
|
{
|
|
return stringToList(d->divemaster);
|
|
}
|
|
|
|
void EditDiveMaster::set(struct dive *d, const QStringList &v) const
|
|
{
|
|
QString text = v.join(", ");
|
|
free(d->divemaster);
|
|
d->divemaster = copy_qstring(text);
|
|
}
|
|
|
|
QString EditDiveMaster::fieldName() const
|
|
{
|
|
return tr("dive master");
|
|
}
|
|
|
|
DiveField EditDiveMaster::fieldId() const
|
|
{
|
|
return DiveField::DIVEMASTER;
|
|
}
|
|
|
|
static void swapCandQString(QString &q, char *&c)
|
|
{
|
|
QString tmp(c);
|
|
free(c);
|
|
c = copy_qstring(q);
|
|
q = std::move(tmp);
|
|
}
|
|
|
|
PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dIn),
|
|
tags(nullptr)
|
|
{
|
|
memset(&cylinders, 0, sizeof(cylinders));
|
|
memset(&weightsystems, 0, sizeof(weightsystems));
|
|
if (what.notes)
|
|
notes = data->notes;
|
|
if (what.divemaster)
|
|
divemaster = data->divemaster;
|
|
if (what.buddy)
|
|
buddy = data->buddy;
|
|
if (what.suit)
|
|
suit = data->suit;
|
|
if (what.rating)
|
|
rating = data->rating;
|
|
if (what.visibility)
|
|
visibility = data->visibility;
|
|
if (what.divesite)
|
|
divesite = data->dive_site;
|
|
if (what.tags)
|
|
tags = taglist_copy(data->tag_list);
|
|
if (what.cylinders)
|
|
copy_cylinders(&data->cylinders, &cylinders);
|
|
if (what.weights)
|
|
copy_weights(&data->weightsystems, &weightsystems);
|
|
}
|
|
|
|
PasteState::~PasteState()
|
|
{
|
|
taglist_free(tags);
|
|
clear_cylinder_table(&cylinders);
|
|
clear_weightsystem_table(&weightsystems);
|
|
free(weightsystems.weightsystems);
|
|
}
|
|
|
|
void PasteState::swap(dive_components what)
|
|
{
|
|
if (what.notes)
|
|
swapCandQString(notes, d->notes);
|
|
if (what.divemaster)
|
|
swapCandQString(divemaster, d->divemaster);
|
|
if (what.buddy)
|
|
swapCandQString(buddy, d->buddy);
|
|
if (what.suit)
|
|
swapCandQString(suit, d->suit);
|
|
if (what.rating)
|
|
std::swap(rating, d->rating);
|
|
if (what.visibility)
|
|
std::swap(visibility, d->visibility);
|
|
if (what.divesite)
|
|
std::swap(divesite, d->dive_site);
|
|
if (what.tags)
|
|
std::swap(tags, d->tag_list);
|
|
if (what.cylinders)
|
|
std::swap(cylinders, d->cylinders);
|
|
if (what.weights)
|
|
std::swap(weightsystems, d->weightsystems);
|
|
}
|
|
|
|
// ***** Paste *****
|
|
PasteDives::PasteDives(const dive *data, dive_components whatIn) : what(whatIn),
|
|
current(current_dive)
|
|
{
|
|
std::vector<dive *> selection = getDiveSelection();
|
|
dives.reserve(selection.size());
|
|
for (dive *d: selection)
|
|
dives.emplace_back(d, data, what);
|
|
|
|
setText(tr("Paste onto %n dive(s)", "", dives.size()));
|
|
}
|
|
|
|
bool PasteDives::workToBeDone()
|
|
{
|
|
return !dives.empty();
|
|
}
|
|
|
|
void PasteDives::undo()
|
|
{
|
|
bool diveSiteListChanged = false;
|
|
|
|
// If we had taken ownership of dive sites, readd them to the system
|
|
for (OwningDiveSitePtr &ds: ownedDiveSites) {
|
|
register_dive_site(ds.release());
|
|
diveSiteListChanged = true;
|
|
}
|
|
ownedDiveSites.clear();
|
|
|
|
QVector<dive *> divesToNotify; // Remember dives so that we can send signals later
|
|
divesToNotify.reserve(dives.size());
|
|
for (PasteState &state: dives) {
|
|
divesToNotify.push_back(state.d);
|
|
state.swap(what);
|
|
invalidate_dive_cache(state.d); // Ensure that dive is written in git_save()
|
|
}
|
|
|
|
// If dive sites were pasted, collect all overwritten dive sites
|
|
// and remove those which don't have users anymore from the core.
|
|
// But keep an owning pointer. Thus if this undo command is freed, the
|
|
// dive-site will be automatically deleted and on redo() it can be
|
|
// readded to the system
|
|
if (what.divesite) {
|
|
std::vector<dive_site *> divesites;
|
|
for (const PasteState &d: dives) {
|
|
if (std::find(divesites.begin(), divesites.end(), d.divesite) == divesites.end())
|
|
divesites.push_back(d.divesite);
|
|
}
|
|
for (dive_site *ds: divesites) {
|
|
unregister_dive_site(ds);
|
|
ownedDiveSites.emplace_back(ds);
|
|
diveSiteListChanged = true;
|
|
}
|
|
}
|
|
|
|
// Send signals.
|
|
DiveField fields(DiveField::NONE);
|
|
fields.notes = what.notes;
|
|
fields.divemaster = what.divemaster;
|
|
fields.buddy = what.buddy;
|
|
fields.suit = what.suit;
|
|
fields.rating = what.rating;
|
|
fields.visibility = what.visibility;
|
|
fields.divesite = what.divesite;
|
|
fields.tags = what.tags;
|
|
emit diveListNotifier.divesChanged(divesToNotify, fields);
|
|
if (what.cylinders)
|
|
emit diveListNotifier.cylindersReset(divesToNotify);
|
|
if (what.weights)
|
|
emit diveListNotifier.weightsystemsReset(divesToNotify);
|
|
|
|
if (diveSiteListChanged)
|
|
MapWidget::instance()->reload();
|
|
}
|
|
|
|
// Redo and undo do the same
|
|
void PasteDives::redo()
|
|
{
|
|
undo();
|
|
}
|
|
|
|
// ***** Paste *****
|
|
ReplanDive::ReplanDive(dive *source) : d(current_dive),
|
|
dc({ 0 }),
|
|
notes(nullptr)
|
|
{
|
|
memset(&cylinders, 0, sizeof(cylinders));
|
|
if (!d)
|
|
return;
|
|
|
|
when = source->when;
|
|
maxdepth = source->maxdepth;
|
|
meandepth = source->meandepth;
|
|
notes = copy_string(source->notes);
|
|
duration = source->duration;
|
|
salinity = source->salinity;
|
|
surface_pressure = source->surface_pressure;
|
|
|
|
// This resets the dive computers and cylinders of the source dive, avoiding deep copies.
|
|
std::swap(source->cylinders, cylinders);
|
|
std::swap(source->dc, dc);
|
|
|
|
setText(tr("Replan dive"));
|
|
}
|
|
|
|
ReplanDive::~ReplanDive()
|
|
{
|
|
clear_cylinder_table(&cylinders);
|
|
free_dive_dcs(&dc);
|
|
free(notes);
|
|
}
|
|
|
|
bool ReplanDive::workToBeDone()
|
|
{
|
|
return !!d;
|
|
}
|
|
|
|
void ReplanDive::undo()
|
|
{
|
|
std::swap(d->when, when);
|
|
std::swap(d->maxdepth, maxdepth);
|
|
std::swap(d->meandepth, meandepth);
|
|
std::swap(d->cylinders, cylinders);
|
|
std::swap(d->dc, dc);
|
|
std::swap(d->notes, notes);
|
|
std::swap(d->surface_pressure, surface_pressure);
|
|
std::swap(d->duration, duration);
|
|
std::swap(d->salinity, salinity);
|
|
fixup_dive(d);
|
|
|
|
QVector<dive *> divesToNotify = { d };
|
|
// Note that we have to emit cylindersReset before divesChanged, because the divesChanged
|
|
// updates the DivePlotDataModel, which is out-of-sync and gets confused.
|
|
emit diveListNotifier.cylindersReset(divesToNotify);
|
|
emit diveListNotifier.divesChanged(divesToNotify, DiveField::DATETIME | DiveField::DURATION | DiveField::DEPTH | DiveField::MODE |
|
|
DiveField::NOTES | DiveField::SALINITY | DiveField::ATM_PRESS);
|
|
}
|
|
|
|
// Redo and undo do the same
|
|
void ReplanDive::redo()
|
|
{
|
|
undo();
|
|
}
|
|
|
|
} // namespace Command
|