mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	When editing the dive site of a dive, the dive-table of the corresponding dive site was not properly updated by the undo commands. Try to get this right. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
		
			
				
	
	
		
			1151 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1151 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| 
 | |
| #include "command_edit.h"
 | |
| #include "core/divelist.h"
 | |
| #include "core/fulltext.h"
 | |
| #include "core/qthelper.h" // for copy_qstring
 | |
| #include "core/selection.h"
 | |
| #include "core/subsurface-string.h"
 | |
| #include "core/tag.h"
 | |
| #include "qt-models/weightsysteminfomodel.h"
 | |
| #ifdef SUBSURFACE_MOBILE
 | |
| #include "qt-models/divelocationmodel.h"
 | |
| #endif
 | |
| 
 | |
| namespace Command {
 | |
| 
 | |
| template <typename T, DiveField::Flags ID>
 | |
| DiveField EditTemplate<T, ID>::fieldId() const
 | |
| {
 | |
| 	return ID;
 | |
| }
 | |
| 
 | |
| template <DiveField::Flags ID>
 | |
| DiveField EditTagsTemplate<ID>::fieldId() const
 | |
| {
 | |
| 	return ID;
 | |
| }
 | |
| 
 | |
| template <typename T, DiveField::Flags ID, T dive::*PTR>
 | |
| void EditDefaultSetter<T, ID, PTR>::set(struct dive *d, T v) const
 | |
| {
 | |
| 	d->*PTR = v;
 | |
| }
 | |
| 
 | |
| template <typename T, DiveField::Flags ID, T dive::*PTR>
 | |
| T EditDefaultSetter<T, ID, PTR>::data(struct dive *d) const
 | |
| {
 | |
| 	return d->*PTR;
 | |
| }
 | |
| 
 | |
| template <DiveField::Flags ID, char *dive::*PTR>
 | |
| void EditStringSetter<ID, PTR>::set(struct dive *d, QString v) const
 | |
| {
 | |
| 	free(d->*PTR);
 | |
| 	d->*PTR = copy_qstring(v);
 | |
| }
 | |
| 
 | |
| template <DiveField::Flags ID, char *dive::*PTR>
 | |
| QString EditStringSetter<ID, PTR>::data(struct dive *d) const
 | |
| {
 | |
| 	return QString(d->*PTR);
 | |
| }
 | |
| 
 | |
| 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 == 1)
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Edit %1").arg(fieldName())).arg(diveNumberOrDate(dives[0])));
 | |
| 	else if (num_dives > 0)
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Edit %1 (%n dive(s))", "", num_dives).arg(fieldName())).arg(getListOfDives(dives)));
 | |
| 
 | |
| 	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);
 | |
| 		fulltext_register(d); // Update the fulltext cache
 | |
| 		invalidate_dive_cache(d); // Ensure that dive is written in git_save()
 | |
| 	}
 | |
| 
 | |
| 	std::swap(old, value);
 | |
| 
 | |
| 	// Send signals.
 | |
| 	DiveField id = fieldId();
 | |
| #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
 | |
| 	emit diveListNotifier.divesChanged(QVector<dive *>(dives.begin(), dives.end()), id);
 | |
| #else
 | |
| 	emit diveListNotifier.divesChanged(QVector<dive *>::fromStdVector(dives), id);
 | |
| #endif
 | |
| 
 | |
| 	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<bool>::EditBase(bool 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 *****
 | |
| QString EditNotes::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("notes");
 | |
| }
 | |
| 
 | |
| // ***** Suit *****
 | |
| QString EditSuit::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("suit");
 | |
| }
 | |
| 
 | |
| // ***** Rating *****
 | |
| QString EditRating::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("rating");
 | |
| }
 | |
| 
 | |
| // ***** Visibility *****
 | |
| QString EditVisibility::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("visibility");
 | |
| }
 | |
| 
 | |
| // ***** WaveSize *****
 | |
| QString EditWaveSize::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("wavesize");
 | |
| }
 | |
| 
 | |
| // ***** Current *****
 | |
| QString EditCurrent::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("current");
 | |
| }
 | |
| 
 | |
| // ***** Surge *****
 | |
| QString EditSurge::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("surge");
 | |
| }
 | |
| 
 | |
| // ***** Chill *****
 | |
| QString EditChill::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("chill");
 | |
| }
 | |
| 
 | |
| // ***** 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 Command::Base::tr("air temperature");
 | |
| }
 | |
| 
 | |
| // ***** 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 Command::Base::tr("water temperature");
 | |
| }
 | |
| 
 | |
| // ***** Water Type *****
 | |
| void EditWaterTypeUser::set(struct dive *d, int value) const
 | |
| {
 | |
| 	d->user_salinity = value > 0 ? value : 0;
 | |
| }
 | |
| 
 | |
| int EditWaterTypeUser::data(struct dive *d) const
 | |
| {
 | |
| 	return d->user_salinity;
 | |
| }
 | |
| 
 | |
| QString EditWaterTypeUser::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("salinity");
 | |
| }
 | |
| 
 | |
| // ***** 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 Command::Base::tr("Atm. pressure");
 | |
| }
 | |
| 
 | |
| // ***** 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 Command::Base::tr("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 Command::Base::tr("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 Command::Base::tr("dive site");
 | |
| }
 | |
| 
 | |
| 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)
 | |
| 	: EditTemplate(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 Command::Base::tr("dive mode");
 | |
| }
 | |
| 
 | |
| // ***** Invalid *****
 | |
| QString EditInvalid::fieldName() const
 | |
| {
 | |
| 	return Command::Base::tr("invalid");
 | |
| }
 | |
| 
 | |
| // ***** 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 == 1)
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Edit %1").arg(fieldName())).arg(diveNumberOrDate(dives[0])));
 | |
| 	else if (num_dives > 0)
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Edit %1 (%n dive(s))", "", num_dives).arg(fieldName())).arg(getListOfDives(dives)));
 | |
| 
 | |
| 	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);
 | |
| 		}
 | |
| 		set(d, tags);
 | |
| 		fulltext_register(d); // Update the fulltext cache
 | |
| 		invalidate_dive_cache(d); // Ensure that dive is written in git_save()
 | |
| 	}
 | |
| 
 | |
| 	std::swap(tagsToAdd, tagsToRemove);
 | |
| 
 | |
| 	// Send signals.
 | |
| 	DiveField id = fieldId();
 | |
| #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
 | |
| 	emit diveListNotifier.divesChanged(QVector<dive *>(dives.begin(), dives.end()), id);
 | |
| #else
 | |
| 	emit diveListNotifier.divesChanged(QVector<dive *>::fromStdVector(dives), id);
 | |
| #endif
 | |
| 	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 Command::Base::tr("tags");
 | |
| }
 | |
| 
 | |
| // ***** 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 Command::Base::tr("buddies");
 | |
| }
 | |
| 
 | |
| // ***** 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 Command::Base::tr("dive master");
 | |
| }
 | |
| 
 | |
| 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.wavesize)
 | |
| 		wavesize = data->wavesize;
 | |
| 	if (what.current)
 | |
| 		current = data->current;
 | |
| 	if (what.surge)
 | |
| 		surge = data->surge;
 | |
| 	if (what.chill)
 | |
| 		chill = data->chill;
 | |
| 	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.wavesize)
 | |
| 		std::swap(wavesize, d->wavesize);
 | |
| 	if (what.current)
 | |
| 		std::swap(current, d->current);
 | |
| 	if (what.surge)
 | |
| 		std::swap(surge, d->surge);
 | |
| 	if (what.chill)
 | |
| 		std::swap(chill, d->chill);
 | |
| 	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)
 | |
| {
 | |
| 	std::vector<dive *> selection = getDiveSelection();
 | |
| 	dives.reserve(selection.size());
 | |
| 	for (dive *d: selection)
 | |
| 		dives.emplace_back(d, data, what);
 | |
| 
 | |
| 	setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Paste onto %n dive(s)", "", dives.size())).arg(getListOfDives(selection)));
 | |
| }
 | |
| 
 | |
| bool PasteDives::workToBeDone()
 | |
| {
 | |
| 	return !dives.empty();
 | |
| }
 | |
| 
 | |
| void PasteDives::undo()
 | |
| {
 | |
| 	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()
 | |
| 	}
 | |
| 
 | |
| 	// 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.wavesize = what.wavesize;
 | |
| 	fields.current = what.current;
 | |
| 	fields.surge = what.surge;
 | |
| 	fields.chill = what.chill;
 | |
| 	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);
 | |
| }
 | |
| 
 | |
| // Redo and undo do the same
 | |
| void PasteDives::redo()
 | |
| {
 | |
| 	undo();
 | |
| }
 | |
| 
 | |
| // ***** ReplanDive *****
 | |
| ReplanDive::ReplanDive(dive *source, bool edit_profile) : d(current_dive),
 | |
| 	when(0),
 | |
| 	maxdepth({0}),
 | |
| 	meandepth({0}),
 | |
| 	dc({ 0 }),
 | |
| 	notes(nullptr),
 | |
| 	surface_pressure({0}),
 | |
| 	duration({0}),
 | |
| 	salinity(0)
 | |
| {
 | |
| 	memset(&cylinders, 0, sizeof(cylinders));
 | |
| 	if (!d)
 | |
| 		return;
 | |
| 
 | |
| 	// Fix source. Things might be inconsistent after modifying the profile.
 | |
| 	source->maxdepth.mm = source->dc.maxdepth.mm = 0;
 | |
| 	fixup_dive(source);
 | |
| 
 | |
| 	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((edit_profile ? Command::Base::tr("Replan dive") : Command::Base::tr("Edit profile")) + diveNumberOrDate(d));
 | |
| }
 | |
| 
 | |
| 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();
 | |
| }
 | |
| 
 | |
| // ***** Add Weight *****
 | |
| AddWeight::AddWeight(bool currentDiveOnly) :
 | |
| 	EditDivesBase(currentDiveOnly)
 | |
| {
 | |
| 	if (dives.size() == 1)
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Add weight")).arg(diveNumberOrDate(dives[0])));
 | |
| 	else
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Add weight (%n dive(s))", "", dives.size())).arg(getListOfDives(dives)));
 | |
| }
 | |
| 
 | |
| bool AddWeight::workToBeDone()
 | |
| {
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void AddWeight::undo()
 | |
| {
 | |
| 	for (dive *d: dives) {
 | |
| 		if (d->weightsystems.nr <= 0)
 | |
| 			continue;
 | |
| 		remove_weightsystem(d, d->weightsystems.nr - 1);
 | |
| 		emit diveListNotifier.weightRemoved(d, d->weightsystems.nr);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void AddWeight::redo()
 | |
| {
 | |
| 	for (dive *d: dives) {
 | |
| 		add_cloned_weightsystem(&d->weightsystems, empty_weightsystem);
 | |
| 		emit diveListNotifier.weightAdded(d, d->weightsystems.nr - 1);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int find_weightsystem_index(const struct dive *d, weightsystem_t ws)
 | |
| {
 | |
| 	for (int idx = 0; idx < d->weightsystems.nr; ++idx) {
 | |
| 		if (same_weightsystem(d->weightsystems.weightsystems[idx], ws))
 | |
| 			return idx;
 | |
| 	}
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| EditWeightBase::EditWeightBase(int index, bool currentDiveOnly) :
 | |
| 	EditDivesBase(currentDiveOnly),
 | |
| 	ws(empty_weightsystem)
 | |
| {
 | |
| 	// Get the old weightsystem, bail if index is invalid
 | |
| 	if (!current || index < 0 || index >= current->weightsystems.nr) {
 | |
| 		dives.clear();
 | |
| 		return;
 | |
| 	}
 | |
| 	ws = clone_weightsystem(current->weightsystems.weightsystems[index]);
 | |
| 
 | |
| 	// Deleting a weightsystem from multiple dives is semantically ill-defined.
 | |
| 	// What we will do is trying to delete the same weightsystem if it exists.
 | |
| 	// For that purpose, we will determine the indices of the same weightsystem.
 | |
| 	std::vector<dive *> divesNew;
 | |
| 	divesNew.reserve(dives.size());
 | |
| 	indices.reserve(dives.size());
 | |
| 
 | |
| 	for (dive *d: dives) {
 | |
| 		if (d == current) {
 | |
| 			divesNew.push_back(d);
 | |
| 			indices.push_back(index);
 | |
| 			continue;
 | |
| 		}
 | |
| 		int idx = find_weightsystem_index(d, ws);
 | |
| 		if (idx >= 0) {
 | |
| 			divesNew.push_back(d);
 | |
| 			indices.push_back(idx);
 | |
| 		}
 | |
| 	}
 | |
| 	dives = std::move(divesNew);
 | |
| }
 | |
| 
 | |
| EditWeightBase::~EditWeightBase()
 | |
| {
 | |
| 	free_weightsystem(ws);
 | |
| }
 | |
| 
 | |
| bool EditWeightBase::workToBeDone()
 | |
| {
 | |
| 	return !dives.empty();
 | |
| }
 | |
| 
 | |
| RemoveWeight::RemoveWeight(int index, bool currentDiveOnly) :
 | |
| 	EditWeightBase(index, currentDiveOnly)
 | |
| {
 | |
| 	size_t num_dives = dives.size();
 | |
| 	if (num_dives == 1)
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Remove weight")).arg(diveNumberOrDate(dives[0])));
 | |
| 	else
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Remove weight (%n dive(s))", "", num_dives)).arg(getListOfDives(dives)));
 | |
| }
 | |
| 
 | |
| void RemoveWeight::undo()
 | |
| {
 | |
| 	for (size_t i = 0; i < dives.size(); ++i) {
 | |
| 		add_to_weightsystem_table(&dives[i]->weightsystems, indices[i], clone_weightsystem(ws));
 | |
| 		emit diveListNotifier.weightAdded(dives[i], indices[i]);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void RemoveWeight::redo()
 | |
| {
 | |
| 	for (size_t i = 0; i < dives.size(); ++i) {
 | |
| 		remove_weightsystem(dives[i], indices[i]);
 | |
| 		emit diveListNotifier.weightRemoved(dives[i], indices[i]);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| EditWeight::EditWeight(int index, weightsystem_t wsIn, bool currentDiveOnly) :
 | |
| 	EditWeightBase(index, currentDiveOnly),
 | |
| 	new_ws(empty_weightsystem)
 | |
| {
 | |
| 	if (dives.empty())
 | |
| 		return;
 | |
| 
 | |
| 	size_t num_dives = dives.size();
 | |
| 	if (num_dives == 1)
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Edit weight")).arg(diveNumberOrDate(dives[0])));
 | |
| 	else
 | |
| 		setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Edit weight (%n dive(s))", "", num_dives)).arg(getListOfDives(dives)));
 | |
| 
 | |
| 	// Try to untranslate the weightsystem name
 | |
| 	new_ws = clone_weightsystem(wsIn);
 | |
| 	QString vString(new_ws.description);
 | |
| 	for (int i = 0; i < MAX_WS_INFO && ws_info[i].name; ++i) {
 | |
| 		if (gettextFromC::tr(ws_info[i].name) == vString) {
 | |
| 			free_weightsystem(new_ws);
 | |
| 			new_ws.description = copy_string(ws_info[i].name);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// If that doesn't change anything, do nothing
 | |
| 	if (same_weightsystem(ws, new_ws)) {
 | |
| 		dives.clear();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	WSInfoModel *wsim = WSInfoModel::instance();
 | |
| 	QModelIndexList matches = wsim->match(wsim->index(0, 0), Qt::DisplayRole, gettextFromC::tr(new_ws.description));
 | |
| 	if (!matches.isEmpty())
 | |
| 		wsim->setData(wsim->index(matches.first().row(), WSInfoModel::GR), new_ws.weight.grams);
 | |
| }
 | |
| 
 | |
| EditWeight::~EditWeight()
 | |
| {
 | |
| 	free_weightsystem(new_ws);
 | |
| }
 | |
| 
 | |
| void EditWeight::redo()
 | |
| {
 | |
| 	for (size_t i = 0; i < dives.size(); ++i) {
 | |
| 		set_weightsystem(dives[i], indices[i], new_ws);
 | |
| 		emit diveListNotifier.weightEdited(dives[i], indices[i]);
 | |
| 	}
 | |
| 	std::swap(ws, new_ws);
 | |
| }
 | |
| 
 | |
| // Undo and redo do the same as just the stored value is exchanged
 | |
| void EditWeight::undo()
 | |
| {
 | |
| 	redo();
 | |
| }
 | |
| 
 | |
| #ifdef SUBSURFACE_MOBILE
 | |
| 
 | |
| EditDive::EditDive(dive *oldDiveIn, dive *newDiveIn, dive_site *createDs, dive_site *editDs, location_t dsLocationIn)
 | |
| 	: oldDive(oldDiveIn)
 | |
| 	, newDive(newDiveIn)
 | |
| 	, newDiveSite(newDiveIn->dive_site)
 | |
| 	, changedFields(DiveField::NONE)
 | |
| 	, siteToRemove(nullptr)
 | |
| 	, siteToAdd(createDs)
 | |
| 	, siteToEdit(editDs)
 | |
| 	, dsLocation(dsLocationIn)
 | |
| {
 | |
| 	if (!oldDive || !newDive)
 | |
| 		return;
 | |
| 
 | |
| 	setText(Command::Base::tr("Edit dive [%1]").arg(diveNumberOrDate(oldDive)));
 | |
| 
 | |
| 	// Calculate the fields that changed.
 | |
| 	// Note: Probably not needed, as on mobile we don't have that granularity.
 | |
| 	// However, for future-proofeness let's just do it.
 | |
| 	changedFields = DiveField::NONE;
 | |
| 	if (oldDive->number != newDive->number)
 | |
| 		changedFields |= DiveField::NR;
 | |
| 	if (oldDive->when != newDive->when)
 | |
| 		changedFields |= DiveField::DATETIME;
 | |
| 	if (oldDive->maxdepth.mm != newDive->maxdepth.mm)
 | |
| 		changedFields |= DiveField::DEPTH;
 | |
| 	if (oldDive->duration.seconds != newDive->duration.seconds)
 | |
| 		changedFields |= DiveField::DURATION;
 | |
| 	if (oldDive->airtemp.mkelvin != newDive->airtemp.mkelvin)
 | |
| 		changedFields |= DiveField::AIR_TEMP;
 | |
| 	if (oldDive->watertemp.mkelvin != newDive->watertemp.mkelvin)
 | |
| 		changedFields |= DiveField::WATER_TEMP;
 | |
| 	if (oldDive->surface_pressure.mbar != newDive->surface_pressure.mbar)
 | |
| 		changedFields |= DiveField::ATM_PRESS;
 | |
| 	if (oldDive->dive_site != newDive->dive_site)
 | |
| 		changedFields |= DiveField::DIVESITE;
 | |
| 	if (!same_string(oldDive->divemaster, newDive->divemaster))
 | |
| 		changedFields |= DiveField::DIVEMASTER;
 | |
| 	if (!same_string(oldDive->buddy, newDive->buddy))
 | |
| 		changedFields |= DiveField::BUDDY;
 | |
| 	if (oldDive->rating != newDive->rating)
 | |
| 		changedFields |= DiveField::RATING;
 | |
| 	if (oldDive->visibility != newDive->visibility)
 | |
| 		changedFields |= DiveField::VISIBILITY;
 | |
| 	if (oldDive->wavesize != newDive->wavesize)
 | |
| 		changedFields |= DiveField::WAVESIZE;
 | |
| 	if (oldDive->current != newDive->current)
 | |
| 		changedFields |= DiveField::CURRENT;
 | |
| 	if (oldDive->surge != newDive->surge)
 | |
| 		changedFields |= DiveField::SURGE;
 | |
| 	if (oldDive->chill != newDive->chill)
 | |
| 		changedFields |= DiveField::CHILL;
 | |
| 	if (!same_string(oldDive->suit, newDive->suit))
 | |
| 		changedFields |= DiveField::SUIT;
 | |
| 	if (get_taglist_string(oldDive->tag_list) != get_taglist_string(newDive->tag_list)) // This is cheating. Do we have a taglist comparison function?
 | |
| 		changedFields |= DiveField::TAGS;
 | |
| 	if (oldDive->dc.divemode != newDive->dc.divemode)
 | |
| 		changedFields |= DiveField::MODE;
 | |
| 	if (!same_string(oldDive->notes, newDive->notes))
 | |
| 		changedFields |= DiveField::NOTES;
 | |
| 	if (oldDive->salinity != newDive->salinity)
 | |
| 		changedFields |= DiveField::SALINITY;
 | |
| 
 | |
| 	newDive->dive_site = nullptr; // We will add the dive to the site manually and therefore saved the dive site.
 | |
| }
 | |
| 
 | |
| void EditDive::undo()
 | |
| {
 | |
| 	if (siteToRemove) {
 | |
| 		int idx = unregister_dive_site(siteToRemove);
 | |
| 		siteToAdd.reset(siteToRemove);
 | |
| 		emit diveListNotifier.diveSiteDeleted(siteToRemove, idx); // Inform frontend of removed dive site.
 | |
| 	}
 | |
| 
 | |
| 	exchangeDives();
 | |
| 	editDs();
 | |
| }
 | |
| 
 | |
| void EditDive::redo()
 | |
| {
 | |
| 	if (siteToAdd) {
 | |
| 		siteToRemove = siteToAdd.get();
 | |
| 		int idx = register_dive_site(siteToAdd.release()); // Return ownership to backend.
 | |
| 		emit diveListNotifier.diveSiteAdded(siteToRemove, idx); // Inform frontend of new dive site.
 | |
| 	}
 | |
| 
 | |
| 	exchangeDives();
 | |
| 	editDs();
 | |
| }
 | |
| 
 | |
| void EditDive::exchangeDives()
 | |
| {
 | |
| 	// Set the filter flag of the new dive to the old dive.
 | |
| 	// Reason: When we send the dive-changed signal, the model will
 | |
| 	// track the *change* of the filter flag, so we must not overwrite
 | |
| 	// it by swapping the dive data.
 | |
| 	newDive->hidden_by_filter = oldDive->hidden_by_filter;
 | |
| 
 | |
| 	// Bluntly exchange dive data by shallow copy.
 | |
| 	// Don't forget to unregister the old and register the new dive!
 | |
| 	// Likewise take care to add/remove the dive from the dive site.
 | |
| 	fulltext_unregister(oldDive);
 | |
| 	dive_site *oldDiveSite = oldDive->dive_site;
 | |
| 	if (oldDiveSite)
 | |
| 		unregister_dive_from_dive_site(oldDive); // the dive-site pointer in the dive is now NULL
 | |
| 	std::swap(*newDive, *oldDive);
 | |
| 	fulltext_register(oldDive);
 | |
| 	if (newDiveSite)
 | |
| 		add_dive_to_dive_site(oldDive, newDiveSite);
 | |
| 	newDiveSite = oldDiveSite; // remember the previous dive site
 | |
| 	invalidate_dive_cache(oldDive);
 | |
| 
 | |
| 	// Changing times may have unsorted the dive and trip tables
 | |
| 	QVector<dive *> dives = { oldDive };
 | |
| 	timestamp_t delta = oldDive->when - newDive->when;
 | |
| 	if (delta != 0) {
 | |
| 		sort_dive_table(&dive_table);
 | |
| 		sort_trip_table(&trip_table);
 | |
| 		if (newDive->divetrip != oldDive->divetrip)
 | |
| 			qWarning("Command::EditDive::redo(): This command does not support moving between trips!");
 | |
| 		if (oldDive->divetrip)
 | |
| 			sort_dive_table(&newDive->divetrip->dives); // Keep the trip-table in order
 | |
| 		emit diveListNotifier.divesTimeChanged(delta, dives);
 | |
| 	}
 | |
| 
 | |
| 	// Send signals
 | |
| 	emit diveListNotifier.divesChanged(dives, changedFields);
 | |
| 
 | |
| 	// Select the changed dives
 | |
| 	setSelection( { oldDive }, oldDive);
 | |
| }
 | |
| 
 | |
| void EditDive::editDs()
 | |
| {
 | |
| 	if (siteToEdit) {
 | |
| 		std::swap(siteToEdit->location, dsLocation);
 | |
| 		emit diveListNotifier.diveSiteChanged(siteToEdit, LocationInformationModel::LOCATION); // Inform frontend of changed dive site.
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool EditDive::workToBeDone()
 | |
| {
 | |
| 	// We trust the frontend that an EditDive command is only created if there are changes.
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| #endif // SUBSURFACE_MOBILE
 | |
| 
 | |
| } // namespace Command
 |