fix copy/paste of dive-site

The copy/pasting of dive-sites was fundamentally broken in at least two
ways:

1) The dive-site pointer in struct dive was simply overwritten, which
   breaks internal consistency. Also, no dive-site changed signals where
   sent.

2) The copied dive-site was stored as a pointer in a struct dive. Thus,
   the user could copy a dive, then delete the dive-site and paste.
   This would lead to a dangling pointer and ultimately crash the
   application.

Fix this by storing the UUID of the dive-site, not a pointer.
To do that, don't store a copy of the dive, but collect all
the data in a `dive_paste_data` structure.
If the dive site has been deleted on paste, do nothing.
Send the appropriate signals on pasting.

The mobile version had an additional bug: It kept a pointer to the
dive to be copied, which might become stale by undo.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2024-08-13 07:04:52 +02:00 committed by Michael Keller
parent 48b4308a7d
commit 152e6966c9
17 changed files with 359 additions and 425 deletions

View file

@ -267,9 +267,9 @@ int editDiveGuide(const QStringList &newList, bool currentDiveOnly)
return execute_edit(new EditDiveGuide(newList, currentDiveOnly)); return execute_edit(new EditDiveGuide(newList, currentDiveOnly));
} }
void pasteDives(const dive *d, dive_components what) void pasteDives(const dive_paste_data &data)
{ {
execute(new PasteDives(d, what)); execute(new PasteDives(data));
} }
void replanDive(dive *d) void replanDive(dive *d)

View file

@ -13,6 +13,7 @@
struct divecomputer; struct divecomputer;
struct divelog; struct divelog;
struct dive_components; struct dive_components;
struct dive_paste_data;
struct dive_site; struct dive_site;
struct dive_trip; struct dive_trip;
struct event; struct event;
@ -95,7 +96,7 @@ int editDiveSiteNew(const QString &newName, bool currentDiveOnly);
int editTags(const QStringList &newList, bool currentDiveOnly); int editTags(const QStringList &newList, bool currentDiveOnly);
int editBuddies(const QStringList &newList, bool currentDiveOnly); int editBuddies(const QStringList &newList, bool currentDiveOnly);
int editDiveGuide(const QStringList &newList, bool currentDiveOnly); int editDiveGuide(const QStringList &newList, bool currentDiveOnly);
void pasteDives(const dive *d, dive_components what); void pasteDives(const dive_paste_data &data);
enum class EditProfileType { enum class EditProfileType {
ADD, ADD,
REMOVE, REMOVE,

View file

@ -613,41 +613,51 @@ QString EditDiveGuide::fieldName() const
return Command::Base::tr("dive guide"); return Command::Base::tr("dive guide");
} }
PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dIn) template <typename T>
ptrdiff_t comp_ptr(T *v1, T *v2)
{ {
if (what.notes) return v1 - v2;
notes = data->notes; }
if (what.diveguide)
diveguide = data->diveguide; PasteState::PasteState(dive &d, const dive_paste_data &data, std::vector<dive_site *> &dive_sites_changed) : d(d)
if (what.buddy) {
buddy = data->buddy; notes = data.notes;
if (what.suit) diveguide = data.diveguide;
suit = data->suit; buddy = data.buddy;
if (what.rating) suit = data.suit;
rating = data->rating; rating = data.rating;
if (what.visibility) visibility = data.visibility;
visibility = data->visibility; wavesize = data.wavesize;
if (what.wavesize) current = data.current;
wavesize = data->wavesize; surge = data.surge;
if (what.current) chill = data.chill;
current = data->current; if (data.divesite.has_value()) {
if (what.surge) if (data.divesite) {
surge = data->surge; // In the undo system, we can turn the uuid into a pointer,
if (what.chill) // because everything is serialized.
chill = data->chill; dive_site *ds = divelog.sites.get_by_uuid(*data.divesite);
if (what.divesite) if (ds)
divesite = data->dive_site; divesite = ds;
if (what.tags) } else {
tags = data->tags; divesite = nullptr;
if (what.cylinders) { }
cylinders = data->cylinders; }
if (divesite.has_value() && *divesite != d.dive_site) {
if (d.dive_site)
range_insert_sorted_unique(dive_sites_changed, d.dive_site, comp_ptr<dive_site>); // Use <=> once on C++20
if (*divesite)
range_insert_sorted_unique(dive_sites_changed, *divesite, comp_ptr<dive_site>); // Use <=> once on C++20
}
tags = data.tags;
if (data.cylinders.has_value()) {
cylinders = data.cylinders;
// Paste cylinders is "special": // Paste cylinders is "special":
// 1) For cylinders that exist in the destination dive we keep the gas-mix and pressures. // 1) For cylinders that exist in the destination dive we keep the gas-mix and pressures.
// 2) For cylinders that do not yet exist in the destination dive, we set the pressures to 0, i.e. unset. // 2) For cylinders that do not yet exist in the destination dive, we set the pressures to 0, i.e. unset.
// Moreover, for these we set the manually_added flag, because they weren't downloaded from a DC. // Moreover, for these we set the manually_added flag, because they weren't downloaded from a DC.
for (size_t i = 0; i < d->cylinders.size() && i < cylinders.size(); ++i) { for (size_t i = 0; i < data.cylinders->size() && i < cylinders->size(); ++i) {
const cylinder_t &src = d->cylinders[i]; const cylinder_t &src = (*data.cylinders)[i];
cylinder_t &dst = cylinders[i]; cylinder_t &dst = (*cylinders)[i];
dst.gasmix = src.gasmix; dst.gasmix = src.gasmix;
dst.start = src.start; dst.start = src.start;
dst.end = src.end; dst.end = src.end;
@ -661,8 +671,8 @@ PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dI
dst.bestmix_o2 = src.bestmix_o2; dst.bestmix_o2 = src.bestmix_o2;
dst.bestmix_he = src.bestmix_he; dst.bestmix_he = src.bestmix_he;
} }
for (size_t i = d->cylinders.size(); i < cylinders.size(); ++i) { for (size_t i = data.cylinders->size(); i < cylinders->size(); ++i) {
cylinder_t &cyl = cylinders[i]; cylinder_t &cyl = (*cylinders)[i];
cyl.start.mbar = 0; cyl.start.mbar = 0;
cyl.end.mbar = 0; cyl.end.mbar = 0;
cyl.sample_start.mbar = 0; cyl.sample_start.mbar = 0;
@ -670,61 +680,62 @@ PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dI
cyl.manually_added = true; cyl.manually_added = true;
} }
} }
if (what.weights) weightsystems = data.weights;
weightsystems = data->weightsystems; number = data.number;
if (what.number) when = data.when;
number = data->number;
if (what.when)
when = data->when;
} }
PasteState::~PasteState() PasteState::~PasteState()
{ {
} }
void PasteState::swap(dive_components what) void PasteState::swap()
{ {
if (what.notes) if (notes.has_value())
std::swap(notes, d->notes); std::swap(*notes, d.notes);
if (what.diveguide) if (diveguide.has_value())
std::swap(diveguide, d->diveguide); std::swap(*diveguide, d.diveguide);
if (what.buddy) if (buddy.has_value())
std::swap(buddy, d->buddy); std::swap(*buddy, d.buddy);
if (what.suit) if (suit.has_value())
std::swap(suit, d->suit); std::swap(*suit, d.suit);
if (what.rating) if (rating.has_value())
std::swap(rating, d->rating); std::swap(*rating, d.rating);
if (what.visibility) if (visibility.has_value())
std::swap(visibility, d->visibility); std::swap(*visibility, d.visibility);
if (what.wavesize) if (wavesize.has_value())
std::swap(wavesize, d->wavesize); std::swap(*wavesize, d.wavesize);
if (what.current) if (current.has_value())
std::swap(current, d->current); std::swap(*current, d.current);
if (what.surge) if (surge.has_value())
std::swap(surge, d->surge); std::swap(*surge, d.surge);
if (what.chill) if (chill.has_value())
std::swap(chill, d->chill); std::swap(*chill, d.chill);
if (what.divesite) if (divesite.has_value() && *divesite != d.dive_site) {
std::swap(divesite, d->dive_site); auto old_ds = unregister_dive_from_dive_site(&d);
if (what.tags) if (*divesite)
std::swap(tags, d->tags); (*divesite)->add_dive(&d);
if (what.cylinders) divesite = old_ds;
std::swap(cylinders, d->cylinders); }
if (what.weights) if (tags.has_value())
std::swap(weightsystems, d->weightsystems); std::swap(*tags, d.tags);
if (what.number) if (cylinders.has_value())
std::swap(number, d->number); std::swap(*cylinders, d.cylinders);
if (what.when) if (weightsystems.has_value())
std::swap(when, d->when); std::swap(*weightsystems, d.weightsystems);
if (number.has_value())
std::swap(*number, d.number);
if (when.has_value())
std::swap(*when, d.when);
} }
// ***** Paste ***** // ***** Paste *****
PasteDives::PasteDives(const dive *data, dive_components whatIn) : what(whatIn) PasteDives::PasteDives(const dive_paste_data &data)
{ {
std::vector<dive *> selection = getDiveSelection(); std::vector<dive *> selection = getDiveSelection();
dives.reserve(selection.size()); dives.reserve(selection.size());
for (dive *d: selection) for (dive *d: selection)
dives.emplace_back(d, data, what); dives.emplace_back(*d, data, dive_sites_changed);
setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Paste onto %n dive(s)", "", dives.size())).arg(getListOfDives(selection))); setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Paste onto %n dive(s)", "", dives.size())).arg(getListOfDives(selection)));
} }
@ -739,32 +750,35 @@ void PasteDives::undo()
QVector<dive *> divesToNotify; // Remember dives so that we can send signals later QVector<dive *> divesToNotify; // Remember dives so that we can send signals later
divesToNotify.reserve(dives.size()); divesToNotify.reserve(dives.size());
for (PasteState &state: dives) { for (PasteState &state: dives) {
divesToNotify.push_back(state.d); divesToNotify.push_back(&state.d);
state.swap(what); state.swap();
state.d->invalidate_cache(); // Ensure that dive is written in git_save() state.d.invalidate_cache(); // Ensure that dive is written in git_save()
} }
// Send signals. // Send signals. We use the first data item to determine what changed
DiveField fields(DiveField::NONE); DiveField fields(DiveField::NONE);
fields.notes = what.notes; const PasteState &state = dives[0];
fields.diveguide = what.diveguide; fields.notes = state.notes.has_value();
fields.buddy = what.buddy; fields.diveguide = state.diveguide.has_value();
fields.suit = what.suit; fields.buddy = state.buddy.has_value();
fields.rating = what.rating; fields.suit = state.suit.has_value();
fields.visibility = what.visibility; fields.rating = state.rating.has_value();
fields.wavesize = what.wavesize; fields.visibility = state.visibility.has_value();
fields.current = what.current; fields.wavesize = state.wavesize.has_value();
fields.surge = what.surge; fields.current = state.current.has_value();
fields.chill = what.chill; fields.surge = state.surge.has_value();
fields.divesite = what.divesite; fields.chill = state.chill.has_value();
fields.tags = what.tags; fields.divesite = !dive_sites_changed.empty();
fields.datetime = what.when; fields.tags = state.tags.has_value();
fields.nr = what.number; fields.datetime = state.when.has_value();
fields.nr = state.number.has_value();
emit diveListNotifier.divesChanged(divesToNotify, fields); emit diveListNotifier.divesChanged(divesToNotify, fields);
if (what.cylinders) if (state.cylinders.has_value())
emit diveListNotifier.cylindersReset(divesToNotify); emit diveListNotifier.cylindersReset(divesToNotify);
if (what.weights) if (state.weightsystems.has_value())
emit diveListNotifier.weightsystemsReset(divesToNotify); emit diveListNotifier.weightsystemsReset(divesToNotify);
for (dive_site *ds: dive_sites_changed)
emit diveListNotifier.diveSiteDivesChanged(ds);
} }
// Redo and undo do the same // Redo and undo do the same

View file

@ -287,34 +287,35 @@ public:
// Fields we have to remember to undo paste // Fields we have to remember to undo paste
struct PasteState { struct PasteState {
dive *d; dive &d;
dive_site *divesite; std::optional<dive_site *> divesite;
std::string notes; std::optional<std::string> notes;
std::string diveguide; std::optional<std::string> diveguide;
std::string buddy; std::optional<std::string> buddy;
std::string suit; std::optional<std::string> suit;
int rating; std::optional<int> rating;
int wavesize; std::optional<int> wavesize;
int visibility; std::optional<int> visibility;
int current; std::optional<int> current;
int surge; std::optional<int> surge;
int chill; std::optional<int> chill;
tag_list tags; std::optional<tag_list> tags;
cylinder_table cylinders; std::optional<cylinder_table> cylinders;
weightsystem_table weightsystems; std::optional<weightsystem_table> weightsystems;
int number; std::optional<int> number;
timestamp_t when; std::optional<timestamp_t> when;
PasteState(dive *d, const dive *data, dive_components what); // Read data from dive data for dive d PasteState(dive &d, const dive_paste_data &data, std::vector<dive_site *> &changed_dive_sites);
~PasteState(); ~PasteState();
void swap(dive_components what); // Exchange values here and in dive void swap(); // Exchange values here and in dive
}; };
class PasteDives : public Base { class PasteDives : public Base {
dive_components what; dive_paste_data data;
std::vector<PasteState> dives; std::vector<PasteState> dives;
std::vector<dive_site *> dive_sites_changed;
public: public:
PasteDives(const dive *d, dive_components what); PasteDives(const dive_paste_data &data);
private: private:
void undo() override; void undo() override;
void redo() override; void redo() override;

View file

@ -203,40 +203,6 @@ void copy_dive(const struct dive *s, struct dive *d)
d->invalidate_cache(); d->invalidate_cache();
} }
#define CONDITIONAL_COPY_STRING(_component) \
if (what._component) \
d->_component = s->_component
// copy elements, depending on bits in what that are set
void selective_copy_dive(const struct dive *s, struct dive *d, struct dive_components what, bool clear)
{
if (clear)
d->clear();
CONDITIONAL_COPY_STRING(notes);
CONDITIONAL_COPY_STRING(diveguide);
CONDITIONAL_COPY_STRING(buddy);
CONDITIONAL_COPY_STRING(suit);
if (what.rating)
d->rating = s->rating;
if (what.visibility)
d->visibility = s->visibility;
if (what.divesite) {
unregister_dive_from_dive_site(d);
s->dive_site->add_dive(d);
}
if (what.tags)
d->tags = s->tags;
if (what.cylinders)
copy_cylinder_types(s, d);
if (what.weights)
d->weightsystems = s->weightsystems;
if (what.number)
d->number = s->number;
if (what.when)
d->when = s->when;
}
#undef CONDITIONAL_COPY_STRING
/* copies all events from the given dive computer before a given time /* copies all events from the given dive computer before a given time
this is used when editing a dive in the planner to preserve the events this is used when editing a dive in the planner to preserve the events
of the old dive */ of the old dive */

View file

@ -12,6 +12,7 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -146,24 +147,25 @@ struct dive_or_trip {
extern void cylinder_renumber(struct dive &dive, int mapping[]); extern void cylinder_renumber(struct dive &dive, int mapping[]);
extern int same_gasmix_cylinder(const cylinder_t &cyl, int cylid, const struct dive *dive, bool check_unused); extern int same_gasmix_cylinder(const cylinder_t &cyl, int cylid, const struct dive *dive, bool check_unused);
/* when selectively copying dive information, which parts should be copied? */ /* Data stored when copying a dive */
struct dive_components { struct dive_paste_data {
unsigned int divesite : 1; std::optional<uint32_t> divesite; // We save the uuid not a pointer, because the
unsigned int notes : 1; // user might copy and then delete the dive site.
unsigned int diveguide : 1; std::optional<std::string> notes;
unsigned int buddy : 1; std::optional<std::string> diveguide;
unsigned int suit : 1; std::optional<std::string> buddy;
unsigned int rating : 1; std::optional<std::string> suit;
unsigned int visibility : 1; std::optional<int> rating;
unsigned int wavesize : 1; std::optional<int> visibility;
unsigned int current : 1; std::optional<int> wavesize;
unsigned int surge : 1; std::optional<int> current;
unsigned int chill : 1; std::optional<int> surge;
unsigned int tags : 1; std::optional<int> chill;
unsigned int cylinders : 1; std::optional<tag_list> tags;
unsigned int weights : 1; std::optional<cylinder_table> cylinders;
unsigned int number : 1; std::optional<weightsystem_table> weights;
unsigned int when : 1; std::optional<int> number;
std::optional<timestamp_t> when;
}; };
extern std::unique_ptr<dive> clone_make_first_dc(const struct dive &d, int dc_number); extern std::unique_ptr<dive> clone_make_first_dc(const struct dive &d, int dc_number);
@ -179,7 +181,6 @@ struct membuffer;
extern void save_one_dive_to_mb(struct membuffer *b, const struct dive &dive, bool anonymize); extern void save_one_dive_to_mb(struct membuffer *b, const struct dive &dive, bool anonymize);
extern void copy_dive(const struct dive *s, struct dive *d); extern void copy_dive(const struct dive *s, struct dive *d);
extern void selective_copy_dive(const struct dive *s, struct dive *d, struct dive_components what, bool clear);
extern int legacy_format_o2pressures(const struct dive *dive, const struct divecomputer *dc); extern int legacy_format_o2pressures(const struct dive *dive, const struct divecomputer *dc);

View file

@ -181,13 +181,23 @@ bool range_contains(const Range &v, const Element &item)
// Insert into an already sorted range // Insert into an already sorted range
template<typename Range, typename Element, typename Comp> template<typename Range, typename Element, typename Comp>
void range_insert_sorted(Range &v, Element &item, Comp &comp) void range_insert_sorted(Range &v, Element &item, Comp comp)
{ {
auto it = std::lower_bound(std::begin(v), std::end(v), item, auto it = std::lower_bound(std::begin(v), std::end(v), item,
[&comp](auto &a, auto &b) { return comp(a, b) < 0; }); [&comp](auto &a, auto &b) { return comp(a, b) < 0; });
v.insert(it, std::move(item)); v.insert(it, std::move(item));
} }
// Insert into an already sorted range, but don't add an item twice
template<typename Range, typename Element, typename Comp>
void range_insert_sorted_unique(Range &v, Element &item, Comp comp)
{
auto it = std::lower_bound(std::begin(v), std::end(v), item,
[&comp](auto &a, auto &b) { return comp(a, b) < 0; });
if (it == std::end(v) || comp(item, *it) != 0)
v.insert(it, std::move(item));
}
template<typename Range, typename Element> template<typename Range, typename Element>
void range_remove(Range &v, const Element &item) void range_remove(Range &v, const Element &item)
{ {

View file

@ -68,6 +68,8 @@ set(SUBSURFACE_INTERFACE
about.h about.h
configuredivecomputerdialog.cpp configuredivecomputerdialog.cpp
configuredivecomputerdialog.h configuredivecomputerdialog.h
divecomponentselection.cpp
divecomponentselection.h
divelistview.cpp divelistview.cpp
divelistview.h divelistview.h
divelogexportdialog.cpp divelogexportdialog.cpp

View file

@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0
#include "divecomponentselection.h"
#include "core/dive.h"
#include "core/divesite.h"
#include "core/format.h"
#include "core/qthelper.h" // for get_dive_date_string()
#include "core/selection.h"
#include "core/string-format.h"
#include <QClipboard>
#include <QShortcut>
template <typename T>
static void assign_paste_data(QCheckBox &checkbox, const T &src, std::optional<T> &dest)
{
if (checkbox.isChecked())
dest = src;
else
dest = {};
}
template <typename T>
static void set_checked(QCheckBox &checkbox, const std::optional<T> &v)
{
checkbox.setChecked(v.has_value());
}
DiveComponentSelection::DiveComponentSelection(dive_paste_data &data, QWidget *parent) :
QDialog(parent), data(data)
{
ui.setupUi(this);
set_checked(*ui.divesite, data.divesite);
set_checked(*ui.diveguide, data.diveguide);
set_checked(*ui.buddy, data.buddy);
set_checked(*ui.rating, data.rating);
set_checked(*ui.visibility, data.visibility);
set_checked(*ui.notes, data.notes);
set_checked(*ui.suit, data.suit);
set_checked(*ui.tags, data.tags);
set_checked(*ui.cylinders, data.cylinders);
set_checked(*ui.weights, data.weights);
set_checked(*ui.number, data.number);
set_checked(*ui.when, data.when);
connect(ui.buttonBox, &QDialogButtonBox::clicked, this, &DiveComponentSelection::buttonClicked);
QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_W), this);
connect(close, &QShortcut::activated, this, &DiveComponentSelection::close);
QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this);
connect(quit, &QShortcut::activated, parent, &QWidget::close);
}
void DiveComponentSelection::buttonClicked(QAbstractButton *button)
{
if (current_dive && ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) {
// Divesite is special, as we store the uuid not the pointer, since the
// user may delete the divesite after copying.
assign_paste_data(*ui.divesite, current_dive->dive_site ? current_dive->dive_site->uuid : uint32_t(), data.divesite);
assign_paste_data(*ui.diveguide, current_dive->diveguide, data.diveguide);
assign_paste_data(*ui.buddy, current_dive->buddy, data.buddy);
assign_paste_data(*ui.rating, current_dive->rating, data.rating);
assign_paste_data(*ui.visibility, current_dive->visibility, data.visibility);
assign_paste_data(*ui.notes, current_dive->notes, data.notes);
assign_paste_data(*ui.suit, current_dive->suit, data.suit);
assign_paste_data(*ui.tags, current_dive->tags, data.tags);
assign_paste_data(*ui.cylinders, current_dive->cylinders, data.cylinders);
assign_paste_data(*ui.weights, current_dive->weightsystems, data.weights);
assign_paste_data(*ui.number, current_dive->number, data.number);
assign_paste_data(*ui.when, current_dive->when, data.when);
std::string text;
if (data.divesite && current_dive->dive_site)
text += tr("Dive site: ").toStdString() + current_dive->dive_site->name + '\n';
if (data.diveguide)
text += tr("Dive guide: ").toStdString() + current_dive->diveguide + '\n';
if (data.buddy)
text += tr("Buddy: ").toStdString() + current_dive->buddy + '\n';
if (data.rating)
text += tr("Rating: ").toStdString() + std::string(current_dive->rating, '*') + '\n';
if (data.visibility)
text += tr("Visibility: ").toStdString() + std::string(current_dive->visibility, '*') + '\n';
if (data.wavesize)
text += tr("Wave size: ").toStdString() + std::string(current_dive->wavesize, '*') + '\n';
if (data.current)
text += tr("Current: ").toStdString() + std::string(current_dive->current, '*') + '\n';
if (data.surge)
text += tr("Surge: ").toStdString() + std::string(current_dive->surge, '*') + '\n';
if (data.chill)
text += tr("Chill: ").toStdString() + std::string(current_dive->chill, '*') + '\n';
if (data.notes)
text += tr("Notes:\n").toStdString() + current_dive->notes + '\n';
if (data.suit)
text += tr("Suit: ").toStdString() + current_dive->suit + '\n';
if (data.tags)
text += tr("Tags: ").toStdString() + taglist_get_tagstring(current_dive->tags) + '\n';
if (data.cylinders)
text += tr("Cylinders:\n").toStdString() + formatGas(current_dive).toStdString();
if (data.weights)
text += tr("Weights:\n").toStdString() + formatWeightList(current_dive).toStdString();
if (data.number)
text += tr("Dive number: ").toStdString() + casprintf_loc("%d", current_dive->number) + '\n';
if (data.when)
text += tr("Date / time: ").toStdString() + get_dive_date_string(current_dive->when).toStdString() + '\n';
QApplication::clipboard()->setText(QString::fromStdString(text));
}
}

View file

@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef DIVECOMPONENTSELECTION_H
#define DIVECOMPONENTSELECTION_H
#include "ui_divecomponentselection.h"
struct dive_paste_data;
class DiveComponentSelection : public QDialog {
Q_OBJECT
public:
explicit DiveComponentSelection(dive_paste_data &data, QWidget *parent = nullptr);
private
slots:
void buttonClicked(QAbstractButton *button);
private:
Ui::DiveComponentSelectionDialog ui;
dive_paste_data &data;
};
#endif

View file

@ -38,6 +38,7 @@
#include "core/settings/qPrefDisplay.h" #include "core/settings/qPrefDisplay.h"
#include "desktop-widgets/about.h" #include "desktop-widgets/about.h"
#include "desktop-widgets/divecomponentselection.h"
#include "desktop-widgets/divelistview.h" #include "desktop-widgets/divelistview.h"
#include "desktop-widgets/divelogexportdialog.h" #include "desktop-widgets/divelogexportdialog.h"
#include "desktop-widgets/divelogimportdialog.h" #include "desktop-widgets/divelogimportdialog.h"
@ -207,7 +208,6 @@ MainWindow::MainWindow() :
#ifdef NO_USERMANUAL #ifdef NO_USERMANUAL
ui.menuHelp->removeAction(ui.actionUserManual); ui.menuHelp->removeAction(ui.actionUserManual);
#endif #endif
memset(&what, 0, sizeof(what));
updateManager = new UpdateManager(this); updateManager = new UpdateManager(this);
undoAction = Command::undoAction(this); undoAction = Command::undoAction(this);
@ -1424,13 +1424,13 @@ void MainWindow::on_copy_triggered()
{ {
// open dialog to select what gets copied // open dialog to select what gets copied
// copy the displayed dive // copy the displayed dive
DiveComponentSelection dialog(this, &copyPasteDive, &what); DiveComponentSelection dialog(paste_data, this);
dialog.exec(); dialog.exec();
} }
void MainWindow::on_paste_triggered() void MainWindow::on_paste_triggered()
{ {
Command::pasteDives(&copyPasteDive, what); Command::pasteDives(paste_data);
} }
void MainWindow::on_actionFilterTags_triggered() void MainWindow::on_actionFilterTags_triggered()

View file

@ -203,8 +203,7 @@ private:
bool plannerStateClean(); bool plannerStateClean();
void setupSocialNetworkMenu(); void setupSocialNetworkMenu();
QDialog *findMovedImagesDialog; QDialog *findMovedImagesDialog;
struct dive copyPasteDive; dive_paste_data paste_data;
struct dive_components what;
QStringList recentFiles; QStringList recentFiles;
QAction *actionsRecent[NUM_RECENT_FILES]; QAction *actionsRecent[NUM_RECENT_FILES];

View file

@ -9,7 +9,6 @@
#include <QAction> #include <QAction>
#include <QDesktopServices> #include <QDesktopServices>
#include <QToolTip> #include <QToolTip>
#include <QClipboard>
#include <QCompleter> #include <QCompleter>
#include "core/file.h" #include "core/file.h"
@ -277,93 +276,6 @@ QString URLDialog::url() const
return ui.urlField->toPlainText(); return ui.urlField->toPlainText();
} }
#define COMPONENT_FROM_UI(_component) what->_component = ui._component->isChecked()
#define UI_FROM_COMPONENT(_component) ui._component->setChecked(what->_component)
DiveComponentSelection::DiveComponentSelection(QWidget *parent, struct dive *target, struct dive_components *_what) : targetDive(target)
{
ui.setupUi(this);
what = _what;
UI_FROM_COMPONENT(divesite);
UI_FROM_COMPONENT(diveguide);
UI_FROM_COMPONENT(buddy);
UI_FROM_COMPONENT(rating);
UI_FROM_COMPONENT(visibility);
UI_FROM_COMPONENT(notes);
UI_FROM_COMPONENT(suit);
UI_FROM_COMPONENT(tags);
UI_FROM_COMPONENT(cylinders);
UI_FROM_COMPONENT(weights);
UI_FROM_COMPONENT(number);
UI_FROM_COMPONENT(when);
connect(ui.buttonBox, &QDialogButtonBox::clicked, this, &DiveComponentSelection::buttonClicked);
QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_W), this);
connect(close, &QShortcut::activated, this, &DiveComponentSelection::close);
QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this);
connect(quit, &QShortcut::activated, parent, &QWidget::close);
}
void DiveComponentSelection::buttonClicked(QAbstractButton *button)
{
if (current_dive && ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) {
COMPONENT_FROM_UI(divesite);
COMPONENT_FROM_UI(diveguide);
COMPONENT_FROM_UI(buddy);
COMPONENT_FROM_UI(rating);
COMPONENT_FROM_UI(visibility);
COMPONENT_FROM_UI(notes);
COMPONENT_FROM_UI(suit);
COMPONENT_FROM_UI(tags);
COMPONENT_FROM_UI(cylinders);
COMPONENT_FROM_UI(weights);
COMPONENT_FROM_UI(number);
COMPONENT_FROM_UI(when);
selective_copy_dive(current_dive, targetDive, *what, true);
QClipboard *clipboard = QApplication::clipboard();
QTextStream text;
QString cliptext;
text.setString(&cliptext);
if (what->divesite && current_dive->dive_site)
text << tr("Dive site: ") << QString::fromStdString(current_dive->dive_site->name) << "\n";
if (what->diveguide)
text << tr("Dive guide: ") << QString::fromStdString(current_dive->diveguide) << "\n";
if (what->buddy)
text << tr("Buddy: ") << QString::fromStdString(current_dive->buddy) << "\n";
if (what->rating)
text << tr("Rating: ") + QString("*").repeated(current_dive->rating) << "\n";
if (what->visibility)
text << tr("Visibility: ") + QString("*").repeated(current_dive->visibility) << "\n";
if (what->notes)
text << tr("Notes:\n") << QString::fromStdString(current_dive->notes) << "\n";
if (what->suit)
text << tr("Suit: ") << QString::fromStdString(current_dive->suit) << "\n";
if (what-> tags) {
text << tr("Tags: ");
for (const divetag *tag: current_dive->tags)
text << tag->name.c_str() << " ";
text << "\n";
}
if (what->cylinders) {
text << tr("Cylinders:\n");
for (auto [idx, cyl]: enumerated_range(current_dive->cylinders)) {
if (current_dive->is_cylinder_used(idx))
text << QString::fromStdString(cyl.type.description) << " "
<< QString::fromStdString(cyl.gasmix.name()) << "\n";
}
}
if (what->weights) {
text << tr("Weights:\n");
for (auto &ws: current_dive->weightsystems)
text << QString::fromStdString(ws.description) << ws.weight.grams / 1000 << "kg\n";
}
if (what->number)
text << tr("Dive number: ") << current_dive->number << "\n";
if (what->when)
text << tr("Date / time: ") << get_dive_date_string(current_dive->when) << "\n";
clipboard->setText(cliptext);
}
}
AddFilterPresetDialog::AddFilterPresetDialog(const QString &defaultName, QWidget *parent) AddFilterPresetDialog::AddFilterPresetDialog(const QString &defaultName, QWidget *parent)
{ {
ui.setupUi(this); ui.setupUi(this);

View file

@ -7,7 +7,6 @@ class QAbstractButton;
class QNetworkReply; class QNetworkReply;
class FilterModelBase; class FilterModelBase;
struct dive; struct dive;
struct dive_components;
#include "core/units.h" #include "core/units.h"
#include <QWidget> #include <QWidget>
@ -20,7 +19,6 @@ struct dive_components;
#include "ui_shifttimes.h" #include "ui_shifttimes.h"
#include "ui_shiftimagetimes.h" #include "ui_shiftimagetimes.h"
#include "ui_urldialog.h" #include "ui_urldialog.h"
#include "ui_divecomponentselection.h"
#include "ui_listfilter.h" #include "ui_listfilter.h"
#include "ui_addfilterpreset.h" #include "ui_addfilterpreset.h"
@ -102,20 +100,6 @@ private:
Ui::URLDialog ui; Ui::URLDialog ui;
}; };
class DiveComponentSelection : public QDialog {
Q_OBJECT
public:
explicit DiveComponentSelection(QWidget *parent, struct dive *target, struct dive_components *_what);
private
slots:
void buttonClicked(QAbstractButton *button);
private:
Ui::DiveComponentSelectionDialog ui;
struct dive *targetDive;
struct dive_components *what;
};
class AddFilterPresetDialog : public QDialog { class AddFilterPresetDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:

View file

@ -38,11 +38,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleDiveSite(false) checked: manager.pasteDiveSite
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleDiveSite(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Notes") text: qsTr("Notes")
@ -50,11 +47,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleNotes(false) checked: manager.pasteNotes
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleNotes(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Dive guide") text: qsTr("Dive guide")
@ -62,11 +56,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleDiveGuide(false) checked: manager.pasteDiveGuide
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleDiveGuide(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Buddy") text: qsTr("Buddy")
@ -74,11 +65,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleBuddy(false) checked: manager.pasteBuddy
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleBuddy(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Suit") text: qsTr("Suit")
@ -86,11 +74,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleSuit(false) checked: manager.pasteSuit
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleSuit(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Rating") text: qsTr("Rating")
@ -98,11 +83,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleRating(false) checked: manager.pasteRating
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleRating(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Visibility") text: qsTr("Visibility")
@ -110,11 +92,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleVisibility(false) checked: manager.pasteVisibility
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleVisibility(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Tags") text: qsTr("Tags")
@ -122,11 +101,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleTags(false) checked: manager.pasteTags
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleTags(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Cylinders") text: qsTr("Cylinders")
@ -134,11 +110,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleCylinders(false) checked: manager.pasteCylinders
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleCylinders(true)
}
} }
Controls.Label { Controls.Label {
text: qsTr("Weights") text: qsTr("Weights")
@ -146,11 +119,8 @@ Kirigami.ScrollablePage {
Layout.preferredWidth: gridWidth * 0.75 Layout.preferredWidth: gridWidth * 0.75
} }
SsrfSwitch { SsrfSwitch {
checked: manager.toggleWeights(false) checked: manager.pasteWeights
Layout.preferredWidth: gridWidth * 0.25 Layout.preferredWidth: gridWidth * 0.25
onClicked: {
manager.toggleWeights(true)
}
} }
} }

View file

@ -303,17 +303,18 @@ QMLManager::QMLManager() :
// make sure we know if the current cloud repo has been successfully synced // make sure we know if the current cloud repo has been successfully synced
syncLoadFromCloud(); syncLoadFromCloud();
memset(&m_copyPasteDive, 0, sizeof(m_copyPasteDive));
memset(&what, 0, sizeof(what));
// Let's set some defaults to be copied so users don't necessarily need // Let's set some defaults to be copied so users don't necessarily need
// to know how to configure this // to know how to configure this
what.diveguide = true; m_pasteDiveSite = false;
what.buddy = true; m_pasteNotes = false;
what.suit = true; m_pasteDiveGuide = true;
what.tags = true; m_pasteBuddy = true;
what.cylinders = true; m_pasteSuit = true;
what.weights = true; m_pasteRating = false;
m_pasteVisibility = false;
m_pasteTags = true;
m_pasteCylinders = true;
m_pasteWeights = true;
// monitor when dives changed - but only in verbose mode // monitor when dives changed - but only in verbose mode
// careful - changing verbose at runtime isn't enough (of course that could be added if we want it) // careful - changing verbose at runtime isn't enough (of course that could be added if we want it)
@ -1607,104 +1608,40 @@ void QMLManager::toggleDiveInvalid(int id)
changesNeedSaving(); changesNeedSaving();
} }
bool QMLManager::toggleDiveSite(bool toggle) template <typename T>
static void assign_paste_data(bool copy, const T &src, std::optional<T> &dest)
{ {
if (toggle) if (copy)
what.divesite = what.divesite ? false : true; dest = src;
else
return what.divesite; dest = {};
}
bool QMLManager::toggleNotes(bool toggle)
{
if (toggle)
what.notes = what.notes ? false : true;
return what.notes;
}
bool QMLManager::toggleDiveGuide(bool toggle)
{
if (toggle)
what.diveguide = what.diveguide ? false : true;
return what.diveguide;
}
bool QMLManager::toggleBuddy(bool toggle)
{
if (toggle)
what.buddy = what.buddy ? false : true;
return what.buddy;
}
bool QMLManager::toggleSuit(bool toggle)
{
if (toggle)
what.suit = what.suit ? false : true;
return what.suit;
}
bool QMLManager::toggleRating(bool toggle)
{
if (toggle)
what.rating = what.rating ? false : true;
return what.rating;
}
bool QMLManager::toggleVisibility(bool toggle)
{
if (toggle)
what.visibility = what.visibility ? false : true;
return what.visibility;
}
bool QMLManager::toggleTags(bool toggle)
{
if (toggle)
what.tags = what.tags ? false : true;
return what.tags;
}
bool QMLManager::toggleCylinders(bool toggle)
{
if (toggle)
what.cylinders = what.cylinders ? false : true;
return what.cylinders;
}
bool QMLManager::toggleWeights(bool toggle)
{
if (toggle)
what.weights = what.weights ? false : true;
return what.weights;
} }
void QMLManager::copyDiveData(int id) void QMLManager::copyDiveData(int id)
{ {
m_copyPasteDive = divelog.dives.get_by_uniq_id(id); const dive *d = divelog.dives.get_by_uniq_id(id);
if (!m_copyPasteDive) { if (!d) {
appendTextToLog("trying to copy non-existing dive"); appendTextToLog("trying to copy non-existing dive");
return; return;
} }
assign_paste_data(m_pasteDiveSite, d->dive_site ? d->dive_site->uuid : uint32_t(), paste_data.divesite);
assign_paste_data(m_pasteNotes, d->notes, paste_data.notes);
assign_paste_data(m_pasteDiveGuide, d->diveguide, paste_data.diveguide);
assign_paste_data(m_pasteBuddy, d->buddy, paste_data.buddy);
assign_paste_data(m_pasteSuit, d->suit, paste_data.suit);
assign_paste_data(m_pasteRating, d->rating, paste_data.rating);
assign_paste_data(m_pasteVisibility, d->visibility, paste_data.visibility);
assign_paste_data(m_pasteTags, d->tags, paste_data.tags);
assign_paste_data(m_pasteCylinders, d->cylinders, paste_data.cylinders);
assign_paste_data(m_pasteWeights, d->weightsystems, paste_data.weights);
setNotificationText("Copy"); setNotificationText("Copy");
} }
void QMLManager::pasteDiveData(int id) void QMLManager::pasteDiveData(int id)
{ {
if (!m_copyPasteDive) { Command::pasteDives(paste_data);
appendTextToLog("dive to paste is not selected");
return;
}
Command::pasteDives(m_copyPasteDive, what);
changesNeedSaving(); changesNeedSaving();
} }

View file

@ -43,6 +43,17 @@ class QMLManager : public QObject {
Q_PROPERTY(QString progressMessage MEMBER m_progressMessage WRITE setProgressMessage NOTIFY progressMessageChanged) Q_PROPERTY(QString progressMessage MEMBER m_progressMessage WRITE setProgressMessage NOTIFY progressMessageChanged)
Q_PROPERTY(bool btEnabled MEMBER m_btEnabled WRITE setBtEnabled NOTIFY btEnabledChanged) Q_PROPERTY(bool btEnabled MEMBER m_btEnabled WRITE setBtEnabled NOTIFY btEnabledChanged)
Q_PROPERTY(bool pasteDiveSite MEMBER m_pasteDiveSite)
Q_PROPERTY(bool pasteNotes MEMBER m_pasteNotes)
Q_PROPERTY(bool pasteDiveGuide MEMBER m_pasteDiveGuide)
Q_PROPERTY(bool pasteBuddy MEMBER m_pasteBuddy)
Q_PROPERTY(bool pasteSuit MEMBER m_pasteSuit)
Q_PROPERTY(bool pasteRating MEMBER m_pasteRating)
Q_PROPERTY(bool pasteVisibility MEMBER m_pasteVisibility)
Q_PROPERTY(bool pasteTags MEMBER m_pasteTags)
Q_PROPERTY(bool pasteCylinders MEMBER m_pasteCylinders)
Q_PROPERTY(bool pasteWeights MEMBER m_pasteWeights)
Q_PROPERTY(QString DC_vendor READ DC_vendor WRITE DC_setVendor) Q_PROPERTY(QString DC_vendor READ DC_vendor WRITE DC_setVendor)
Q_PROPERTY(QString DC_product READ DC_product WRITE DC_setProduct) Q_PROPERTY(QString DC_product READ DC_product WRITE DC_setProduct)
Q_PROPERTY(QString DC_devName READ DC_devName WRITE DC_setDevName) Q_PROPERTY(QString DC_devName READ DC_devName WRITE DC_setDevName)
@ -187,16 +198,6 @@ public slots:
void toggleDiveInvalid(int id); void toggleDiveInvalid(int id);
void copyDiveData(int id); void copyDiveData(int id);
void pasteDiveData(int id); void pasteDiveData(int id);
bool toggleDiveSite(bool toggle);
bool toggleNotes(bool toggle);
bool toggleDiveGuide(bool toggle);
bool toggleBuddy(bool toggle);
bool toggleSuit(bool toggle);
bool toggleRating(bool toggle);
bool toggleVisibility(bool toggle);
bool toggleTags(bool toggle);
bool toggleCylinders(bool toggle);
bool toggleWeights(bool toggle);
void undo(); void undo();
void redo(); void redo();
int addDive(); int addDive();
@ -250,11 +251,21 @@ private:
void updateAllGlobalLists(); void updateAllGlobalLists();
void updateHaveLocalChanges(bool status); void updateHaveLocalChanges(bool status);
bool m_pasteDiveSite;
bool m_pasteNotes;
bool m_pasteDiveGuide;
bool m_pasteBuddy;
bool m_pasteSuit;
bool m_pasteRating;
bool m_pasteVisibility;
bool m_pasteTags;
bool m_pasteCylinders;
bool m_pasteWeights;
location_t getGps(QString &gps); location_t getGps(QString &gps);
QString m_pluggedInDeviceName; QString m_pluggedInDeviceName;
bool m_showNonDiveComputers; bool m_showNonDiveComputers;
struct dive *m_copyPasteDive = NULL; struct dive_paste_data paste_data;
struct dive_components what;
QAction *undoAction; QAction *undoAction;
bool verifyCredentials(QString email, QString password, QString pin); bool verifyCredentials(QString email, QString password, QString pin);