subsurface/commands/command_edit.h
Berthold Stoeger ad540ce5e8 undo: generate fieldId() virtual functions by templates
Most edit commands derive from a common base class EditBase,
which declares a fieldId() virtual function that has to be
defined by the child classes. This is tedious. For some reason
the C++ makers refuse to allow "virtual member constants".

To make the code somewhat less verbose, create these functions
by a template. Of course, we could introduce the template
parameter directly in the EditBase class. However, that would
mean that the code in this base class is generated for every
single undo command. I'm not sure we want that.

This should als make it somewhat less tedious to create new
edit commands by copy & paste.

We could do the same for the fieldName. However, that is more
complicated for two reasons:

1) For historic reasons(?) C++ doesn't allow for string literals
   as template parameters. Therefore, arrays-of-string would have
   to be defined, which is not very nice.
2) We would have to make sure that these strings are recognized
   by Qt's translation machinery and use the QT_TRANSLATE_NOOP
   macro, which makes the whole thing even less attractive.
Maybe later.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2020-03-31 21:53:19 +02:00

408 lines
13 KiB
C++

// 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 "core/subsurface-qt/divelistnotifier.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 {
// Base class for commands that have a list of dives.
// This is used for extracting the number of dives and show a
// warning message when multiple dives are edited.
class EditDivesBase : public Base {
protected:
EditDivesBase(bool currentDiveOnly);
EditDivesBase(dive *d);
std::vector<dive *> dives; // Dives to be edited.
// On undo, we set the selection and current dive at the time of the operation.
std::vector<dive *> selectedDives;
struct dive *current;
public:
int numDives() const;
};
template <typename T>
class EditBase : public EditDivesBase {
protected:
T value; // Value to be set
T old; // Previous value
void undo() override;
void redo() override;
bool workToBeDone() override;
public:
EditBase(T newValue, bool currentDiveOnly);
EditBase(T newValue, dive *d);
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
virtual DiveField fieldId() const = 0;
};
// The individual Edit-commands define a virtual function that return the field-id.
// For reasons, which I don't fully understand, the C++ makers are strictly opposed
// to "virtual member constants" so we have to define these functions. To make
// things a bit more compact we do this automatically with the following template.
// Of course, we could directly encode the value in the EditBase-template, but
// that would lead to a multiplication of the created code.
template <typename T, DiveField::Flags ID>
class EditTemplate : public EditBase<T> {
private:
using EditBase<T>::EditBase; // Use constructor of base class.
DiveField fieldId() const override final; // final prevents further overriding - then just don't use this template
};
class EditNotes : public EditTemplate<QString, DiveField::NOTES> {
public:
using EditTemplate::EditTemplate; // 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;
};
class EditSuit : public EditTemplate<QString, DiveField::SUIT> {
public:
using EditTemplate::EditTemplate; // 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;
};
class EditRating : public EditTemplate<int, DiveField::RATING> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditVisibility : public EditTemplate<int, DiveField::VISIBILITY> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditWaveSize : public EditTemplate<int, DiveField::WAVESIZE> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditCurrent : public EditTemplate<int, DiveField::CURRENT> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditSurge : public EditTemplate<int, DiveField::SURGE> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditChill : public EditTemplate<int, DiveField::CHILL> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditAirTemp : public EditTemplate<int, DiveField::AIR_TEMP> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditWaterTemp : public EditTemplate<int, DiveField::WATER_TEMP> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditAtmPress : public EditTemplate<int, DiveField::ATM_PRESS> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditWaterTypeUser : public EditTemplate<int, DiveField::SALINITY> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditDuration : public EditTemplate<int, DiveField::DURATION> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditDepth : public EditTemplate<int, DiveField::DEPTH> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditDiveSite : public EditTemplate<struct dive_site *, DiveField::DIVESITE> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, struct dive_site *value) const override;
struct dive_site *data(struct dive *d) const override;
QString fieldName() const override;
// We specialize these so that we can send dive-site changed signals.
void undo() override;
void redo() override;
};
// Edit dive site, but add a new dive site first. Reuses the code of EditDiveSite by
// deriving from it and hooks into undo() and redo() to add / remove the dive site.
class EditDiveSiteNew : public EditDiveSite {
public:
OwningDiveSitePtr diveSiteToAdd;
struct dive_site *diveSiteToRemove;
EditDiveSiteNew(const QString &newName, bool currentDiveOnly);
void undo() override;
void redo() override;
};
class EditMode : public EditTemplate<int, DiveField::MODE> {
int index;
public:
EditMode(int indexIn, int newValue, bool currentDiveOnly);
void set(struct dive *d, int i) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
class EditInvalid : public EditTemplate<int, DiveField::INVALID> {
public:
using EditTemplate::EditTemplate; // Use constructor of base class.
void set(struct dive *d, int number) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
};
// Fields that work with tag-lists (tags, buddies, divemasters) work differently and therefore
// have their own base class. In this case, it's not a template, as all these lists are base
// on strings.
class EditTagsBase : public EditDivesBase {
bool workToBeDone() override;
QStringList newList; // Temporary until initialized
public:
EditTagsBase(const QStringList &newList, bool currentDiveOnly);
protected:
QStringList tagsToAdd;
QStringList tagsToRemove;
void undo() override;
void redo() override;
// Getters, setters and parsers to be overriden by sub-classes.
virtual QStringList data(struct dive *d) const = 0;
virtual void set(struct dive *d, const QStringList &v) const = 0;
virtual QString fieldName() const = 0; // Name of the field, used to create the undo menu-entry
virtual DiveField fieldId() const = 0;
};
// See comments for EditTemplate
template <DiveField::Flags ID>
class EditTagsTemplate : public EditTagsBase {
private:
using EditTagsBase::EditTagsBase; // Use constructor of base class.
DiveField fieldId() const override final; // final prevents further overriding - then just don't use this template
};
class EditTags : public EditTagsTemplate<DiveField::TAGS> {
public:
using EditTagsTemplate::EditTagsTemplate; // Use constructor of base class.
QStringList data(struct dive *d) const override;
void set(struct dive *d, const QStringList &v) const override;
QString fieldName() const override;
};
class EditBuddies : public EditTagsTemplate<DiveField::BUDDY> {
public:
using EditTagsTemplate::EditTagsTemplate; // Use constructor of base class.
QStringList data(struct dive *d) const override;
void set(struct dive *d, const QStringList &v) const override;
QString fieldName() const override;
};
class EditDiveMaster : public EditTagsTemplate<DiveField::DIVEMASTER> {
public:
using EditTagsTemplate::EditTagsTemplate; // Use constructor of base class.
QStringList data(struct dive *d) const override;
void set(struct dive *d, const QStringList &v) const override;
QString fieldName() const override;
};
// Fields we have to remember to undo paste
struct PasteState {
dive *d;
dive_site *divesite;
QString notes;
QString divemaster;
QString buddy;
QString suit;
int rating;
int wavesize;
int visibility;
int current;
int surge;
int chill;
tag_entry *tags;
struct cylinder_table cylinders;
struct weightsystem_table weightsystems;
PasteState(dive *d, const dive *data, dive_components what); // Read data from dive data for dive d
~PasteState();
void swap(dive_components what); // Exchange values here and in dive
};
class PasteDives : public Base {
dive_components what;
std::vector<PasteState> dives;
public:
PasteDives(const dive *d, dive_components what);
private:
void undo() override;
void redo() override;
bool workToBeDone() override;
};
class ReplanDive : public Base {
dive *d;
// Exchange these data with current dive
timestamp_t when;
depth_t maxdepth, meandepth;
struct cylinder_table cylinders;
struct divecomputer dc;
char *notes;
pressure_t surface_pressure;
duration_t duration;
int salinity;
public:
// Dive computer(s) and cylinders(s) of the source dive will be reset!
// If edit_profile is true, the text will be changed from "replan dive" to "edit profile".
ReplanDive(dive *source, bool edit_profile);
~ReplanDive();
private:
void undo() override;
void redo() override;
bool workToBeDone() override;
};
class AddWeight : public EditDivesBase {
public:
AddWeight(bool currentDiveOnly);
private:
void undo() override;
void redo() override;
bool workToBeDone() override;
};
class EditWeightBase : public EditDivesBase {
protected:
EditWeightBase(int index, bool currentDiveOnly);
~EditWeightBase();
weightsystem_t ws;
std::vector<int> indices; // An index for each dive in the dives vector.
bool workToBeDone() override;
};
class RemoveWeight : public EditWeightBase {
public:
RemoveWeight(int index, bool currentDiveOnly);
private:
void undo() override;
void redo() override;
};
class EditWeight : public EditWeightBase {
public:
EditWeight(int index, weightsystem_t ws, bool currentDiveOnly); // Clones ws
~EditWeight();
private:
weightsystem_t new_ws;
void undo() override;
void redo() override;
};
#ifdef SUBSURFACE_MOBILE
// Edit a full dive. This is used on mobile where we don't have per-field granularity.
// It may add or edit a dive site.
class EditDive : public Base {
public:
EditDive(dive *oldDive, dive *newDive, dive_site *createDs, dive_site *editDs, location_t dsLocation); // Takes ownership of newDive
private:
dive *oldDive; // Dive that is going to be overwritten
OwningDivePtr newDive; // New data
int changedFields;
dive_site *siteToRemove;
OwningDiveSitePtr siteToAdd;
dive_site *siteToEdit;
location_t dsLocation;
void undo() override;
void redo() override;
bool workToBeDone() override;
void exchangeDives();
void editDs();
};
#endif // SUBSURFACE_MOBILE
} // namespace Command
#endif