mirror of
https://github.com/subsurface/subsurface.git
synced 2025-01-31 21:53:23 +00:00
Undo: implement rudimentary undo of dive-notes editing
Implement a first rudimentary dive-editing command. The main code resides in a base class Command::Edit, which calls virtual functions to read / set the fields and extract the field name. Implement an example: editing of dive notes. This dose not yet update the UI on undo / redo. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
8858bfa1f8
commit
9e603cbe2b
7 changed files with 177 additions and 12 deletions
|
@ -64,6 +64,8 @@ set(SUBSURFACE_INTERFACE
|
||||||
command_divelist.h
|
command_divelist.h
|
||||||
command_divesite.cpp
|
command_divesite.cpp
|
||||||
command_divesite.h
|
command_divesite.h
|
||||||
|
command_edit.cpp
|
||||||
|
command_edit.h
|
||||||
configuredivecomputerdialog.cpp
|
configuredivecomputerdialog.cpp
|
||||||
configuredivecomputerdialog.h
|
configuredivecomputerdialog.h
|
||||||
divecomputermanagementdialog.cpp
|
divecomputermanagementdialog.cpp
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "command_divelist.h"
|
#include "command_divelist.h"
|
||||||
#include "command_divesite.h"
|
#include "command_divesite.h"
|
||||||
|
#include "command_edit.h"
|
||||||
|
|
||||||
namespace Command {
|
namespace Command {
|
||||||
|
|
||||||
|
@ -128,4 +129,10 @@ void purgeUnusedDiveSites()
|
||||||
execute(new PurgeUnusedDiveSites);
|
execute(new PurgeUnusedDiveSites);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dive editing related commands
|
||||||
|
void editNotes(const QVector<dive *> dives, const QString &newValue, const QString &oldValue)
|
||||||
|
{
|
||||||
|
execute(new EditNotes(dives, newValue, oldValue));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Command
|
} // namespace Command
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
namespace Command {
|
namespace Command {
|
||||||
|
|
||||||
// 1) General commands
|
// 1) General commands
|
||||||
|
|
||||||
void clear(); // Reset the undo stack. Delete all commands.
|
void clear(); // Reset the undo stack. Delete all commands.
|
||||||
QAction *undoAction(QObject *parent); // Create an undo action.
|
QAction *undoAction(QObject *parent); // Create an undo action.
|
||||||
QAction *redoAction(QObject *parent); // Create an redo action.
|
QAction *redoAction(QObject *parent); // Create an redo action.
|
||||||
|
@ -51,6 +50,10 @@ void addDiveSite(const QString &name);
|
||||||
void mergeDiveSites(dive_site *ds, const QVector<dive_site *> &sites);
|
void mergeDiveSites(dive_site *ds, const QVector<dive_site *> &sites);
|
||||||
void purgeUnusedDiveSites();
|
void purgeUnusedDiveSites();
|
||||||
|
|
||||||
|
// 4) Dive editing related commands
|
||||||
|
|
||||||
|
void editNotes(const QVector<dive *> dives, const QString &newValue, const QString &oldValue);
|
||||||
|
|
||||||
} // namespace Command
|
} // namespace Command
|
||||||
|
|
||||||
#endif // COMMAND_H
|
#endif // COMMAND_H
|
||||||
|
|
93
desktop-widgets/command_edit.cpp
Normal file
93
desktop-widgets/command_edit.cpp
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
#include "command_edit.h"
|
||||||
|
#include "core/divelist.h"
|
||||||
|
|
||||||
|
namespace Command {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
EditBase<T>::EditBase(const QVector<dive *> &divesIn, T newValue, T oldValue) :
|
||||||
|
value(std::move(newValue)),
|
||||||
|
old(std::move(oldValue)),
|
||||||
|
dives(divesIn.toStdVector())
|
||||||
|
{
|
||||||
|
// If there is nothing to do, clear the dives vector.
|
||||||
|
// This signals that no action has to be taken.
|
||||||
|
if (old == value)
|
||||||
|
dives.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is quite hackish: we can't use virtual functions in the constructor and
|
||||||
|
// therefore can't initialize the list of dives [the values of the dives are
|
||||||
|
// accessed by virtual functions]. Therefore, we (mis)use the fact that workToBeDone()
|
||||||
|
// is called exactly once before adding the Command to the system and perform this here.
|
||||||
|
// To be more explicit about this, we might think about renaming workToBeDone() to init().
|
||||||
|
template<typename T>
|
||||||
|
bool EditBase<T>::workToBeDone()
|
||||||
|
{
|
||||||
|
std::vector<dive *> divesNew;
|
||||||
|
divesNew.reserve(dives.size());
|
||||||
|
for (dive *d: dives) {
|
||||||
|
if (data(d) == old)
|
||||||
|
divesNew.push_back(d);
|
||||||
|
}
|
||||||
|
dives = std::move(divesNew);
|
||||||
|
|
||||||
|
// Create a text for the menu entry. In the case of multiple dives add the number
|
||||||
|
size_t num_dives = dives.size();
|
||||||
|
if (num_dives > 0)
|
||||||
|
//: remove the part in parantheses for %n = 1
|
||||||
|
setText(tr("Edit %1 (%n dive(s))", "", num_dives).arg(fieldName()));
|
||||||
|
|
||||||
|
return num_dives;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void EditBase<T>::undo()
|
||||||
|
{
|
||||||
|
if (dives.empty()) {
|
||||||
|
qWarning("Edit command called with empty dives list (shouldn't happen)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (dive *d: dives) {
|
||||||
|
set(d, value);
|
||||||
|
invalidate_dive_cache(d); // Ensure that dive is written in git_save()
|
||||||
|
}
|
||||||
|
|
||||||
|
std::swap(old, value);
|
||||||
|
|
||||||
|
mark_divelist_changed(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to manually instantiate the constructors of the EditBase class,
|
||||||
|
// because the base class is never constructed and the derived classes
|
||||||
|
// don't have their own constructor. They simply delegate to the base
|
||||||
|
// class by virtue of a "using" declaration.
|
||||||
|
template
|
||||||
|
EditBase<QString>::EditBase(const QVector<dive *> &dives, QString oldValue, QString newValue);
|
||||||
|
|
||||||
|
// Undo and redo do the same as just the stored value is exchanged
|
||||||
|
template<typename T>
|
||||||
|
void EditBase<T>::redo()
|
||||||
|
{
|
||||||
|
undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditNotes::set(struct dive *d, QString s) const
|
||||||
|
{
|
||||||
|
free(d->notes);
|
||||||
|
d->notes = strdup(qPrintable(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString EditNotes::data(struct dive *d) const
|
||||||
|
{
|
||||||
|
return QString(d->notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString EditNotes::fieldName() const
|
||||||
|
{
|
||||||
|
return tr("notes");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Command
|
58
desktop-widgets/command_edit.h
Normal file
58
desktop-widgets/command_edit.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Note: this header file is used by the undo-machinery and should not be included elsewhere.
|
||||||
|
|
||||||
|
#ifndef COMMAND_EDIT_H
|
||||||
|
#define COMMAND_EDIT_H
|
||||||
|
|
||||||
|
#include "command_base.h"
|
||||||
|
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
// These are commands that edit individual fields of a set of dives.
|
||||||
|
// The implementation is very OO-style. Out-of-fashion and certainly
|
||||||
|
// not elegant, but in line with Qt's OO-based design.
|
||||||
|
// The actual code is in a common base class "Command::EditBase". To
|
||||||
|
// read and set the fields, the base class calls virtual functions of
|
||||||
|
// the derived classes.
|
||||||
|
//
|
||||||
|
// To deal with different data types, the base class is implemented
|
||||||
|
// as a template. The template parameter is the type to be read or
|
||||||
|
// set. Thus, switch-cascades and union trickery can be avoided.
|
||||||
|
|
||||||
|
// We put everything in a namespace, so that we can shorten names without polluting the global namespace
|
||||||
|
namespace Command {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class EditBase : public Base {
|
||||||
|
T value; // Value to be set
|
||||||
|
T old; // Previous value
|
||||||
|
|
||||||
|
void undo() override;
|
||||||
|
void redo() override;
|
||||||
|
bool workToBeDone() override;
|
||||||
|
|
||||||
|
// Dives to be edited. For historical reasons, the *last* entry was
|
||||||
|
// the active dive when the user initialized the action. This dive
|
||||||
|
// will be made the current dive on redo / undo.
|
||||||
|
std::vector<dive *> dives;
|
||||||
|
public:
|
||||||
|
EditBase(const QVector<dive *> &dives, T newValue, T oldValue);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Get and set functions to be overriden by sub-classes.
|
||||||
|
virtual void set(struct dive *d, T) const = 0;
|
||||||
|
virtual T data(struct dive *d) const = 0;
|
||||||
|
virtual QString fieldName() const = 0; // Name of the field, used to create the undo menu-entry
|
||||||
|
};
|
||||||
|
|
||||||
|
class EditNotes : public EditBase<QString> {
|
||||||
|
public:
|
||||||
|
using EditBase<QString>::EditBase; // Use constructor of base class.
|
||||||
|
void set(struct dive *d, QString s) const override;
|
||||||
|
QString data(struct dive *d) const override;
|
||||||
|
QString fieldName() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Command
|
||||||
|
|
||||||
|
#endif
|
|
@ -767,8 +767,6 @@ void MainTab::acceptChanges()
|
||||||
// were identical with the master dive shown (and mark the divelist as changed)
|
// were identical with the master dive shown (and mark the divelist as changed)
|
||||||
if (!same_string(displayed_dive.suit, cd->suit))
|
if (!same_string(displayed_dive.suit, cd->suit))
|
||||||
MODIFY_DIVES(selectedDives, EDIT_TEXT(suit));
|
MODIFY_DIVES(selectedDives, EDIT_TEXT(suit));
|
||||||
if (!same_string(displayed_dive.notes, cd->notes))
|
|
||||||
MODIFY_DIVES(selectedDives, EDIT_TEXT(notes));
|
|
||||||
if (displayed_dive.rating != cd->rating)
|
if (displayed_dive.rating != cd->rating)
|
||||||
MODIFY_DIVES(selectedDives, EDIT_VALUE(rating));
|
MODIFY_DIVES(selectedDives, EDIT_VALUE(rating));
|
||||||
if (displayed_dive.visibility != cd->visibility)
|
if (displayed_dive.visibility != cd->visibility)
|
||||||
|
@ -1343,16 +1341,19 @@ void MainTab::on_notes_textChanged()
|
||||||
return;
|
return;
|
||||||
free(displayedTrip.notes);
|
free(displayedTrip.notes);
|
||||||
displayedTrip.notes = copy_qstring(ui.notes->toPlainText());
|
displayedTrip.notes = copy_qstring(ui.notes->toPlainText());
|
||||||
} else {
|
markChangedWidget(ui.notes);
|
||||||
if (same_string(displayed_dive.notes, qPrintable(ui.notes->toPlainText())))
|
|
||||||
return;
|
|
||||||
free(displayed_dive.notes);
|
|
||||||
if (ui.notes->toHtml().indexOf("<div") != -1)
|
|
||||||
displayed_dive.notes = copy_qstring(ui.notes->toHtml());
|
|
||||||
else
|
|
||||||
displayed_dive.notes = copy_qstring(ui.notes->toPlainText());
|
|
||||||
}
|
}
|
||||||
markChangedWidget(ui.notes);
|
}
|
||||||
|
|
||||||
|
void MainTab::on_notes_editingFinished()
|
||||||
|
{
|
||||||
|
if (currentTrip || !current_dive)
|
||||||
|
return; // Trip-note editing is done via on_notes_textChanged()
|
||||||
|
|
||||||
|
QString notes = ui.notes->toHtml().indexOf("<div") != -1 ?
|
||||||
|
ui.notes->toHtml() : ui.notes->toPlainText();
|
||||||
|
|
||||||
|
Command::editNotes(getSelectedDivesCurrentLast(), notes, QString(current_dive->notes));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainTab::on_rating_valueChanged(int value)
|
void MainTab::on_rating_valueChanged(int value)
|
||||||
|
|
|
@ -75,6 +75,7 @@ slots:
|
||||||
void on_suit_textChanged(const QString &text);
|
void on_suit_textChanged(const QString &text);
|
||||||
void on_diveTripLocation_textEdited(const QString& text);
|
void on_diveTripLocation_textEdited(const QString& text);
|
||||||
void on_notes_textChanged();
|
void on_notes_textChanged();
|
||||||
|
void on_notes_editingFinished();
|
||||||
void on_airtemp_textChanged(const QString &text);
|
void on_airtemp_textChanged(const QString &text);
|
||||||
void on_duration_textChanged(const QString &text);
|
void on_duration_textChanged(const QString &text);
|
||||||
void on_depth_textChanged(const QString &text);
|
void on_depth_textChanged(const QString &text);
|
||||||
|
|
Loading…
Add table
Reference in a new issue