mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
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:
parent
48b4308a7d
commit
152e6966c9
17 changed files with 359 additions and 425 deletions
|
@ -68,6 +68,8 @@ set(SUBSURFACE_INTERFACE
|
|||
about.h
|
||||
configuredivecomputerdialog.cpp
|
||||
configuredivecomputerdialog.h
|
||||
divecomponentselection.cpp
|
||||
divecomponentselection.h
|
||||
divelistview.cpp
|
||||
divelistview.h
|
||||
divelogexportdialog.cpp
|
||||
|
|
104
desktop-widgets/divecomponentselection.cpp
Normal file
104
desktop-widgets/divecomponentselection.cpp
Normal 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));
|
||||
}
|
||||
}
|
22
desktop-widgets/divecomponentselection.h
Normal file
22
desktop-widgets/divecomponentselection.h
Normal 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
|
|
@ -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()
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
#include <QToolTip>
|
||||
#include <QClipboard>
|
||||
#include <QCompleter>
|
||||
|
||||
#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);
|
||||
|
|
|
@ -7,7 +7,6 @@ class QAbstractButton;
|
|||
class QNetworkReply;
|
||||
class FilterModelBase;
|
||||
struct dive;
|
||||
struct dive_components;
|
||||
|
||||
#include "core/units.h"
|
||||
#include <QWidget>
|
||||
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue