From 152e6966c9a20261181ec874473fbec1efb6c950 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 13 Aug 2024 07:04:52 +0200 Subject: [PATCH] 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 --- commands/command.cpp | 4 +- commands/command.h | 3 +- commands/command_edit.cpp | 200 +++++++++++---------- commands/command_edit.h | 43 ++--- core/dive.cpp | 34 ---- core/dive.h | 39 ++-- core/range.h | 12 +- desktop-widgets/CMakeLists.txt | 2 + desktop-widgets/divecomponentselection.cpp | 104 +++++++++++ desktop-widgets/divecomponentselection.h | 22 +++ desktop-widgets/mainwindow.cpp | 6 +- desktop-widgets/mainwindow.h | 3 +- desktop-widgets/simplewidgets.cpp | 88 --------- desktop-widgets/simplewidgets.h | 16 -- mobile-widgets/qml/CopySettings.qml | 50 ++---- mobile-widgets/qmlmanager.cpp | 123 ++++--------- mobile-widgets/qmlmanager.h | 35 ++-- 17 files changed, 359 insertions(+), 425 deletions(-) create mode 100644 desktop-widgets/divecomponentselection.cpp create mode 100644 desktop-widgets/divecomponentselection.h diff --git a/commands/command.cpp b/commands/command.cpp index 9778c5444..8381101bd 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -267,9 +267,9 @@ int editDiveGuide(const QStringList &newList, bool 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) diff --git a/commands/command.h b/commands/command.h index cdd4837d8..826557f3b 100644 --- a/commands/command.h +++ b/commands/command.h @@ -13,6 +13,7 @@ struct divecomputer; struct divelog; struct dive_components; +struct dive_paste_data; struct dive_site; struct dive_trip; struct event; @@ -95,7 +96,7 @@ int editDiveSiteNew(const QString &newName, bool currentDiveOnly); int editTags(const QStringList &newList, bool currentDiveOnly); int editBuddies(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 { ADD, REMOVE, diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index c21493369..57c30ed91 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -613,41 +613,51 @@ QString EditDiveGuide::fieldName() const return Command::Base::tr("dive guide"); } -PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dIn) +template +ptrdiff_t comp_ptr(T *v1, T *v2) { - if (what.notes) - notes = data->notes; - if (what.diveguide) - diveguide = data->diveguide; - 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 = data->tags; - if (what.cylinders) { - cylinders = data->cylinders; + return v1 - v2; +} + +PasteState::PasteState(dive &d, const dive_paste_data &data, std::vector &dive_sites_changed) : d(d) +{ + notes = data.notes; + diveguide = data.diveguide; + buddy = data.buddy; + suit = data.suit; + rating = data.rating; + visibility = data.visibility; + wavesize = data.wavesize; + current = data.current; + surge = data.surge; + chill = data.chill; + if (data.divesite.has_value()) { + if (data.divesite) { + // In the undo system, we can turn the uuid into a pointer, + // because everything is serialized. + dive_site *ds = divelog.sites.get_by_uuid(*data.divesite); + if (ds) + divesite = ds; + } else { + divesite = nullptr; + } + } + if (divesite.has_value() && *divesite != d.dive_site) { + if (d.dive_site) + range_insert_sorted_unique(dive_sites_changed, d.dive_site, comp_ptr); // Use <=> once on C++20 + if (*divesite) + range_insert_sorted_unique(dive_sites_changed, *divesite, comp_ptr); // Use <=> once on C++20 + } + tags = data.tags; + if (data.cylinders.has_value()) { + cylinders = data.cylinders; // Paste cylinders is "special": // 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. // 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) { - const cylinder_t &src = d->cylinders[i]; - cylinder_t &dst = cylinders[i]; + for (size_t i = 0; i < data.cylinders->size() && i < cylinders->size(); ++i) { + const cylinder_t &src = (*data.cylinders)[i]; + cylinder_t &dst = (*cylinders)[i]; dst.gasmix = src.gasmix; dst.start = src.start; 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_he = src.bestmix_he; } - for (size_t i = d->cylinders.size(); i < cylinders.size(); ++i) { - cylinder_t &cyl = cylinders[i]; + for (size_t i = data.cylinders->size(); i < cylinders->size(); ++i) { + cylinder_t &cyl = (*cylinders)[i]; cyl.start.mbar = 0; cyl.end.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; } } - if (what.weights) - weightsystems = data->weightsystems; - if (what.number) - number = data->number; - if (what.when) - when = data->when; + weightsystems = data.weights; + number = data.number; + when = data.when; } PasteState::~PasteState() { } -void PasteState::swap(dive_components what) +void PasteState::swap() { - if (what.notes) - std::swap(notes, d->notes); - if (what.diveguide) - std::swap(diveguide, d->diveguide); - if (what.buddy) - std::swap(buddy, d->buddy); - if (what.suit) - std::swap(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->tags); - if (what.cylinders) - std::swap(cylinders, d->cylinders); - if (what.weights) - std::swap(weightsystems, d->weightsystems); - if (what.number) - std::swap(number, d->number); - if (what.when) - std::swap(when, d->when); + if (notes.has_value()) + std::swap(*notes, d.notes); + if (diveguide.has_value()) + std::swap(*diveguide, d.diveguide); + if (buddy.has_value()) + std::swap(*buddy, d.buddy); + if (suit.has_value()) + std::swap(*suit, d.suit); + if (rating.has_value()) + std::swap(*rating, d.rating); + if (visibility.has_value()) + std::swap(*visibility, d.visibility); + if (wavesize.has_value()) + std::swap(*wavesize, d.wavesize); + if (current.has_value()) + std::swap(*current, d.current); + if (surge.has_value()) + std::swap(*surge, d.surge); + if (chill.has_value()) + std::swap(*chill, d.chill); + if (divesite.has_value() && *divesite != d.dive_site) { + auto old_ds = unregister_dive_from_dive_site(&d); + if (*divesite) + (*divesite)->add_dive(&d); + divesite = old_ds; + } + if (tags.has_value()) + std::swap(*tags, d.tags); + if (cylinders.has_value()) + std::swap(*cylinders, d.cylinders); + if (weightsystems.has_value()) + std::swap(*weightsystems, d.weightsystems); + if (number.has_value()) + std::swap(*number, d.number); + if (when.has_value()) + std::swap(*when, d.when); } // ***** Paste ***** -PasteDives::PasteDives(const dive *data, dive_components whatIn) : what(whatIn) +PasteDives::PasteDives(const dive_paste_data &data) { std::vector selection = getDiveSelection(); dives.reserve(selection.size()); 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))); } @@ -739,32 +750,35 @@ void PasteDives::undo() QVector 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); - state.d->invalidate_cache(); // Ensure that dive is written in git_save() + divesToNotify.push_back(&state.d); + state.swap(); + 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); - fields.notes = what.notes; - fields.diveguide = what.diveguide; - 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; - fields.datetime = what.when; - fields.nr = what.number; + const PasteState &state = dives[0]; + fields.notes = state.notes.has_value(); + fields.diveguide = state.diveguide.has_value(); + fields.buddy = state.buddy.has_value(); + fields.suit = state.suit.has_value(); + fields.rating = state.rating.has_value(); + fields.visibility = state.visibility.has_value(); + fields.wavesize = state.wavesize.has_value(); + fields.current = state.current.has_value(); + fields.surge = state.surge.has_value(); + fields.chill = state.chill.has_value(); + fields.divesite = !dive_sites_changed.empty(); + fields.tags = state.tags.has_value(); + fields.datetime = state.when.has_value(); + fields.nr = state.number.has_value(); emit diveListNotifier.divesChanged(divesToNotify, fields); - if (what.cylinders) + if (state.cylinders.has_value()) emit diveListNotifier.cylindersReset(divesToNotify); - if (what.weights) + if (state.weightsystems.has_value()) emit diveListNotifier.weightsystemsReset(divesToNotify); + for (dive_site *ds: dive_sites_changed) + emit diveListNotifier.diveSiteDivesChanged(ds); } // Redo and undo do the same diff --git a/commands/command_edit.h b/commands/command_edit.h index 26bdabe7d..29e8b5ed2 100644 --- a/commands/command_edit.h +++ b/commands/command_edit.h @@ -287,34 +287,35 @@ public: // Fields we have to remember to undo paste struct PasteState { - dive *d; - dive_site *divesite; - std::string notes; - std::string diveguide; - std::string buddy; - std::string suit; - int rating; - int wavesize; - int visibility; - int current; - int surge; - int chill; - tag_list tags; - cylinder_table cylinders; - weightsystem_table weightsystems; - int number; - timestamp_t when; + dive &d; + std::optional divesite; + std::optional notes; + std::optional diveguide; + std::optional buddy; + std::optional suit; + std::optional rating; + std::optional wavesize; + std::optional visibility; + std::optional current; + std::optional surge; + std::optional chill; + std::optional tags; + std::optional cylinders; + std::optional weightsystems; + std::optional number; + std::optional 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 &changed_dive_sites); ~PasteState(); - void swap(dive_components what); // Exchange values here and in dive + void swap(); // Exchange values here and in dive }; class PasteDives : public Base { - dive_components what; + dive_paste_data data; std::vector dives; + std::vector dive_sites_changed; public: - PasteDives(const dive *d, dive_components what); + PasteDives(const dive_paste_data &data); private: void undo() override; void redo() override; diff --git a/core/dive.cpp b/core/dive.cpp index 34de44de7..81330d1fa 100644 --- a/core/dive.cpp +++ b/core/dive.cpp @@ -203,40 +203,6 @@ void copy_dive(const struct dive *s, struct dive *d) 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 this is used when editing a dive in the planner to preserve the events of the old dive */ diff --git a/core/dive.h b/core/dive.h index 0c3b14f82..03a19dd54 100644 --- a/core/dive.h +++ b/core/dive.h @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -146,24 +147,25 @@ struct dive_or_trip { 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); -/* when selectively copying dive information, which parts should be copied? */ -struct dive_components { - unsigned int divesite : 1; - unsigned int notes : 1; - unsigned int diveguide : 1; - unsigned int buddy : 1; - unsigned int suit : 1; - unsigned int rating : 1; - unsigned int visibility : 1; - unsigned int wavesize : 1; - unsigned int current : 1; - unsigned int surge : 1; - unsigned int chill : 1; - unsigned int tags : 1; - unsigned int cylinders : 1; - unsigned int weights : 1; - unsigned int number : 1; - unsigned int when : 1; +/* Data stored when copying a dive */ +struct dive_paste_data { + std::optional divesite; // We save the uuid not a pointer, because the + // user might copy and then delete the dive site. + std::optional notes; + std::optional diveguide; + std::optional buddy; + std::optional suit; + std::optional rating; + std::optional visibility; + std::optional wavesize; + std::optional current; + std::optional surge; + std::optional chill; + std::optional tags; + std::optional cylinders; + std::optional weights; + std::optional number; + std::optional when; }; extern std::unique_ptr 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 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); diff --git a/core/range.h b/core/range.h index 7f5bc0f89..48178ecb3 100644 --- a/core/range.h +++ b/core/range.h @@ -181,13 +181,23 @@ bool range_contains(const Range &v, const Element &item) // Insert into an already sorted range template -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, [&comp](auto &a, auto &b) { return comp(a, b) < 0; }); v.insert(it, std::move(item)); } +// Insert into an already sorted range, but don't add an item twice +template +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 void range_remove(Range &v, const Element &item) { diff --git a/desktop-widgets/CMakeLists.txt b/desktop-widgets/CMakeLists.txt index 4d4ed03c9..fb09f51f6 100644 --- a/desktop-widgets/CMakeLists.txt +++ b/desktop-widgets/CMakeLists.txt @@ -68,6 +68,8 @@ set(SUBSURFACE_INTERFACE about.h configuredivecomputerdialog.cpp configuredivecomputerdialog.h + divecomponentselection.cpp + divecomponentselection.h divelistview.cpp divelistview.h divelogexportdialog.cpp diff --git a/desktop-widgets/divecomponentselection.cpp b/desktop-widgets/divecomponentselection.cpp new file mode 100644 index 000000000..0603a9081 --- /dev/null +++ b/desktop-widgets/divecomponentselection.cpp @@ -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 +#include + +template +static void assign_paste_data(QCheckBox &checkbox, const T &src, std::optional &dest) +{ + if (checkbox.isChecked()) + dest = src; + else + dest = {}; +} + +template +static void set_checked(QCheckBox &checkbox, const std::optional &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)); + } +} diff --git a/desktop-widgets/divecomponentselection.h b/desktop-widgets/divecomponentselection.h new file mode 100644 index 000000000..00aa5122f --- /dev/null +++ b/desktop-widgets/divecomponentselection.h @@ -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 diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 20846cbc3..595ba791f 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -38,6 +38,7 @@ #include "core/settings/qPrefDisplay.h" #include "desktop-widgets/about.h" +#include "desktop-widgets/divecomponentselection.h" #include "desktop-widgets/divelistview.h" #include "desktop-widgets/divelogexportdialog.h" #include "desktop-widgets/divelogimportdialog.h" @@ -207,7 +208,6 @@ MainWindow::MainWindow() : #ifdef NO_USERMANUAL ui.menuHelp->removeAction(ui.actionUserManual); #endif - memset(&what, 0, sizeof(what)); updateManager = new UpdateManager(this); undoAction = Command::undoAction(this); @@ -1424,13 +1424,13 @@ void MainWindow::on_copy_triggered() { // open dialog to select what gets copied // copy the displayed dive - DiveComponentSelection dialog(this, ©PasteDive, &what); + DiveComponentSelection dialog(paste_data, this); dialog.exec(); } void MainWindow::on_paste_triggered() { - Command::pasteDives(©PasteDive, what); + Command::pasteDives(paste_data); } void MainWindow::on_actionFilterTags_triggered() diff --git a/desktop-widgets/mainwindow.h b/desktop-widgets/mainwindow.h index 12587347b..6e757b216 100644 --- a/desktop-widgets/mainwindow.h +++ b/desktop-widgets/mainwindow.h @@ -203,8 +203,7 @@ private: bool plannerStateClean(); void setupSocialNetworkMenu(); QDialog *findMovedImagesDialog; - struct dive copyPasteDive; - struct dive_components what; + dive_paste_data paste_data; QStringList recentFiles; QAction *actionsRecent[NUM_RECENT_FILES]; diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index 6b80cb549..712167ed3 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "core/file.h" @@ -277,93 +276,6 @@ QString URLDialog::url() const 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) { ui.setupUi(this); diff --git a/desktop-widgets/simplewidgets.h b/desktop-widgets/simplewidgets.h index 72011d71d..585c4b3f4 100644 --- a/desktop-widgets/simplewidgets.h +++ b/desktop-widgets/simplewidgets.h @@ -7,7 +7,6 @@ class QAbstractButton; class QNetworkReply; class FilterModelBase; struct dive; -struct dive_components; #include "core/units.h" #include @@ -20,7 +19,6 @@ struct dive_components; #include "ui_shifttimes.h" #include "ui_shiftimagetimes.h" #include "ui_urldialog.h" -#include "ui_divecomponentselection.h" #include "ui_listfilter.h" #include "ui_addfilterpreset.h" @@ -102,20 +100,6 @@ private: 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 { Q_OBJECT public: diff --git a/mobile-widgets/qml/CopySettings.qml b/mobile-widgets/qml/CopySettings.qml index 17bf8dbf0..910803e36 100644 --- a/mobile-widgets/qml/CopySettings.qml +++ b/mobile-widgets/qml/CopySettings.qml @@ -38,11 +38,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleDiveSite(false) + checked: manager.pasteDiveSite Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleDiveSite(true) - } } Controls.Label { text: qsTr("Notes") @@ -50,11 +47,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleNotes(false) + checked: manager.pasteNotes Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleNotes(true) - } } Controls.Label { text: qsTr("Dive guide") @@ -62,11 +56,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleDiveGuide(false) + checked: manager.pasteDiveGuide Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleDiveGuide(true) - } } Controls.Label { text: qsTr("Buddy") @@ -74,11 +65,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleBuddy(false) + checked: manager.pasteBuddy Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleBuddy(true) - } } Controls.Label { text: qsTr("Suit") @@ -86,11 +74,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleSuit(false) + checked: manager.pasteSuit Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleSuit(true) - } } Controls.Label { text: qsTr("Rating") @@ -98,11 +83,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleRating(false) + checked: manager.pasteRating Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleRating(true) - } } Controls.Label { text: qsTr("Visibility") @@ -110,11 +92,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleVisibility(false) + checked: manager.pasteVisibility Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleVisibility(true) - } } Controls.Label { text: qsTr("Tags") @@ -122,11 +101,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleTags(false) + checked: manager.pasteTags Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleTags(true) - } } Controls.Label { text: qsTr("Cylinders") @@ -134,11 +110,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleCylinders(false) + checked: manager.pasteCylinders Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleCylinders(true) - } } Controls.Label { text: qsTr("Weights") @@ -146,11 +119,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleWeights(false) + checked: manager.pasteWeights Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleWeights(true) - } } } diff --git a/mobile-widgets/qmlmanager.cpp b/mobile-widgets/qmlmanager.cpp index 0688ecb89..b57599229 100644 --- a/mobile-widgets/qmlmanager.cpp +++ b/mobile-widgets/qmlmanager.cpp @@ -303,17 +303,18 @@ QMLManager::QMLManager() : // make sure we know if the current cloud repo has been successfully synced 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 // to know how to configure this - what.diveguide = true; - what.buddy = true; - what.suit = true; - what.tags = true; - what.cylinders = true; - what.weights = true; + m_pasteDiveSite = false; + m_pasteNotes = false; + m_pasteDiveGuide = true; + m_pasteBuddy = true; + m_pasteSuit = 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 // 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(); } -bool QMLManager::toggleDiveSite(bool toggle) +template +static void assign_paste_data(bool copy, const T &src, std::optional &dest) { - if (toggle) - what.divesite = what.divesite ? false : true; - - return what.divesite; -} - -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; + if (copy) + dest = src; + else + dest = {}; } void QMLManager::copyDiveData(int id) { - m_copyPasteDive = divelog.dives.get_by_uniq_id(id); - if (!m_copyPasteDive) { + const dive *d = divelog.dives.get_by_uniq_id(id); + if (!d) { appendTextToLog("trying to copy non-existing dive"); 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"); } void QMLManager::pasteDiveData(int id) { - if (!m_copyPasteDive) { - appendTextToLog("dive to paste is not selected"); - return; - } - Command::pasteDives(m_copyPasteDive, what); + Command::pasteDives(paste_data); changesNeedSaving(); } diff --git a/mobile-widgets/qmlmanager.h b/mobile-widgets/qmlmanager.h index 9a24b5399..b93bf6b80 100644 --- a/mobile-widgets/qmlmanager.h +++ b/mobile-widgets/qmlmanager.h @@ -43,6 +43,17 @@ class QMLManager : public QObject { 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 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_product READ DC_product WRITE DC_setProduct) Q_PROPERTY(QString DC_devName READ DC_devName WRITE DC_setDevName) @@ -187,16 +198,6 @@ public slots: void toggleDiveInvalid(int id); void copyDiveData(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 redo(); int addDive(); @@ -250,11 +251,21 @@ private: void updateAllGlobalLists(); 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); QString m_pluggedInDeviceName; bool m_showNonDiveComputers; - struct dive *m_copyPasteDive = NULL; - struct dive_components what; + struct dive_paste_data paste_data; QAction *undoAction; bool verifyCredentials(QString email, QString password, QString pin);