From 2453d4fc1fa6c6109645853483839f1e3c87d4a8 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 23 Feb 2020 11:05:19 +0100 Subject: [PATCH 01/78] core: introduce clone_cylinder() function We have a clone_weightsystem function. For symmetry, introduce a clone_cylinder() function so that we can more-or-less copy&paste the weightsystem undo code for cylinder undo. Signed-off-by: Berthold Stoeger --- core/equipment.c | 10 ++++++++-- core/equipment.h | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/equipment.c b/core/equipment.c index c9c0539c3..e96ce0d6a 100644 --- a/core/equipment.c +++ b/core/equipment.c @@ -137,12 +137,18 @@ void add_cloned_weightsystem_at(struct weightsystem_table *t, weightsystem_t ws) add_to_weightsystem_table(t, t->nr, clone_weightsystem(ws)); } +cylinder_t clone_cylinder(cylinder_t cyl) +{ + cylinder_t res = cyl; + res.type.description = copy_string(res.type.description); + return res; +} + /* Add a clone of a cylinder to the end of a cylinder table. * Cloned in means that the description-string is copied. */ void add_cloned_cylinder(struct cylinder_table *t, cylinder_t cyl) { - cyl.type.description = copy_string(cyl.type.description); - add_to_cylinder_table(t, t->nr, cyl); + add_to_cylinder_table(t, t->nr, clone_cylinder(cyl)); } bool same_weightsystem(weightsystem_t w1, weightsystem_t w2) diff --git a/core/equipment.h b/core/equipment.h index b62587447..74f04dfdf 100644 --- a/core/equipment.h +++ b/core/equipment.h @@ -75,6 +75,7 @@ extern weightsystem_t clone_weightsystem(weightsystem_t ws); extern void free_weightsystem(weightsystem_t ws); extern void copy_cylinder_types(const struct dive *s, struct dive *d); extern void add_cloned_weightsystem(struct weightsystem_table *t, weightsystem_t ws); +extern cylinder_t clone_cylinder(cylinder_t cyl); extern cylinder_t *add_empty_cylinder(struct cylinder_table *t); extern void add_cloned_cylinder(struct cylinder_table *t, cylinder_t cyl); extern cylinder_t *get_cylinder(const struct dive *d, int idx); From 1ca25ec03950cea052fd151c41e7d6535cfb1381 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 23 Feb 2020 11:07:51 +0100 Subject: [PATCH 02/78] core: make free_cylinder() global The cylinder undo commands will keep a copy of a cylinder and therefore need the ability to free a cylinder object. Signed-off-by: Berthold Stoeger --- core/equipment.c | 2 +- core/equipment.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/equipment.c b/core/equipment.c index e96ce0d6a..4672cbe00 100644 --- a/core/equipment.c +++ b/core/equipment.c @@ -29,7 +29,7 @@ void free_weightsystem(weightsystem_t ws) ws.description = NULL; } -static void free_cylinder(cylinder_t c) +void free_cylinder(cylinder_t c) { free((void *)c.type.description); c.type.description = NULL; diff --git a/core/equipment.h b/core/equipment.h index 74f04dfdf..8bc752022 100644 --- a/core/equipment.h +++ b/core/equipment.h @@ -76,6 +76,7 @@ extern void free_weightsystem(weightsystem_t ws); extern void copy_cylinder_types(const struct dive *s, struct dive *d); extern void add_cloned_weightsystem(struct weightsystem_table *t, weightsystem_t ws); extern cylinder_t clone_cylinder(cylinder_t cyl); +extern void free_cylinder(cylinder_t cyl); extern cylinder_t *add_empty_cylinder(struct cylinder_table *t); extern void add_cloned_cylinder(struct cylinder_table *t, cylinder_t cyl); extern cylinder_t *get_cylinder(const struct dive *d, int idx); From f1e08fd470502e5703a7338d6b5a5130a5f41c14 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 23 Feb 2020 11:38:53 +0100 Subject: [PATCH 03/78] core: introduce set_cylinder() function We have a set_weightsystem() function. For symmetry, introduce a set_cylinder() function so that we can more-or-less copy&paste the weightsystem undo code for cylinder undo. Signed-off-by: Berthold Stoeger --- core/equipment.c | 9 +++++++++ core/equipment.h | 1 + 2 files changed, 10 insertions(+) diff --git a/core/equipment.c b/core/equipment.c index 4672cbe00..3d169a338 100644 --- a/core/equipment.c +++ b/core/equipment.c @@ -291,6 +291,15 @@ void remove_cylinder(struct dive *dive, int idx) remove_from_cylinder_table(&dive->cylinders, idx); } +// cyl is cloned. +void set_cylinder(struct dive *dive, int idx, cylinder_t cyl) +{ + if (idx < 0 || idx >= dive->cylinders.nr) + return; + free_cylinder(dive->cylinders.cylinders[idx]); + dive->cylinders.cylinders[idx] = clone_cylinder(cyl); +} + void remove_weightsystem(struct dive *dive, int idx) { remove_from_weightsystem_table(&dive->weightsystems, idx); diff --git a/core/equipment.h b/core/equipment.h index 8bc752022..4ed809eff 100644 --- a/core/equipment.h +++ b/core/equipment.h @@ -86,6 +86,7 @@ extern void add_weightsystem_description(const weightsystem_t *); extern bool same_weightsystem(weightsystem_t w1, weightsystem_t w2); extern bool same_cylinder(cylinder_t cyl1, cylinder_t cyl2); extern void remove_cylinder(struct dive *dive, int idx); +extern void set_cylinder(struct dive *dive, int idx, cylinder_t ws); extern void remove_weightsystem(struct dive *dive, int idx); extern void set_weightsystem(struct dive *dive, int idx, weightsystem_t ws); extern void reset_cylinders(struct dive *dive, bool track_gas); From e008b42a591b83eb1304a552fdd950287ddbc375 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 1 Mar 2020 16:26:47 +0100 Subject: [PATCH 04/78] core: add create_new_cylinder() function Turn the code in CylindersModel that creates a new cylinder for addition into its own function to avoid code duplication. This will be used from the undo commands. Signed-off-by: Berthold Stoeger --- core/equipment.c | 10 ++++++++++ core/equipment.h | 1 + qt-models/cylindermodel.cpp | 6 +----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/equipment.c b/core/equipment.c index 3d169a338..c3b9fa494 100644 --- a/core/equipment.c +++ b/core/equipment.c @@ -392,6 +392,16 @@ cylinder_t *get_or_create_cylinder(struct dive *d, int idx) return &d->cylinders.cylinders[idx]; } +cylinder_t create_new_cylinder(const struct dive *d) +{ + cylinder_t cyl = empty_cylinder; + fill_default_cylinder(d, &cyl); + cyl.start = cyl.type.workingpressure; + cyl.manually_added = true; + cyl.cylinder_use = OC_GAS; + return cyl; +} + #ifdef DEBUG_CYL void dump_cylinders(struct dive *dive, bool verbose) { diff --git a/core/equipment.h b/core/equipment.h index 4ed809eff..289eb75a1 100644 --- a/core/equipment.h +++ b/core/equipment.h @@ -92,6 +92,7 @@ extern void set_weightsystem(struct dive *dive, int idx, weightsystem_t ws); extern void reset_cylinders(struct dive *dive, bool track_gas); extern int gas_volume(const cylinder_t *cyl, pressure_t p); /* Volume in mliter of a cylinder at pressure 'p' */ extern int find_best_gasmix_match(struct gasmix mix, const struct cylinder_table *cylinders); +extern cylinder_t create_new_cylinder(const struct dive *dive); /* dive is needed to fill out MOD, which depends on salinity. */ #ifdef DEBUG_CYL extern void dump_cylinders(struct dive *dive, bool verbose); #endif diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index da8b2eb1e..428d3c868 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -448,11 +448,7 @@ int CylindersModel::rowCount(const QModelIndex&) const void CylindersModel::add() { int row = rows; - cylinder_t cyl = empty_cylinder; - fill_default_cylinder(&displayed_dive, &cyl); - cyl.start = cyl.type.workingpressure; - cyl.manually_added = true; - cyl.cylinder_use = OC_GAS; + cylinder_t cyl = create_new_cylinder(&displayed_dive); beginInsertRows(QModelIndex(), row, row); add_to_cylinder_table(&displayed_dive.cylinders, row, cyl); rows++; From 36754d3399dab9155fac50e9451dcd325c7c73c0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 15:34:56 +0100 Subject: [PATCH 05/78] cleanup: move fill_default_cylinder from planner.c to equipment.c Moreover, move the declaration from dive.h to equipment.h. The result is a) more consistent and b) more logical. Signed-off-by: Berthold Stoeger --- core/dive.h | 1 - core/equipment.c | 30 ++++++++++++++++++++++++++++++ core/equipment.h | 1 + core/planner.c | 31 ------------------------------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/core/dive.h b/core/dive.h index 1e5ca873d..76eccb5e8 100644 --- a/core/dive.h +++ b/core/dive.h @@ -376,7 +376,6 @@ extern void copy_used_cylinders(const struct dive *s, struct dive *d, bool used_ extern void copy_samples(const struct divecomputer *s, struct divecomputer *d); extern bool is_cylinder_used(const struct dive *dive, int idx); extern bool is_cylinder_prot(const struct dive *dive, int idx); -extern void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl); extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx); extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); extern void remove_event(struct event *event); diff --git a/core/equipment.c b/core/equipment.c index c3b9fa494..24d1d826b 100644 --- a/core/equipment.c +++ b/core/equipment.c @@ -392,6 +392,36 @@ cylinder_t *get_or_create_cylinder(struct dive *d, int idx) return &d->cylinders.cylinders[idx]; } +/* if a default cylinder is set, use that */ +void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl) +{ + const char *cyl_name = prefs.default_cylinder; + struct tank_info_t *ti = tank_info; + pressure_t pO2 = {.mbar = 1600}; + + if (!cyl_name) + return; + while (ti->name != NULL && ti < tank_info + MAX_TANK_INFO) { + if (strcmp(ti->name, cyl_name) == 0) + break; + ti++; + } + if (ti->name == NULL) + /* didn't find it */ + return; + cyl->type.description = strdup(ti->name); + if (ti->ml) { + cyl->type.size.mliter = ti->ml; + cyl->type.workingpressure.mbar = ti->bar * 1000; + } else { + cyl->type.workingpressure.mbar = psi_to_mbar(ti->psi); + if (ti->psi) + cyl->type.size.mliter = lrint(cuft_to_l(ti->cuft) * 1000 / bar_to_atm(psi_to_bar(ti->psi))); + } + // MOD of air + cyl->depth = gas_mod(cyl->gasmix, pO2, dive, 1); +} + cylinder_t create_new_cylinder(const struct dive *d) { cylinder_t cyl = empty_cylinder; diff --git a/core/equipment.h b/core/equipment.h index 289eb75a1..494f8dc2b 100644 --- a/core/equipment.h +++ b/core/equipment.h @@ -92,6 +92,7 @@ extern void set_weightsystem(struct dive *dive, int idx, weightsystem_t ws); extern void reset_cylinders(struct dive *dive, bool track_gas); extern int gas_volume(const cylinder_t *cyl, pressure_t p); /* Volume in mliter of a cylinder at pressure 'p' */ extern int find_best_gasmix_match(struct gasmix mix, const struct cylinder_table *cylinders); +extern void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl); /* dive is needed to fill out MOD, which depends on salinity. */ extern cylinder_t create_new_cylinder(const struct dive *dive); /* dive is needed to fill out MOD, which depends on salinity. */ #ifdef DEBUG_CYL extern void dump_cylinders(struct dive *dive, bool verbose); diff --git a/core/planner.c b/core/planner.c index 0e26a9a57..204958a7e 100644 --- a/core/planner.c +++ b/core/planner.c @@ -175,37 +175,6 @@ static int tissue_at_end(struct deco_state *ds, struct dive *dive, struct deco_s return surface_interval; } - -/* if a default cylinder is set, use that */ -void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl) -{ - const char *cyl_name = prefs.default_cylinder; - struct tank_info_t *ti = tank_info; - pressure_t pO2 = {.mbar = 1600}; - - if (!cyl_name) - return; - while (ti->name != NULL && ti < tank_info + MAX_TANK_INFO) { - if (strcmp(ti->name, cyl_name) == 0) - break; - ti++; - } - if (ti->name == NULL) - /* didn't find it */ - return; - cyl->type.description = strdup(ti->name); - if (ti->ml) { - cyl->type.size.mliter = ti->ml; - cyl->type.workingpressure.mbar = ti->bar * 1000; - } else { - cyl->type.workingpressure.mbar = psi_to_mbar(ti->psi); - if (ti->psi) - cyl->type.size.mliter = lrint(cuft_to_l(ti->cuft) * 1000 / bar_to_atm(psi_to_bar(ti->psi))); - } - // MOD of air - cyl->depth = gas_mod(cyl->gasmix, pO2, dive, 1); -} - /* calculate the new end pressure of the cylinder, based on its current end pressure and the * latest segment. */ static void update_cylinder_pressure(struct dive *d, int old_depth, int new_depth, int duration, int sac, cylinder_t *cyl, bool in_deco, enum divemode_t divemode) From aa7b0cadb2f737e65d490f4ad026f5df09a394f0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 23 Feb 2020 11:43:50 +0100 Subject: [PATCH 06/78] undo: add cylinder undo commands by copy & paste Do a simple copy & paste followed by a simple search & replace to generate cylinder undo commands from weight undo commands. Obviously, this is still missing the necessary code to keep the dive-data consistent after cylinder editing. Signed-off-by: Berthold Stoeger --- commands/command.cpp | 15 +++ commands/command.h | 3 + commands/command_edit.cpp | 180 ++++++++++++++++++++++++++ commands/command_edit.h | 40 +++++- core/subsurface-qt/divelistnotifier.h | 3 + 5 files changed, 240 insertions(+), 1 deletion(-) diff --git a/commands/command.cpp b/commands/command.cpp index 1fb968778..5d71e6725 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -293,6 +293,21 @@ int editWeight(int index, weightsystem_t ws, bool currentDiveOnly) return execute_edit(new EditWeight(index, ws, currentDiveOnly)); } +int addCylinder(bool currentDiveOnly) +{ + return execute_edit(new AddCylinder(currentDiveOnly)); +} + +int removeCylinder(int index, bool currentDiveOnly) +{ + return execute_edit(new RemoveCylinder(index, currentDiveOnly)); +} + +int editCylinder(int index, cylinder_t cyl, bool currentDiveOnly) +{ + return execute_edit(new EditCylinder(index, cyl, currentDiveOnly)); +} + // Trip editing related commands void editTripLocation(dive_trip *trip, const QString &s) { diff --git a/commands/command.h b/commands/command.h index e19d093cb..b8024c03d 100644 --- a/commands/command.h +++ b/commands/command.h @@ -90,6 +90,9 @@ void editProfile(dive *d); // dive computer(s) and cylinder(s) will be reset! int addWeight(bool currentDiveOnly); int removeWeight(int index, bool currentDiveOnly); int editWeight(int index, weightsystem_t ws, bool currentDiveOnly); +int addCylinder(bool currentDiveOnly); +int removeCylinder(int index, bool currentDiveOnly); +int editCylinder(int index, cylinder_t cyl, bool currentDiveOnly); #ifdef SUBSURFACE_MOBILE // Edits a dive and creates a divesite (if createDs != NULL) or edits a divesite (if changeDs != NULL). // Takes ownership of newDive and createDs! diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index 46c206c98..c06ef1ffa 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -8,6 +8,7 @@ #include "core/subsurface-string.h" #include "core/tag.h" #include "qt-models/weightsysteminfomodel.h" +#include "qt-models/tankinfomodel.h" #ifdef SUBSURFACE_MOBILE #include "qt-models/divelocationmodel.h" #endif @@ -917,6 +918,7 @@ bool EditWeightBase::workToBeDone() return !dives.empty(); } +// ***** Remove Weight ***** RemoveWeight::RemoveWeight(int index, bool currentDiveOnly) : EditWeightBase(index, currentDiveOnly) { @@ -943,6 +945,7 @@ void RemoveWeight::redo() } } +// ***** Edit Weight ***** EditWeight::EditWeight(int index, weightsystem_t wsIn, bool currentDiveOnly) : EditWeightBase(index, currentDiveOnly), new_ws(empty_weightsystem) @@ -999,6 +1002,183 @@ void EditWeight::undo() redo(); } +// ***** Add Cylinder ***** +AddCylinder::AddCylinder(bool currentDiveOnly) : + EditDivesBase(currentDiveOnly), + cyl(empty_cylinder) +{ + if (dives.empty()) + return; + else if (dives.size() == 1) + setText(tr("Add cylinder")); + else + setText(tr("Add cylinder (%n dive(s))", "", dives.size())); + cyl = create_new_cylinder(dives[0]); +} + +AddCylinder::~AddCylinder() +{ + free_cylinder(cyl); +} + +bool AddCylinder::workToBeDone() +{ + return true; +} + +void AddCylinder::undo() +{ + for (dive *d: dives) { + if (d->cylinders.nr <= 0) + continue; + remove_cylinder(d, d->cylinders.nr - 1); + emit diveListNotifier.cylinderRemoved(d, d->cylinders.nr); + } +} + +void AddCylinder::redo() +{ + for (dive *d: dives) { + add_cloned_cylinder(&d->cylinders, cyl); + emit diveListNotifier.cylinderAdded(d, d->cylinders.nr - 1); + } +} + +static int find_cylinder_index(const struct dive *d, const cylinder_t &cyl) +{ + for (int idx = 0; idx < d->cylinders.nr; ++idx) { + if (same_cylinder(d->cylinders.cylinders[idx], cyl)) + return idx; + } + return -1; +} + +EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly) : + EditDivesBase(currentDiveOnly), + cyl(empty_cylinder) +{ + // Get the old cylinder, bail if index is invalid + if (!current || index < 0 || index >= current->cylinders.nr) { + dives.clear(); + return; + } + cyl = clone_cylinder(current->cylinders.cylinders[index]); + + std::vector divesNew; + divesNew.reserve(dives.size()); + indexes.reserve(dives.size()); + + for (dive *d: dives) { + if (d == current) { + divesNew.push_back(d); + indexes.push_back(index); + continue; + } + int idx = find_cylinder_index(d, cyl); + if (idx >= 0) { + divesNew.push_back(d); + indexes.push_back(idx); + } + } + dives = std::move(divesNew); +} + +EditCylinderBase::~EditCylinderBase() +{ + free_cylinder(cyl); +} + +bool EditCylinderBase::workToBeDone() +{ + return !dives.empty(); +} + +// ***** Remove Cylinder ***** +RemoveCylinder::RemoveCylinder(int index, bool currentDiveOnly) : + EditCylinderBase(index, currentDiveOnly) +{ + if (dives.size() == 1) + setText(tr("Remove cylinder")); + else + setText(tr("Remove cylinder (%n dive(s))", "", dives.size())); +} + +void RemoveCylinder::undo() +{ + for (size_t i = 0; i < dives.size(); ++i) { + add_to_cylinder_table(&dives[i]->cylinders, indexes[i], clone_cylinder(cyl)); + emit diveListNotifier.cylinderAdded(dives[i], indexes[i]); + } +} + +void RemoveCylinder::redo() +{ + for (size_t i = 0; i < dives.size(); ++i) { + remove_cylinder(dives[i], indexes[i]); + emit diveListNotifier.cylinderRemoved(dives[i], indexes[i]); + } +} + +// ***** Edit Cylinder ***** +EditCylinder::EditCylinder(int index, cylinder_t cylIn, bool currentDiveOnly) : + EditCylinderBase(index, currentDiveOnly), + new_cyl(empty_cylinder) +{ + if (dives.empty()) + return; + + if (dives.size() == 1) + setText(tr("Edit cylinder")); + else + setText(tr("Edit cylinder (%n dive(s))", "", dives.size())); + + // Try to untranslate the cylinder type + new_cyl = clone_cylinder(cylIn); + QString vString(new_cyl.type.description); + for (int i = 0; i < MAX_TANK_INFO && tank_info[i].name; ++i) { + if (gettextFromC::tr(tank_info[i].name) == vString) { + free_cylinder(new_cyl); + new_cyl.type.description = copy_string(tank_info[i].name); + break; + } + } + + // If that doesn't change anything, do nothing + if (same_cylinder(cyl, new_cyl)) { + dives.clear(); + return; + } + + TankInfoModel *tim = TankInfoModel::instance(); + QModelIndexList matches = tim->match(tim->index(0, 0), Qt::DisplayRole, gettextFromC::tr(new_cyl.type.description)); + if (!matches.isEmpty()) { + if (new_cyl.type.size.mliter != cyl.type.size.mliter) + tim->setData(tim->index(matches.first().row(), TankInfoModel::ML), new_cyl.type.size.mliter); + if (new_cyl.type.workingpressure.mbar != cyl.type.workingpressure.mbar) + tim->setData(tim->index(matches.first().row(), TankInfoModel::BAR), new_cyl.type.workingpressure.mbar / 1000.0); + } +} + +EditCylinder::~EditCylinder() +{ + free_cylinder(new_cyl); +} + +void EditCylinder::redo() +{ + for (size_t i = 0; i < dives.size(); ++i) { + set_cylinder(dives[i], indexes[i], new_cyl); + emit diveListNotifier.cylinderEdited(dives[i], indexes[i]); + } + std::swap(cyl, new_cyl); +} + +// Undo and redo do the same as just the stored value is exchanged +void EditCylinder::undo() +{ + redo(); +} + #ifdef SUBSURFACE_MOBILE EditDive::EditDive(dive *oldDiveIn, dive *newDiveIn, dive_site *createDs, dive_site *editDs, location_t dsLocationIn) diff --git a/commands/command_edit.h b/commands/command_edit.h index e99ce2407..88f359fba 100644 --- a/commands/command_edit.h +++ b/commands/command_edit.h @@ -377,6 +377,45 @@ private: void redo() override; }; +class AddCylinder : public EditDivesBase { +public: + AddCylinder(bool currentDiveOnly); + ~AddCylinder(); +private: + cylinder_t cyl; + void undo() override; + void redo() override; + bool workToBeDone() override; +}; + +class EditCylinderBase : public EditDivesBase { +protected: + EditCylinderBase(int index, bool currentDiveOnly); + ~EditCylinderBase(); + + cylinder_t cyl; + std::vector indexes; // An index for each dive in the dives vector. + bool workToBeDone() override; +}; + +class RemoveCylinder : public EditCylinderBase { +public: + RemoveCylinder(int index, bool currentDiveOnly); +private: + void undo() override; + void redo() override; +}; + +class EditCylinder : public EditCylinderBase { +public: + EditCylinder(int index, cylinder_t cyl, bool currentDiveOnly); // Clones cylinder + ~EditCylinder(); +private: + cylinder_t new_cyl; + 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. @@ -406,5 +445,4 @@ private: #endif // SUBSURFACE_MOBILE } // namespace Command - #endif diff --git a/core/subsurface-qt/divelistnotifier.h b/core/subsurface-qt/divelistnotifier.h index aafe29c0c..2a72cdf49 100644 --- a/core/subsurface-qt/divelistnotifier.h +++ b/core/subsurface-qt/divelistnotifier.h @@ -87,6 +87,9 @@ signals: void divesTimeChanged(timestamp_t delta, const QVector &dives); void cylindersReset(const QVector &dives); + void cylinderAdded(dive *d, int pos); + void cylinderRemoved(dive *d, int pos); + void cylinderEdited(dive *d, int pos); void weightsystemsReset(const QVector &dives); void weightAdded(dive *d, int pos); void weightRemoved(dive *d, int pos); From 30d289e4a8a0caadcf30f06872ce69f44cf0bc0d Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 23 Feb 2020 12:44:09 +0100 Subject: [PATCH 07/78] CylinderModel: make dive dynamic The CylinderModel always accessed the global "displayed_dive" and in some special cases also "current_dive". To implement cylinder undo, the model should work on an arbitrary dive. Therefore, in analogy to the weight model, make the dive dynamic. Signed-off-by: Berthold Stoeger --- desktop-widgets/mainwindow.cpp | 2 +- .../tab-widgets/TabDiveEquipment.cpp | 4 +- qt-models/cylindermodel.cpp | 133 ++++++++++-------- qt-models/cylindermodel.h | 5 +- qt-models/diveplannermodel.cpp | 8 +- 5 files changed, 87 insertions(+), 65 deletions(-) diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 8474eee71..78877d1a5 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -918,7 +918,7 @@ void MainWindow::on_actionReplanDive_triggered() divePlannerWidget->setSalinity(current_dive->salinity); DivePlannerPointsModel::instance()->loadFromDive(current_dive); reset_cylinders(&displayed_dive, true); - DivePlannerPointsModel::instance()->cylindersModel()->updateDive(); + DivePlannerPointsModel::instance()->cylindersModel()->updateDive(&displayed_dive); } void MainWindow::on_actionDivePlanner_triggered() diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp index 33a8e5a3c..d34b44c74 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp @@ -129,7 +129,7 @@ void TabDiveEquipment::toggleTriggeredColumn() void TabDiveEquipment::updateData() { - cylindersModel->updateDive(); + cylindersModel->updateDive(current_dive); weightModel->updateDive(current_dive); suitModel.updateModel(); @@ -262,7 +262,7 @@ void TabDiveEquipment::acceptChanges() void TabDiveEquipment::rejectChanges() { cylindersModel->model()->changed = false; - cylindersModel->updateDive(); + cylindersModel->updateDive(current_dive); weightModel->updateDive(current_dive); } diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 428d3c868..5a9fe0f5e 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -10,9 +10,9 @@ #include "core/subsurface-qt/divelistnotifier.h" #include "core/subsurface-string.h" -CylindersModel::CylindersModel(QObject *parent) : - CleanerTableModel(parent), +CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), changed(false), + d(nullptr), rows(0) { // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH, MOD, MND, USE, IS_USED}; @@ -126,13 +126,12 @@ static QVariant percent_string(fraction_t fraction) bool CylindersModel::cylinderUsed(int i) const { - const struct dive *dive = &displayed_dive; - if (i < 0 || i >= dive->cylinders.nr) + if (i < 0 || i >= d->cylinders.nr) return false; - if (is_cylinder_used(dive, i)) + if (is_cylinder_used(d, i)) return true; - cylinder_t *cyl = get_cylinder(dive, i); + cylinder_t *cyl = get_cylinder(d, i); if (cyl->start.mbar || cyl->sample_start.mbar || cyl->end.mbar || cyl->sample_end.mbar) return true; @@ -148,15 +147,15 @@ bool CylindersModel::cylinderUsed(int i) const QVariant CylindersModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= rows) + if (!d || !index.isValid() || index.row() >= rows) return QVariant(); - if (index.row() >= displayed_dive.cylinders.nr) { - qWarning("CylindersModel and displayed_dive are out of sync!"); + if (index.row() >= d->cylinders.nr) { + qWarning("CylindersModel and dive are out of sync!"); return QVariant(); } - const cylinder_t *cyl = get_cylinder(&displayed_dive, index.row()); + const cylinder_t *cyl = get_cylinder(d, index.row()); switch (role) { case Qt::BackgroundRole: { @@ -227,13 +226,13 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const } else { pressure_t modpO2; modpO2.mbar = prefs.bottompo2; - return get_depth_string(gas_mod(cyl->gasmix, modpO2, &displayed_dive, M_OR_FT(1,1)), true); + return get_depth_string(gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(1,1)), true); } case MND: if (cyl->bestmix_he) return QStringLiteral("*"); else - return get_depth_string(gas_mnd(cyl->gasmix, prefs.bestmixend, &displayed_dive, M_OR_FT(1,1)), true); + return get_depth_string(gas_mnd(cyl->gasmix, prefs.bestmixend, d, M_OR_FT(1,1)), true); break; case USE: return gettextFromC::tr(cylinderuse_text[cyl->cylinder_use]); @@ -247,7 +246,7 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const case Qt::SizeHintRole: if (index.column() == REMOVE) { if ((in_planner() && DivePlannerPointsModel::instance()->tankInUse(index.row())) || - (!in_planner() && is_cylinder_prot(&displayed_dive, index.row()))) { + (!in_planner() && is_cylinder_prot(d, index.row()))) { return trashForbiddenIcon(); } return trashIcon(); @@ -257,7 +256,7 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const switch (index.column()) { case REMOVE: if ((in_planner() && DivePlannerPointsModel::instance()->tankInUse(index.row())) || - (!in_planner() && is_cylinder_prot(&displayed_dive, index.row()))) { + (!in_planner() && is_cylinder_prot(d, index.row()))) { return tr("This gas is in use. Only cylinders that are not used in the dive can be removed."); } return tr("Clicking here will remove this cylinder."); @@ -285,13 +284,18 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const cylinder_t *CylindersModel::cylinderAt(const QModelIndex &index) { - return get_cylinder(&displayed_dive, index.row()); + if (!d) + return nullptr; + return get_cylinder(d, index.row()); } bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, int role) { QString vString; + if (!d) + return false; + cylinder_t *cyl = cylinderAt(index); if (!cyl) return false; @@ -369,12 +373,12 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) cyl->gasmix.he.permille = 1000 - get_o2(cyl->gasmix); pressure_t modpO2; - if (displayed_dive.dc.divemode == PSCR) + if (d->dc.divemode == PSCR) modpO2.mbar = prefs.decopo2 + (1000 - get_o2(cyl->gasmix)) * SURFACE_PRESSURE * prefs.o2consumption / prefs.decosac / prefs.pscr_ratio; else modpO2.mbar = prefs.decopo2; - cyl->depth = gas_mod(cyl->gasmix, modpO2, &displayed_dive, M_OR_FT(3, 10)); + cyl->depth = gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(3, 10)); cyl->bestmix_o2 = false; changed = true; } @@ -400,15 +404,15 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (QString::compare(qPrintable(vString), "*") == 0) { cyl->bestmix_o2 = true; // Calculate fO2 for max. depth - cyl->gasmix.o2 = best_o2(displayed_dive.maxdepth, &displayed_dive); + cyl->gasmix.o2 = best_o2(d->maxdepth, d); } else { cyl->bestmix_o2 = false; // Calculate fO2 for input depth - cyl->gasmix.o2 = best_o2(string_to_depth(qPrintable(vString)), &displayed_dive); + cyl->gasmix.o2 = best_o2(string_to_depth(qPrintable(vString)), d); } pressure_t modpO2; modpO2.mbar = prefs.decopo2; - cyl->depth = gas_mod(cyl->gasmix, modpO2, &displayed_dive, M_OR_FT(3, 10)); + cyl->depth = gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(3, 10)); changed = true; } break; @@ -417,11 +421,11 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (QString::compare(qPrintable(vString), "*") == 0) { cyl->bestmix_he = true; // Calculate fO2 for max. depth - cyl->gasmix.he = best_he(displayed_dive.maxdepth, &displayed_dive, prefs.o2narcotic, cyl->gasmix.o2); + cyl->gasmix.he = best_he(d->maxdepth, d, prefs.o2narcotic, cyl->gasmix.o2); } else { cyl->bestmix_he = false; // Calculate fHe for input depth - cyl->gasmix.he = best_he(string_to_depth(qPrintable(vString)), &displayed_dive, prefs.o2narcotic, cyl->gasmix.o2); + cyl->gasmix.he = best_he(string_to_depth(qPrintable(vString)), d, prefs.o2narcotic, cyl->gasmix.o2); } changed = true; } @@ -447,10 +451,12 @@ int CylindersModel::rowCount(const QModelIndex&) const void CylindersModel::add() { + if (!d) + return; int row = rows; - cylinder_t cyl = create_new_cylinder(&displayed_dive); + cylinder_t cyl = create_new_cylinder(d); beginInsertRows(QModelIndex(), row, row); - add_to_cylinder_table(&displayed_dive.cylinders, row, cyl); + add_to_cylinder_table(&d->cylinders, row, cyl); rows++; changed = true; endInsertRows(); @@ -465,13 +471,15 @@ void CylindersModel::clear() } } -void CylindersModel::updateDive() +void CylindersModel::updateDive(dive *dIn) { #ifdef DEBUG_CYL - dump_cylinders(&displayed_dive, true); + if (d) + dump_cylinders(dIn, true); #endif beginResetModel(); - rows = displayed_dive.cylinders.nr; + d = dIn; + rows = d ? d->cylinders.nr : 0; endResetModel(); } @@ -484,6 +492,8 @@ Qt::ItemFlags CylindersModel::flags(const QModelIndex &index) const void CylindersModel::remove(QModelIndex index) { + if (!d) + return; if (index.column() == USE) { cylinder_t *cyl = cylinderAt(index); if (cyl->cylinder_use == OC_GAS) @@ -499,12 +509,12 @@ void CylindersModel::remove(QModelIndex index) return; if ((in_planner() && DivePlannerPointsModel::instance()->tankInUse(index.row())) || - (!in_planner() && is_cylinder_prot(&displayed_dive, index.row()))) + (!in_planner() && is_cylinder_prot(d, index.row()))) return; beginRemoveRows(QModelIndex(), index.row(), index.row()); rows--; - remove_cylinder(&displayed_dive, index.row()); + remove_cylinder(d, index.row()); changed = true; endRemoveRows(); @@ -512,12 +522,12 @@ void CylindersModel::remove(QModelIndex index) // 1) Fill mapping[0]..mapping[index-1] with 0..index // 2) Set mapping[index] to -1 // 3) Fill mapping[index+1]..mapping[end] with index.. - std::vector mapping(displayed_dive.cylinders.nr + 1); + std::vector mapping(d->cylinders.nr + 1); std::iota(mapping.begin(), mapping.begin() + index.row(), 0); mapping[index.row()] = -1; std::iota(mapping.begin() + index.row() + 1, mapping.end(), index.row()); - cylinder_renumber(&displayed_dive, &mapping[0]); + cylinder_renumber(d, &mapping[0]); if (in_planner()) DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]); changed = true; @@ -525,23 +535,26 @@ void CylindersModel::remove(QModelIndex index) void CylindersModel::moveAtFirst(int cylid) { + if (!d) + return; + cylinder_t temp_cyl; beginMoveRows(QModelIndex(), cylid, cylid, QModelIndex(), 0); - memmove(&temp_cyl, get_cylinder(&displayed_dive, cylid), sizeof(temp_cyl)); + memmove(&temp_cyl, get_cylinder(d, cylid), sizeof(temp_cyl)); for (int i = cylid - 1; i >= 0; i--) - memmove(get_cylinder(&displayed_dive, i + 1), get_cylinder(&displayed_dive, i), sizeof(temp_cyl)); - memmove(get_cylinder(&displayed_dive, 0), &temp_cyl, sizeof(temp_cyl)); + memmove(get_cylinder(d, i + 1), get_cylinder(d, i), sizeof(temp_cyl)); + memmove(get_cylinder(d, 0), &temp_cyl, sizeof(temp_cyl)); // Create a mapping of cylinder indices: // 1) Fill mapping[0]..mapping[cyl] with 0..index // 2) Set mapping[cyl] to 0 // 3) Fill mapping[cyl+1]..mapping[end] with cyl.. - std::vector mapping(displayed_dive.cylinders.nr); + std::vector mapping(d->cylinders.nr); std::iota(mapping.begin(), mapping.begin() + cylid, 1); mapping[cylid] = 0; std::iota(mapping.begin() + (cylid + 1), mapping.end(), cylid); - cylinder_renumber(&displayed_dive, &mapping[0]); + cylinder_renumber(d, &mapping[0]); if (in_planner()) DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]); changed = true; @@ -550,42 +563,51 @@ void CylindersModel::moveAtFirst(int cylid) void CylindersModel::updateDecoDepths(pressure_t olddecopo2) { + if (!d) + return; + pressure_t decopo2; decopo2.mbar = prefs.decopo2; - for (int i = 0; i < displayed_dive.cylinders.nr; i++) { - cylinder_t *cyl = get_cylinder(&displayed_dive, i); + for (int i = 0; i < d->cylinders.nr; i++) { + cylinder_t *cyl = get_cylinder(d, i); /* If the gas's deco MOD matches the old pO2, it will have been automatically calculated and should be updated. * If they don't match, we should leave the user entered depth as it is */ - if (cyl->depth.mm == gas_mod(cyl->gasmix, olddecopo2, &displayed_dive, M_OR_FT(3, 10)).mm) { - cyl->depth = gas_mod(cyl->gasmix, decopo2, &displayed_dive, M_OR_FT(3, 10)); + if (cyl->depth.mm == gas_mod(cyl->gasmix, olddecopo2, d, M_OR_FT(3, 10)).mm) { + cyl->depth = gas_mod(cyl->gasmix, decopo2, d, M_OR_FT(3, 10)); } } - emit dataChanged(createIndex(0, 0), createIndex(displayed_dive.cylinders.nr - 1, COLUMNS - 1)); + emit dataChanged(createIndex(0, 0), createIndex(d->cylinders.nr - 1, COLUMNS - 1)); } void CylindersModel::updateTrashIcon() { - emit dataChanged(createIndex(0, 0), createIndex(displayed_dive.cylinders.nr - 1, 0)); + if (!d) + return; + + emit dataChanged(createIndex(0, 0), createIndex(d->cylinders.nr - 1, 0)); } bool CylindersModel::updateBestMixes() { + if (!d) + return false; + // Check if any of the cylinders are best mixes, update if needed bool gasUpdated = false; - for (int i = 0; i < displayed_dive.cylinders.nr; i++) { - cylinder_t *cyl = get_cylinder(&displayed_dive, i); + for (int i = 0; i < d->cylinders.nr; i++) { + cylinder_t *cyl = get_cylinder(d, i); if (cyl->bestmix_o2) { - cyl->gasmix.o2 = best_o2(displayed_dive.maxdepth, &displayed_dive); + cyl->gasmix.o2 = best_o2(d->maxdepth, d); // fO2 + fHe must not be greater than 1 if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) cyl->gasmix.he.permille = 1000 - get_o2(cyl->gasmix); pressure_t modpO2; modpO2.mbar = prefs.decopo2; - cyl->depth = gas_mod(cyl->gasmix, modpO2, &displayed_dive, M_OR_FT(3, 10)); + cyl->depth = gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(3, 10)); gasUpdated = true; } if (cyl->bestmix_he) { - cyl->gasmix.he = best_he(displayed_dive.maxdepth, &displayed_dive, prefs.o2narcotic, cyl->gasmix.o2); + cyl->gasmix.he = best_he(d->maxdepth, d, prefs.o2narcotic, cyl->gasmix.o2); // fO2 + fHe must not be greater than 1 if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) cyl->gasmix.o2.permille = 1000 - get_he(cyl->gasmix); @@ -595,7 +617,7 @@ bool CylindersModel::updateBestMixes() /* This slot is called when the bottom pO2 and END preferences are updated, we want to * emit dataChanged so MOD and MND are refreshed, even if the gas mix hasn't been changed */ if (gasUpdated) - emit dataChanged(createIndex(0, 0), createIndex(displayed_dive.cylinders.nr - 1, COLUMNS - 1)); + emit dataChanged(createIndex(0, 0), createIndex(d->cylinders.nr - 1, COLUMNS - 1)); return gasUpdated; } @@ -603,14 +625,13 @@ void CylindersModel::cylindersReset(const QVector &dives) { // This model only concerns the currently displayed dive. If this is not among the // dives that had their cylinders reset, exit. - if (!current_dive || std::find(dives.begin(), dives.end(), current_dive) == dives.end()) + if (!d || std::find(dives.begin(), dives.end(), d) == dives.end()) return; - // Copy the cylinders from the current dive to the displayed dive. - copy_cylinders(¤t_dive->cylinders, &displayed_dive.cylinders); - - // And update the model.. - updateDive(); + // And update the model (the actual change was already performed in the backend).. + beginResetModel(); + rows = d->cylinders.nr; + endResetModel(); } CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent) @@ -618,9 +639,9 @@ CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterPro setSourceModel(&source); } -void CylindersModelFiltered::updateDive() +void CylindersModelFiltered::updateDive(dive *d) { - source.updateDive(); + source.updateDive(d); } void CylindersModelFiltered::clear() diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index 5cfe72d2d..201459b72 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -41,7 +41,7 @@ public: void add(); void clear(); - void updateDive(); + void updateDive(dive *d); void updateDecoDepths(pressure_t olddecopo2); void updateTrashIcon(); void moveAtFirst(int cylid); @@ -56,6 +56,7 @@ slots: void cylindersReset(const QVector &dives); private: + dive *d; int rows; cylinder_t *cylinderAt(const QModelIndex &index); }; @@ -69,7 +70,7 @@ public: void clear(); void add(); - void updateDive(); + void updateDive(dive *d); public slots: void remove(QModelIndex index); diff --git a/qt-models/diveplannermodel.cpp b/qt-models/diveplannermodel.cpp index df7affe8b..4928900c4 100644 --- a/qt-models/diveplannermodel.cpp +++ b/qt-models/diveplannermodel.cpp @@ -92,7 +92,7 @@ void DivePlannerPointsModel::loadFromDive(dive *d) const struct event *evd = NULL; enum divemode_t current_divemode = UNDEF_COMP_TYPE; recalc = false; - cylinders.updateDive(); + cylinders.updateDive(&displayed_dive); duration_t lasttime = { 0 }; duration_t lastrecordedtime = {}; duration_t newtime = {}; @@ -165,7 +165,7 @@ void DivePlannerPointsModel::setupCylinders() reset_cylinders(&displayed_dive, true); if (displayed_dive.cylinders.nr > 0) { - cylinders.updateDive(); + cylinders.updateDive(&displayed_dive); return; // We have at least one cylinder } } @@ -183,7 +183,7 @@ void DivePlannerPointsModel::setupCylinders() add_to_cylinder_table(&displayed_dive.cylinders, 0, cyl); } reset_cylinders(&displayed_dive, false); - cylinders.updateDive(); + cylinders.updateDive(&displayed_dive); } // Update the dive's maximum depth. Returns true if max. depth changed @@ -908,7 +908,7 @@ void DivePlannerPointsModel::clear() { bool oldRecalc = setRecalc(false); - cylinders.updateDive(); + cylinders.updateDive(&displayed_dive); if (rowCount() > 0) { beginRemoveRows(QModelIndex(), 0, rowCount() - 1); divepoints.clear(); From a37939889b1a77dd269fd7f25f97b813f733133a Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 23 Feb 2020 12:52:45 +0100 Subject: [PATCH 08/78] cleanup: remove CylindersModels::rows Access the number of cylinders in the dive directly instead. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 20 +++++++------------- qt-models/cylindermodel.h | 1 - 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 5a9fe0f5e..4b329eafe 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -12,8 +12,7 @@ CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), changed(false), - d(nullptr), - rows(0) + d(nullptr) { // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH, MOD, MND, USE, IS_USED}; setHeaderDataStrings(QStringList() << "" << tr("Type") << tr("Size") << tr("Work press.") << tr("Start press.") << tr("End press.") << tr("O₂%") << tr("He%") @@ -147,7 +146,7 @@ bool CylindersModel::cylinderUsed(int i) const QVariant CylindersModel::data(const QModelIndex &index, int role) const { - if (!d || !index.isValid() || index.row() >= rows) + if (!d || !index.isValid() || index.row() >= d->cylinders.nr) return QVariant(); if (index.row() >= d->cylinders.nr) { @@ -446,18 +445,17 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in int CylindersModel::rowCount(const QModelIndex&) const { - return rows; + return d ? d->cylinders.nr : 0; } void CylindersModel::add() { if (!d) return; - int row = rows; + int row = d->cylinders.nr; cylinder_t cyl = create_new_cylinder(d); beginInsertRows(QModelIndex(), row, row); add_to_cylinder_table(&d->cylinders, row, cyl); - rows++; changed = true; endInsertRows(); emit dataChanged(createIndex(row, 0), createIndex(row, COLUMNS - 1)); @@ -465,10 +463,9 @@ void CylindersModel::add() void CylindersModel::clear() { - if (rows > 0) { - beginRemoveRows(QModelIndex(), 0, rows - 1); - endRemoveRows(); - } + beginResetModel(); + d = nullptr; + endResetModel(); } void CylindersModel::updateDive(dive *dIn) @@ -479,7 +476,6 @@ void CylindersModel::updateDive(dive *dIn) #endif beginResetModel(); d = dIn; - rows = d ? d->cylinders.nr : 0; endResetModel(); } @@ -513,7 +509,6 @@ void CylindersModel::remove(QModelIndex index) return; beginRemoveRows(QModelIndex(), index.row(), index.row()); - rows--; remove_cylinder(d, index.row()); changed = true; endRemoveRows(); @@ -630,7 +625,6 @@ void CylindersModel::cylindersReset(const QVector &dives) // And update the model (the actual change was already performed in the backend).. beginResetModel(); - rows = d->cylinders.nr; endResetModel(); } diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index 201459b72..40eaf98a1 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -57,7 +57,6 @@ slots: private: dive *d; - int rows; cylinder_t *cylinderAt(const QModelIndex &index); }; From 5b7a3165932d9b3fced80fec17de01eb1ad89bf7 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 23 Feb 2020 19:24:06 +0100 Subject: [PATCH 09/78] undo: reorder cylinders on remove-cylinder undo/redo The cylinders in the events must be reordered if we remove a cylinder. To avoid duplication of code, move the reordering function into qthelper.cpp, though it might not be ideal there. Signed-off-by: Berthold Stoeger --- commands/command_edit.cpp | 3 +++ core/qthelper.cpp | 33 +++++++++++++++++++++++++++++++++ core/qthelper.h | 2 ++ qt-models/cylindermodel.cpp | 10 +--------- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index c06ef1ffa..3ba7a2823 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -1106,6 +1106,7 @@ RemoveCylinder::RemoveCylinder(int index, bool currentDiveOnly) : void RemoveCylinder::undo() { for (size_t i = 0; i < dives.size(); ++i) { + std::vector mapping = get_cylinder_map_for_add(dives[i]->cylinders.nr, indexes[i]); add_to_cylinder_table(&dives[i]->cylinders, indexes[i], clone_cylinder(cyl)); emit diveListNotifier.cylinderAdded(dives[i], indexes[i]); } @@ -1114,7 +1115,9 @@ void RemoveCylinder::undo() void RemoveCylinder::redo() { for (size_t i = 0; i < dives.size(); ++i) { + std::vector mapping = get_cylinder_map_for_remove(dives[i]->cylinders.nr, indexes[i]); remove_cylinder(dives[i], indexes[i]); + cylinder_renumber(dives[i], &mapping[0]); emit diveListNotifier.cylinderRemoved(dives[i], indexes[i]); } } diff --git a/core/qthelper.cpp b/core/qthelper.cpp index 7b879e7d6..10e62eabe 100644 --- a/core/qthelper.cpp +++ b/core/qthelper.cpp @@ -1661,3 +1661,36 @@ extern "C" char *get_changes_made() else return nullptr; } + +// Generate a cylinder-renumber map for use when the n-th cylinder +// of a dive with count cylinders is removed. It fills an int vector +// with 0..n, -1, n..count-1. Each entry in the vector represents +// the new id of the cylinder, whereby <0 means that this particular +// cylinder does not get any new id. This should probably be moved +// to the C-core, but using std::vector is simply more convenient. +// The function assumes that n < count! +std::vector get_cylinder_map_for_remove(int count, int n) +{ + // 1) Fill mapping[0]..mapping[n-1] with 0..n-1 + // 2) Set mapping[n] to -1 + // 3) Fill mapping[n+1]..mapping[count-1] with n..count-2 + std::vector mapping(count); + std::iota(mapping.begin(), mapping.begin() + n, 0); + mapping[n] = -1; + std::iota(mapping.begin() + n + 1, mapping.end(), n); + return mapping; +} + +// Generate a cylinder-renumber map for use when a cylinder is added +// before the n-th cylinder. It fills an int vector with +// with 0..n-1, n+1..count. Each entry in the vector represents +// the new id of the cylinder. This probably should be moved +// to the C-core, but using std::vector is simply more convenient. +// This function assumes that that n <= count! +std::vector get_cylinder_map_for_add(int count, int n) +{ + std::vector mapping(count); + std::iota(mapping.begin(), mapping.begin() + n, 0); + std::iota(mapping.begin() + n, mapping.end(), n + 1); + return mapping; +} diff --git a/core/qthelper.h b/core/qthelper.h index bb8876383..848763138 100644 --- a/core/qthelper.h +++ b/core/qthelper.h @@ -83,6 +83,8 @@ QLocale getLocale(); QVector> selectedDivesGasUsed(); QString getUserAgent(); QString printGPSCoords(const location_t *loc); +std::vector get_cylinder_map_for_remove(int count, int n); +std::vector get_cylinder_map_for_add(int count, int n); extern QString (*changesCallback)(); void uiNotification(const QString &msg); diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 4b329eafe..3a3f14605 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -513,15 +513,7 @@ void CylindersModel::remove(QModelIndex index) changed = true; endRemoveRows(); - // Create a mapping of cylinder indices: - // 1) Fill mapping[0]..mapping[index-1] with 0..index - // 2) Set mapping[index] to -1 - // 3) Fill mapping[index+1]..mapping[end] with index.. - std::vector mapping(d->cylinders.nr + 1); - std::iota(mapping.begin(), mapping.begin() + index.row(), 0); - mapping[index.row()] = -1; - std::iota(mapping.begin() + index.row() + 1, mapping.end(), index.row()); - + std::vector mapping = get_cylinder_map_for_remove(d->cylinders.nr + 1, index.row()); cylinder_renumber(d, &mapping[0]); if (in_planner()) DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]); From c4bf1ce8916c3549e51b455548f87508f3b5e23b Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 24 Feb 2020 10:57:36 +0100 Subject: [PATCH 10/78] undo: remove only "non-protected" cylinders The undo-code must take care not to remove used cylinders. To do so, extend the constructor of EditCylinderBase, which collects the cylinders and dives to edit, by the "nonProtectedOnly" boolean argument. If true, only those cylinders for wich "is_cylinder_prot" returns false will be added. Signed-off-by: Berthold Stoeger --- commands/command_edit.cpp | 16 +++++++++------- commands/command_edit.h | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index 3ba7a2823..fc5200b52 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -1053,7 +1053,7 @@ static int find_cylinder_index(const struct dive *d, const cylinder_t &cyl) return -1; } -EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly) : +EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly) : EditDivesBase(currentDiveOnly), cyl(empty_cylinder) { @@ -1070,15 +1070,17 @@ EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly) : for (dive *d: dives) { if (d == current) { + if (nonProtectedOnly && is_cylinder_prot(d, index)) + continue; divesNew.push_back(d); indexes.push_back(index); continue; } int idx = find_cylinder_index(d, cyl); - if (idx >= 0) { - divesNew.push_back(d); - indexes.push_back(idx); - } + if (idx < 0 || (nonProtectedOnly && is_cylinder_prot(d, idx))) + continue; + divesNew.push_back(d); + indexes.push_back(idx); } dives = std::move(divesNew); } @@ -1095,7 +1097,7 @@ bool EditCylinderBase::workToBeDone() // ***** Remove Cylinder ***** RemoveCylinder::RemoveCylinder(int index, bool currentDiveOnly) : - EditCylinderBase(index, currentDiveOnly) + EditCylinderBase(index, currentDiveOnly, true) { if (dives.size() == 1) setText(tr("Remove cylinder")); @@ -1124,7 +1126,7 @@ void RemoveCylinder::redo() // ***** Edit Cylinder ***** EditCylinder::EditCylinder(int index, cylinder_t cylIn, bool currentDiveOnly) : - EditCylinderBase(index, currentDiveOnly), + EditCylinderBase(index, currentDiveOnly, false), new_cyl(empty_cylinder) { if (dives.empty()) diff --git a/commands/command_edit.h b/commands/command_edit.h index 88f359fba..c0b79d073 100644 --- a/commands/command_edit.h +++ b/commands/command_edit.h @@ -390,7 +390,7 @@ private: class EditCylinderBase : public EditDivesBase { protected: - EditCylinderBase(int index, bool currentDiveOnly); + EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly); ~EditCylinderBase(); cylinder_t cyl; From 1f8a45db44d12ced038d72c84d663885043e8663 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 24 Feb 2020 11:14:55 +0100 Subject: [PATCH 11/78] undo: call removeCylinder undo command in equipment tab Instead of connecting to the remove() function of the model, call the removeCylinder undo command. Take care to translate the index into the source index, should cylinders be hidden! Apart from the map-to-source call, this copies the weightsystem code. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/TabDiveEquipment.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp index d34b44c74..ea2d910ef 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp @@ -33,7 +33,6 @@ TabDiveEquipment::TabDiveEquipment(QWidget *parent) : TabBase(parent), ui.weights->setModel(weightModel); connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &TabDiveEquipment::divesChanged); - connect(ui.cylinders, &TableView::itemClicked, cylindersModel, &CylindersModelFiltered::remove); connect(ui.cylinders, &TableView::itemClicked, this, &TabDiveEquipment::editCylinderWidget); connect(ui.weights, &TableView::itemClicked, this, &TabDiveEquipment::editWeightWidget); @@ -164,14 +163,13 @@ void TabDiveEquipment::addWeight_clicked() void TabDiveEquipment::editCylinderWidget(const QModelIndex &index) { - if (cylindersModel->model()->changed && !MainWindow::instance()->mainTab->isEditing()) { - MainWindow::instance()->mainTab->enableEdition(); + if (!index.isValid()) return; - } - if (index.isValid() && index.column() != CylindersModel::REMOVE) { - MainWindow::instance()->mainTab->enableEdition(); + + if (index.column() == CylindersModel::REMOVE) + divesEdited(Command::removeCylinder(cylindersModel->mapToSource(index).row(), false)); + else ui.cylinders->edit(index); - } } void TabDiveEquipment::editWeightWidget(const QModelIndex &index) From 75f37a7d10b4c650552a344770c92a2038e9db34 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 24 Feb 2020 11:21:27 +0100 Subject: [PATCH 12/78] CylindersModel: don't test for planner-state in remove() This is only called from the planner. Therefore, the test is redundant. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 3a3f14605..7864da76c 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -486,6 +486,9 @@ Qt::ItemFlags CylindersModel::flags(const QModelIndex &index) const return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } +// This function is only invoked from the planner! Therefore, there is +// no need to check whether we are in the planner. Perhaps move some +// of this functionality to the planner itself. void CylindersModel::remove(QModelIndex index) { if (!d) @@ -504,8 +507,7 @@ void CylindersModel::remove(QModelIndex index) if (index.column() != REMOVE) return; - if ((in_planner() && DivePlannerPointsModel::instance()->tankInUse(index.row())) || - (!in_planner() && is_cylinder_prot(d, index.row()))) + if (DivePlannerPointsModel::instance()->tankInUse(index.row())) return; beginRemoveRows(QModelIndex(), index.row(), index.row()); @@ -515,8 +517,7 @@ void CylindersModel::remove(QModelIndex index) std::vector mapping = get_cylinder_map_for_remove(d->cylinders.nr + 1, index.row()); cylinder_renumber(d, &mapping[0]); - if (in_planner()) - DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]); + DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]); changed = true; } From 04ce908e8ed6239b8f143b3892da0dcdaa6e8262 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 26 Feb 2020 22:32:04 +0100 Subject: [PATCH 13/78] CylindersModel: listen and react to signals React to signals from the undo-commands and update the model accordingly. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 31 +++++++++++++++++++++++++++++++ qt-models/cylindermodel.h | 3 +++ 2 files changed, 34 insertions(+) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 7864da76c..8938bfbff 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -19,6 +19,9 @@ CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), << tr("Deco switch at") < &dives); + void cylinderAdded(dive *d, int pos); + void cylinderRemoved(dive *d, int pos); + void cylinderEdited(dive *d, int pos); private: dive *d; From 0732bb2302418fe07064ea04df3435ab527fbd00 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 18:40:32 +0100 Subject: [PATCH 14/78] undo: remove TabDiveWidget::acceptChanges and rejectChanges Since cylinders are now edited using the undo system, these functions are not needed anymore. Signed-off-by: Berthold Stoeger --- .../tab-widgets/TabDiveEquipment.cpp | 81 ------------------- .../tab-widgets/TabDiveEquipment.h | 2 - desktop-widgets/tab-widgets/maintab.cpp | 8 -- 3 files changed, 91 deletions(-) diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp index ea2d910ef..c5601f1ce 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp @@ -183,87 +183,6 @@ void TabDiveEquipment::editWeightWidget(const QModelIndex &index) ui.weights->edit(index); } -// tricky little macro to edit all the selected dives -// loop ove all DIVES and do WHAT. -#define MODIFY_DIVES(DIVES, WHAT) \ - do { \ - for (dive *mydive: DIVES) { \ - invalidate_dive_cache(mydive); \ - WHAT; \ - } \ - mark_divelist_changed(true); \ - } while (0) - -// Get the list of selected dives, but put the current dive at the last position of the vector -static QVector getSelectedDivesCurrentLast() -{ - QVector res; - struct dive *d; - int i; - for_each_dive (i, d) { - if (d->selected && d != current_dive) - res.append(d); - } - res.append(current_dive); - return res; -} - -// TODO: This is a temporary functions until undo of cylinders is implemented. -// Therefore it is not worth putting it in a header. -extern bool cylinders_equal(const dive *d1, const dive *d2); - -void TabDiveEquipment::acceptChanges() -{ - bool do_replot = false; - - // now check if something has changed and if yes, edit the selected dives that - // were identical with the master dive shown (and mark the divelist as changed) - struct dive *cd = current_dive; - - // Get list of selected dives, but put the current dive last; - // this is required in case the invocation wants to compare things - // to the original value in current_dive like it should - QVector selectedDives = getSelectedDivesCurrentLast(); - - if (cylindersModel->model()->changed) { - mark_divelist_changed(true); - MODIFY_DIVES(selectedDives, - // if we started out with the same cylinder description (for multi-edit) or if we do copt & paste - // make sure that we have the same cylinder type and copy the gasmix, but DON'T copy the start - // and end pressures (those are per dive after all) - if (cylinders_equal(mydive, cd) && mydive != cd) - copy_cylinder_types(&displayed_dive, cd); - copy_cylinders(&displayed_dive.cylinders, &cd->cylinders); - ); - /* if cylinders changed we may have changed gas change events - * and sensor idx in samples as well - * - so far this is ONLY supported for a single selected dive */ - struct divecomputer *tdc = ¤t_dive->dc; - struct divecomputer *sdc = &displayed_dive.dc; - while(tdc && sdc) { - free_events(tdc->events); - copy_events(sdc, tdc); - free(tdc->sample); - copy_samples(sdc, tdc); - tdc = tdc->next; - sdc = sdc->next; - } - do_replot = true; - } - - if (do_replot) - MainWindow::instance()->graphics->replot(); - - cylindersModel->model()->changed = false; -} - -void TabDiveEquipment::rejectChanges() -{ - cylindersModel->model()->changed = false; - cylindersModel->updateDive(current_dive); - weightModel->updateDive(current_dive); -} - void TabDiveEquipment::divesEdited(int i) { // No warning if only one dive was edited diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.h b/desktop-widgets/tab-widgets/TabDiveEquipment.h index 28e6235d1..3576b449e 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.h +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.h @@ -21,8 +21,6 @@ public: ~TabDiveEquipment(); void updateData() override; void clear() override; - void acceptChanges(); - void rejectChanges(); void divesEdited(int i); void closeWarning(); diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 252e135a6..ea20219ce 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -534,10 +534,6 @@ void MainTab::acceptChanges() ui.dateEdit->setEnabled(true); hideMessage(); - // TODO: This is a temporary hack until the equipment tab is included in the undo system: - // The equipment tab is hardcoded at the first place of the "extra widgets". - ((TabDiveEquipment *)extraWidgets[0])->acceptChanges(); - if (lastMode == MANUALLY_ADDED_DIVE) { MainWindow::instance()->showProfile(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING); @@ -606,10 +602,6 @@ void MainTab::rejectChanges() clear_dive(&displayed_dive); updateDiveInfo(); - // TODO: This is a temporary hack until the equipment tab is included in the undo system: - // The equipment tab is hardcoded at the first place of the "extra widgets". - ((TabDiveEquipment *)extraWidgets[0])->rejectChanges(); - // the user could have edited the location and then canceled the edit // let's get the correct location back in view MapWidget::instance()->centerOnDiveSite(current_dive ? current_dive->dive_site : nullptr); From d02489f8884d4fd5282d335727eeb68105fafc80 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 18:46:13 +0100 Subject: [PATCH 15/78] cylinders: remove CylindersModel::changed Nobody is testing that flag anymore. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 26 +++----------------------- qt-models/cylindermodel.h | 1 - 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 8938bfbff..d69fba3ae 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -11,7 +11,6 @@ #include "core/subsurface-string.h" CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), - changed(false), d(nullptr) { // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH, MOD, MND, USE, IS_USED}; @@ -329,7 +328,6 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (!same_string(qPrintable(type), cyl->type.description)) { free((void *)cyl->type.description); cyl->type.description = strdup(qPrintable(type)); - changed = true; } } break; @@ -342,7 +340,6 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in mark_divelist_changed(true); if (!matches.isEmpty()) tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl->type.size.mliter); - changed = true; } break; case WORKINGPRESS: @@ -352,21 +349,16 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in cyl->type.workingpressure = string_to_pressure(qPrintable(vString)); if (!matches.isEmpty()) tanks->setData(tanks->index(matches.first().row(), TankInfoModel::BAR), cyl->type.workingpressure.mbar / 1000.0); - changed = true; } break; case START: - if (CHANGED()) { + if (CHANGED()) cyl->start = string_to_pressure(qPrintable(vString)); - changed = true; - } break; case END: - if (CHANGED()) { + if (CHANGED()) //&& (!cyl->start.mbar || string_to_pressure(qPrintable(vString)).mbar <= cyl->start.mbar)) { cyl->end = string_to_pressure(qPrintable(vString)); - changed = true; - } break; case O2: if (CHANGED()) { @@ -382,7 +374,6 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in modpO2.mbar = prefs.decopo2; cyl->depth = gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(3, 10)); cyl->bestmix_o2 = false; - changed = true; } break; case HE: @@ -392,14 +383,11 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) cyl->gasmix.o2.permille = 1000 - get_he(cyl->gasmix); cyl->bestmix_he = false; - changed = true; } break; case DEPTH: - if (CHANGED()) { + if (CHANGED()) cyl->depth = string_to_depth(qPrintable(vString)); - changed = true; - } break; case MOD: if (CHANGED()) { @@ -415,7 +403,6 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in pressure_t modpO2; modpO2.mbar = prefs.decopo2; cyl->depth = gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(3, 10)); - changed = true; } break; case MND: @@ -429,7 +416,6 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in // Calculate fHe for input depth cyl->gasmix.he = best_he(string_to_depth(qPrintable(vString)), d, prefs.o2narcotic, cyl->gasmix.o2); } - changed = true; } break; case USE: @@ -438,7 +424,6 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (use > NUM_GAS_USE - 1 || use < 0) use = 0; cyl->cylinder_use = (enum cylinderuse)use; - changed = true; } break; } @@ -459,7 +444,6 @@ void CylindersModel::add() cylinder_t cyl = create_new_cylinder(d); beginInsertRows(QModelIndex(), row, row); add_to_cylinder_table(&d->cylinders, row, cyl); - changed = true; endInsertRows(); emit dataChanged(createIndex(row, 0), createIndex(row, COLUMNS - 1)); } @@ -502,7 +486,6 @@ void CylindersModel::remove(QModelIndex index) cyl->cylinder_use = NOT_USED; else if (cyl->cylinder_use == NOT_USED) cyl->cylinder_use = OC_GAS; - changed = true; dataChanged(index, index); return; } @@ -515,13 +498,11 @@ void CylindersModel::remove(QModelIndex index) beginRemoveRows(QModelIndex(), index.row(), index.row()); remove_cylinder(d, index.row()); - changed = true; endRemoveRows(); std::vector mapping = get_cylinder_map_for_remove(d->cylinders.nr + 1, index.row()); cylinder_renumber(d, &mapping[0]); DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]); - changed = true; } void CylindersModel::cylinderAdded(struct dive *changed, int pos) @@ -576,7 +557,6 @@ void CylindersModel::moveAtFirst(int cylid) cylinder_renumber(d, &mapping[0]); if (in_planner()) DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]); - changed = true; endMoveRows(); } diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index 85a59c9f5..ebebb46ab 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -45,7 +45,6 @@ public: void updateDecoDepths(pressure_t olddecopo2); void updateTrashIcon(); void moveAtFirst(int cylid); - bool changed; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool updateBestMixes(); bool cylinderUsed(int i) const; From a4a06c48bfe2fa6f1644d68bf82e98c196aed7d0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 18:51:13 +0100 Subject: [PATCH 16/78] CylindersModel: remove mark_dive_list_changed Cylinder-editing is controlled by undo (either by saving a planned dive or by using the equipment tab). There is no point in setting the dive_list_changed flag. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index d69fba3ae..9949ee98b 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -3,7 +3,6 @@ #include "tankinfomodel.h" #include "models.h" #include "core/qthelper.h" -#include "core/divelist.h" // for mark_divelist_changed() #include "core/color.h" #include "qt-models/diveplannermodel.h" #include "core/gettextfromc.h" @@ -337,7 +336,6 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); cyl->type.size = string_to_volume(qPrintable(vString), cyl->type.workingpressure); - mark_divelist_changed(true); if (!matches.isEmpty()) tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl->type.size.mliter); } From a75360f58f60d1cdbf102e58997555f490969144 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 19:01:43 +0100 Subject: [PATCH 17/78] CylindersModel: test for CHANGED() outside of switch statement A small code consolidation: With one exception, all targets of the switch statement would test for CHANGED(). Instead do the test once and exit early. This changes the behavior of the function: if not changed, there will be no more dataChanged-signal. However, this appears to be the correct thing to do anyway. And it is easily changed if it matters after all. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 70 ++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 9949ee98b..df8bd179d 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -321,17 +321,18 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in return false; } + bool changed = CHANGED(); + if (index.column() != TYPE && !changed) + return false; + switch (index.column()) { - case TYPE: { - QString type = value.toString(); - if (!same_string(qPrintable(type), cyl->type.description)) { - free((void *)cyl->type.description); - cyl->type.description = strdup(qPrintable(type)); - } + case TYPE: + if (!same_string(qPrintable(vString), cyl->type.description)) { + free((void *)cyl->type.description); + cyl->type.description = strdup(qPrintable(vString)); } break; - case SIZE: - if (CHANGED()) { + case SIZE: { TankInfoModel *tanks = TankInfoModel::instance(); QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); @@ -340,8 +341,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl->type.size.mliter); } break; - case WORKINGPRESS: - if (CHANGED()) { + case WORKINGPRESS: { TankInfoModel *tanks = TankInfoModel::instance(); QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); cyl->type.workingpressure = string_to_pressure(qPrintable(vString)); @@ -350,16 +350,13 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in } break; case START: - if (CHANGED()) - cyl->start = string_to_pressure(qPrintable(vString)); + cyl->start = string_to_pressure(qPrintable(vString)); break; case END: - if (CHANGED()) - //&& (!cyl->start.mbar || string_to_pressure(qPrintable(vString)).mbar <= cyl->start.mbar)) { - cyl->end = string_to_pressure(qPrintable(vString)); + //if (!cyl->start.mbar || string_to_pressure(qPrintable(vString)).mbar <= cyl->start.mbar) { + cyl->end = string_to_pressure(qPrintable(vString)); break; - case O2: - if (CHANGED()) { + case O2: { cyl->gasmix.o2 = string_to_fraction(qPrintable(vString)); // fO2 + fHe must not be greater than 1 if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) @@ -375,20 +372,16 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in } break; case HE: - if (CHANGED()) { - cyl->gasmix.he = string_to_fraction(qPrintable(vString)); - // fO2 + fHe must not be greater than 1 - if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) - cyl->gasmix.o2.permille = 1000 - get_he(cyl->gasmix); - cyl->bestmix_he = false; - } + cyl->gasmix.he = string_to_fraction(qPrintable(vString)); + // fO2 + fHe must not be greater than 1 + if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) + cyl->gasmix.o2.permille = 1000 - get_he(cyl->gasmix); + cyl->bestmix_he = false; break; case DEPTH: - if (CHANGED()) - cyl->depth = string_to_depth(qPrintable(vString)); + cyl->depth = string_to_depth(qPrintable(vString)); break; - case MOD: - if (CHANGED()) { + case MOD: { if (QString::compare(qPrintable(vString), "*") == 0) { cyl->bestmix_o2 = true; // Calculate fO2 for max. depth @@ -404,20 +397,17 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in } break; case MND: - if (CHANGED()) { - if (QString::compare(qPrintable(vString), "*") == 0) { - cyl->bestmix_he = true; - // Calculate fO2 for max. depth - cyl->gasmix.he = best_he(d->maxdepth, d, prefs.o2narcotic, cyl->gasmix.o2); - } else { - cyl->bestmix_he = false; - // Calculate fHe for input depth - cyl->gasmix.he = best_he(string_to_depth(qPrintable(vString)), d, prefs.o2narcotic, cyl->gasmix.o2); - } + if (QString::compare(qPrintable(vString), "*") == 0) { + cyl->bestmix_he = true; + // Calculate fO2 for max. depth + cyl->gasmix.he = best_he(d->maxdepth, d, prefs.o2narcotic, cyl->gasmix.o2); + } else { + cyl->bestmix_he = false; + // Calculate fHe for input depth + cyl->gasmix.he = best_he(string_to_depth(qPrintable(vString)), d, prefs.o2narcotic, cyl->gasmix.o2); } break; - case USE: - if (CHANGED()) { + case USE: { int use = vString.toInt(); if (use > NUM_GAS_USE - 1 || use < 0) use = 0; From 66fc9fcfecfd4d9a40a287b66c15d48691a24660 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 19:07:11 +0100 Subject: [PATCH 18/78] CylindersModel: fold CHANGED() macro into setData() The CHANGED macro was defined in the cleanerTableModel header. Since it had only one user, expand it there. The macro was very questionably anyway, as it would set the local "vString" variable. Signed-off-by: Berthold Stoeger --- qt-models/cleanertablemodel.h | 4 ---- qt-models/cylindermodel.cpp | 5 +++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/qt-models/cleanertablemodel.h b/qt-models/cleanertablemodel.h index 4299e9002..d54deb248 100644 --- a/qt-models/cleanertablemodel.h +++ b/qt-models/cleanertablemodel.h @@ -31,8 +31,4 @@ private: QStringList headers; }; -/* Has the string value changed */ -#define CHANGED() \ - (vString = value.toString()) != data(index, role).toString() - #endif diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index df8bd179d..62f9118f1 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -291,7 +291,6 @@ cylinder_t *CylindersModel::cylinderAt(const QModelIndex &index) bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, int role) { - QString vString; if (!d) return false; @@ -321,7 +320,9 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in return false; } - bool changed = CHANGED(); + QString vString = value.toString(); + bool changed = vString != data(index, role).toString(); + if (index.column() != TYPE && !changed) return false; From ff38c03e0069e57a5dae7907441c53eb776c65f0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 20:42:13 +0100 Subject: [PATCH 19/78] undo: add cylinders via undo Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/TabDiveEquipment.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp index c5601f1ce..13cfe77c4 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp @@ -152,8 +152,7 @@ void TabDiveEquipment::clear() void TabDiveEquipment::addCylinder_clicked() { - MainWindow::instance()->mainTab->enableEdition(); - cylindersModel->add(); + divesEdited(Command::addCylinder(false)); } void TabDiveEquipment::addWeight_clicked() From d597b6dca543f1f43d3921e001f45b76975b36f2 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 20:43:33 +0100 Subject: [PATCH 20/78] cleanup: remove unused CylindersModelFiltered functions add() and remove() are not used anymore since this is done using undo commands. The planner uses CylindersModel instead. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 10 ---------- qt-models/cylindermodel.h | 4 ---- 2 files changed, 14 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 62f9118f1..d76cdb344 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -636,21 +636,11 @@ void CylindersModelFiltered::clear() source.clear(); } -void CylindersModelFiltered::add() -{ - source.add(); -} - CylindersModel *CylindersModelFiltered::model() { return &source; } -void CylindersModelFiltered::remove(QModelIndex index) -{ - source.remove(mapToSource(index)); -} - bool CylindersModelFiltered::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { return prefs.display_unused_tanks || source.cylinderUsed(source_row); diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index ebebb46ab..d3d87b900 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -70,11 +70,7 @@ public: CylindersModel *model(); // Access to unfiltered base model void clear(); - void add(); void updateDive(dive *d); -public -slots: - void remove(QModelIndex index); private: CylindersModel source; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; From ee64a85d2e97c7f3d20de364ac908ac16fbfbc7a Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 20:55:56 +0100 Subject: [PATCH 21/78] cleanup: remove cylinders_equal check in MainTab::rejectChanges() This check make no more sense since cylinder editing is now treated by undo commands. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.cpp | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index ea20219ce..bd3919b24 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -556,33 +556,11 @@ void MainTab::acceptChanges() editMode = NONE; } -bool weightsystems_equal(const dive *d1, const dive *d2) -{ - if (d1->weightsystems.nr != d2->weightsystems.nr) - return false; - for (int i = 0; i < d1->weightsystems.nr; ++i) { - if (!same_weightsystem(d1->weightsystems.weightsystems[i], d2->weightsystems.weightsystems[i])) - return false; - } - return true; -} - -bool cylinders_equal(const dive *d1, const dive *d2) -{ - if (d1->cylinders.nr != d2->cylinders.nr) - return false; - for (int i = 0; i < d1->cylinders.nr; ++i) { - if (!same_cylinder(*get_cylinder(d1, i), *get_cylinder(d2, i))) - return false; - } - return true; -} - void MainTab::rejectChanges() { EditMode lastMode = editMode; - if (lastMode != NONE && current_dive && !cylinders_equal(current_dive, &displayed_dive)) { + if (lastMode != NONE && current_dive) { if (QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT(tr("Discard the changes?"), tr("You are about to discard your changes.")), QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Discard) != QMessageBox::Discard) { From 6288b2a61915b36563f2415e8abadd2f5cc57424 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 22:08:20 +0100 Subject: [PATCH 22/78] cleanup: remove strange recursive code in MainTab::enableEdition When enableEdition() was called with mode == NONE and the dive was a manually added dive, it would call into MainWindow:: editCurrentDive(), which would in turn call enableEdition(), however with another mode. Since the only caller of enableEdition() is now editCurrentDive() anyway, we can remove that weird code. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index bd3919b24..7bc04e7a7 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -210,21 +210,6 @@ void MainTab::enableEdition(EditMode newEditMode) { if (((newEditMode == DIVE || newEditMode == NONE) && current_dive == NULL) || editMode != NONE) return; - if ((newEditMode == DIVE || newEditMode == NONE) && - current_dive->dc.model && - strcmp(current_dive->dc.model, "manually added dive") == 0) { - // editCurrentDive will call enableEdition with newEditMode == MANUALLY_ADDED_DIVE - // so exit this function here after editCurrentDive() returns - - - - // FIXME : can we get rid of this recursive crap? - - - - MainWindow::instance()->editCurrentDive(); - return; - } ui.editDiveSiteButton->setEnabled(false); MainWindow::instance()->diveList->setEnabled(false); From 5f858e137d37e7fd2f6325b22651bf1842e872a0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 27 Feb 2020 22:10:43 +0100 Subject: [PATCH 23/78] edit: remove multi-dive version of edit information The "dive is currently" edited is only shown when the profile is edited. This affects only the current dive and therefore a message saying that multiple dives are edited makes no sense. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 7bc04e7a7..3aed133cd 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -220,11 +220,8 @@ void MainTab::enableEdition(EditMode newEditMode) ui.tabWidget->setTabEnabled(5, false); ui.dateEdit->setEnabled(true); - if (amount_selected > 1) { - displayMessage(tr("Multiple dives are being edited.")); - } else { - displayMessage(tr("This dive is being edited.")); - } + displayMessage(tr("This dive is being edited.")); + editMode = newEditMode != NONE ? newEditMode : DIVE; } From 150bd9763e7f95c589f2118f15bc2401bd3d3350 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 28 Feb 2020 00:29:56 +0100 Subject: [PATCH 24/78] cleanup: remove lastMode variable in MainTab::rejectChanges() This stored the old editMode. However, it was not read after editMode was changed, so there is no point to it. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 3aed133cd..2c64833da 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -540,9 +540,7 @@ void MainTab::acceptChanges() void MainTab::rejectChanges() { - EditMode lastMode = editMode; - - if (lastMode != NONE && current_dive) { + if (editMode != NONE && current_dive) { if (QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT(tr("Discard the changes?"), tr("You are about to discard your changes.")), QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Discard) != QMessageBox::Discard) { From 42b423f02c0b26eedd563be6f75df1e1952f6e72 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 28 Feb 2020 00:34:34 +0100 Subject: [PATCH 25/78] cleanup: don't center map on rejecting edit The editing of the dive site is controlled via an undo command. No point in centering the map when cancelling a profile-edit. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 2c64833da..3ae954c05 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -560,9 +560,6 @@ void MainTab::rejectChanges() clear_dive(&displayed_dive); updateDiveInfo(); - // the user could have edited the location and then canceled the edit - // let's get the correct location back in view - MapWidget::instance()->centerOnDiveSite(current_dive ? current_dive->dive_site : nullptr); // show the profile and dive info MainWindow::instance()->graphics->replot(); MainWindow::instance()->setEnabledToolbar(true); From 1aa25aad6dd553b80dbc8db18e513847121bea58 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 28 Feb 2020 00:43:05 +0100 Subject: [PATCH 26/78] desktop: don't disable tabs in edit state The edit state is now only used to edit the profile. There is no reason to disable random tabs. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 3ae954c05..311887169 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -215,9 +215,6 @@ void MainTab::enableEdition(EditMode newEditMode) MainWindow::instance()->diveList->setEnabled(false); MainWindow::instance()->setEnabledToolbar(false); MainWindow::instance()->enterEditState(); - ui.tabWidget->setTabEnabled(2, false); - ui.tabWidget->setTabEnabled(3, false); - ui.tabWidget->setTabEnabled(5, false); ui.dateEdit->setEnabled(true); displayMessage(tr("This dive is being edited.")); @@ -375,9 +372,6 @@ void MainTab::updateDiveInfo() if (lastSelectedDive && !onDiveSiteTab) lastTabSelectedDive = ui.tabWidget->currentIndex(); ui.tabWidget->setTabText(0, tr("Trip notes")); - ui.tabWidget->setTabEnabled(1, false); - ui.tabWidget->setTabEnabled(2, false); - ui.tabWidget->setTabEnabled(5, false); // Recover the tab selected for last dive trip but only if we're not on the dive site tab if (lastSelectedDive && !onDiveSiteTab) ui.tabWidget->setCurrentIndex(lastTabSelectedDiveTrip); @@ -416,11 +410,6 @@ void MainTab::updateDiveInfo() if (!lastSelectedDive && !onDiveSiteTab) lastTabSelectedDiveTrip = ui.tabWidget->currentIndex(); ui.tabWidget->setTabText(0, tr("Notes")); - ui.tabWidget->setTabEnabled(1, true); - ui.tabWidget->setTabEnabled(2, true); - ui.tabWidget->setTabEnabled(3, true); - ui.tabWidget->setTabEnabled(4, true); - ui.tabWidget->setTabEnabled(5, true); // Recover the tab selected for last dive but only if we're not on the dive site tab if (!lastSelectedDive && !onDiveSiteTab) ui.tabWidget->setCurrentIndex(lastTabSelectedDive); From 1aa06e680230351024c05152aeca9a189c5a8d4f Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 28 Feb 2020 08:36:13 +0100 Subject: [PATCH 27/78] cleanup: remove default-code for editCurrentDive The profile can only be edited for manually added or planned dives. No point in keeping code for other kinds of dives. Signed-off-by: Berthold Stoeger --- desktop-widgets/mainwindow.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 78877d1a5..e1d87b7e6 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -1793,21 +1793,19 @@ void MainWindow::editCurrentDive() struct dive *d = current_dive; QString defaultDC(d->dc.model); DivePlannerPointsModel::instance()->clear(); - disableShortcuts(); if (defaultDC == "manually added dive") { + disableShortcuts(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD); graphics->setAddState(); setApplicationState(ApplicationState::EditDive); DivePlannerPointsModel::instance()->loadFromDive(d); mainTab->enableEdition(MainTab::MANUALLY_ADDED_DIVE); } else if (defaultDC == "planned dive") { + disableShortcuts(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); setApplicationState(ApplicationState::EditPlannedDive); DivePlannerPointsModel::instance()->loadFromDive(d); mainTab->enableEdition(MainTab::MANUALLY_ADDED_DIVE); - } else { - setApplicationState(ApplicationState::EditDive); - mainTab->enableEdition(); } } From 1dcc885bb257d6c8c64074f8788b468397c34aaa Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 28 Feb 2020 20:38:04 +0100 Subject: [PATCH 28/78] undo/cylinders: Implement editing of the type This one is tricky, as when browsing through the types-combobox, the user is presented with presets without actually changing the dive. We do not want an undo-command for every change-event in the combo-box. Therefore, implement a scheme analoguous to the weight-editing: A temporary row can be set / committed or reset. Sadly, the code is more complex because we have to consider the planner, which is not included in the undo system. Firstly, the planner uses a different model, therefore all interactions are channeled through setData() with special roles. Secondly, in the planner we shouldn't place an undo command, but simply overwrite the dive. Signed-off-by: Berthold Stoeger --- desktop-widgets/modeldelegates.cpp | 34 ++++------- qt-models/cylindermodel.cpp | 95 ++++++++++++++++++++++++++---- qt-models/cylindermodel.h | 11 +++- 3 files changed, 106 insertions(+), 34 deletions(-) diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index ed7278b4c..65e8f377e 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -230,12 +230,6 @@ void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionV editor->setGeometry(defaultRect); } -static struct RevertCylinderData { - QString type; - int pressure; - int size; -} currCylinderData; - void TankInfoDelegate::setModelData(QWidget*, QAbstractItemModel*, const QModelIndex&) const { QAbstractItemModel *mymodel = currCombo.model; @@ -254,9 +248,9 @@ void TankInfoDelegate::setModelData(QWidget*, QAbstractItemModel*, const QModelI int tankSize = tanks->data(tanks->index(row, TankInfoModel::ML)).toInt(); int tankPressure = tanks->data(tanks->index(row, TankInfoModel::BAR)).toInt(); - mymodel->setData(IDX(CylindersModel::TYPE), cylinderName, Qt::EditRole); - mymodel->setData(IDX(CylindersModel::WORKINGPRESS), tankPressure, CylindersModel::PASS_IN_ROLE); - mymodel->setData(IDX(CylindersModel::SIZE), tankSize, CylindersModel::PASS_IN_ROLE); + mymodel->setData(IDX(CylindersModel::TYPE), cylinderName, CylindersModel::TEMP_ROLE); + mymodel->setData(IDX(CylindersModel::WORKINGPRESS), tankPressure, CylindersModel::TEMP_ROLE); + mymodel->setData(IDX(CylindersModel::SIZE), tankSize, CylindersModel::TEMP_ROLE); } TankInfoDelegate::TankInfoDelegate(QObject *parent) : ComboBoxDelegate(TankInfoModel::instance(), parent, true) @@ -275,25 +269,19 @@ void TankInfoDelegate::reenableReplot(QWidget*, QAbstractItemDelegate::EndEditHi void TankInfoDelegate::editorClosed(QWidget*, QAbstractItemDelegate::EndEditHint hint) { - if (hint == QAbstractItemDelegate::NoHint || - hint == QAbstractItemDelegate::RevertModelCache) { - QAbstractItemModel *mymodel = currCombo.model; - mymodel->setData(IDX(CylindersModel::TYPE), currCylinderData.type, Qt::EditRole); - mymodel->setData(IDX(CylindersModel::WORKINGPRESS), currCylinderData.pressure, CylindersModel::PASS_IN_ROLE); - mymodel->setData(IDX(CylindersModel::SIZE), currCylinderData.size, CylindersModel::PASS_IN_ROLE); - } + QAbstractItemModel *mymodel = currCombo.model; + // Ugly hack: We misuse setData() with COMMIT_ROLE or REVERT_ROLE to commit or + // revert the current row. We send in the type, because we may get multiple + // end events and thus can prevent multiple commits. + if (hint == QAbstractItemDelegate::RevertModelCache) + mymodel->setData(IDX(CylindersModel::TYPE), currCombo.activeText, CylindersModel::REVERT_ROLE); + else + mymodel->setData(IDX(CylindersModel::TYPE), currCombo.activeText, CylindersModel::COMMIT_ROLE); } QWidget *TankInfoDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { - // ncreate editor needs to be called before because it will populate a few - // things in the currCombo global var. QWidget *delegate = ComboBoxDelegate::createEditor(parent, option, index); - QAbstractItemModel *model = currCombo.model; - int row = index.row(); - currCylinderData.type = model->data(model->index(row, CylindersModel::TYPE)).value(); - currCylinderData.pressure = model->data(model->index(row, CylindersModel::WORKINGPRESS_INT)).value(); - currCylinderData.size = model->data(model->index(row, CylindersModel::SIZE_INT)).value(); MainWindow::instance()->graphics->setReplot(false); return delegate; } diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index d76cdb344..f2ee0c12e 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -2,6 +2,7 @@ #include "cylindermodel.h" #include "tankinfomodel.h" #include "models.h" +#include "commands/command.h" #include "core/qthelper.h" #include "core/color.h" #include "qt-models/diveplannermodel.h" @@ -10,7 +11,9 @@ #include "core/subsurface-string.h" CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), - d(nullptr) + d(nullptr), + tempRow(-1), + tempCyl(empty_cylinder) { // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH, MOD, MND, USE, IS_USED}; setHeaderDataStrings(QStringList() << "" << tr("Type") << tr("Size") << tr("Work press.") << tr("Start press.") << tr("End press.") << tr("O₂%") << tr("He%") @@ -155,7 +158,7 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const return QVariant(); } - const cylinder_t *cyl = get_cylinder(d, index.row()); + const cylinder_t *cyl = index.row() == tempRow ? &tempCyl : get_cylinder(d, index.row()); switch (role) { case Qt::BackgroundRole: { @@ -299,25 +302,49 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (!cyl) return false; - if (role == PASS_IN_ROLE) { - // this is our magic 'pass data in' function that allows the delegate to get - // the data here without silly unit conversions; - // so we only implement the two columns we care about + // Here we handle a few cases that allow us to set / commit / revert + // a temporary row. This is a horribly misuse of the model/view system. + // The reason it is done this way is that the planner and the equipment + // tab use different model-classes which are not in a superclass / subclass + // relationship. + switch (role) { + case TEMP_ROLE: + // TEMP_ROLE means that we are not supposed to write through to the + // actual dive, but a temporary cylinder that is displayed while the + // user browses throught the cylinder types. + initTempCyl(index.row()); + switch (index.column()) { + case TYPE: { + QString type = value.toString(); + if (!same_string(qPrintable(type), tempCyl.type.description)) { + free((void *)tempCyl.type.description); + tempCyl.type.description = strdup(qPrintable(type)); + dataChanged(index, index); + } + } case SIZE: - if (cyl->type.size.mliter != value.toInt()) { - cyl->type.size.mliter = value.toInt(); + if (tempCyl.type.size.mliter != value.toInt()) { + tempCyl.type.size.mliter = value.toInt(); dataChanged(index, index); } return true; case WORKINGPRESS: - if (cyl->type.workingpressure.mbar != value.toInt()) { - cyl->type.workingpressure.mbar = value.toInt(); + if (tempCyl.type.workingpressure.mbar != value.toInt()) { + tempCyl.type.workingpressure.mbar = value.toInt(); dataChanged(index, index); } return true; } return false; + case COMMIT_ROLE: + commitTempCyl(index.row()); + return true; + case REVERT_ROLE: + clearTempCyl(); + return true; + default: + break; } QString vString = value.toString(); @@ -621,6 +648,54 @@ void CylindersModel::cylindersReset(const QVector &dives) endResetModel(); } +// Save the cylinder in the given row so that we can revert if the user cancels a type-editing action. +void CylindersModel::initTempCyl(int row) +{ + if (!d || tempRow == row) + return; + clearTempCyl(); + const cylinder_t *cyl = get_cylinder(d, row); + if (!cyl) + return; + + tempRow = row; + tempCyl = clone_cylinder(*cyl); + + dataChanged(index(row, TYPE), index(row, USE)); +} + +void CylindersModel::clearTempCyl() +{ + if (tempRow < 0) + return; + int oldRow = tempRow; + tempRow = -1; + free_cylinder(tempCyl); + dataChanged(index(oldRow, TYPE), index(oldRow, USE)); +} + +void CylindersModel::commitTempCyl(int row) +{ +#ifndef SUBSURFACE_MOBILE + if (tempRow < 0) + return; + if (row != tempRow) + return clearTempCyl(); // Huh? We are supposed to commit a different row than the one we stored? + cylinder_t *cyl = get_cylinder(d, tempRow); + if (!cyl) + return; + // Only submit a command if the type changed + if (!same_string(cyl->type.description, tempCyl.type.description) || gettextFromC::tr(cyl->type.description) != QString(tempCyl.type.description)) { + if (in_planner()) + std::swap(*cyl, tempCyl); + else + Command::editCylinder(tempRow, tempCyl, false); + } + free_cylinder(tempCyl); + tempRow = -1; +#endif +} + CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent) { setSourceModel(&source); diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index d3d87b900..e4585b0ac 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -31,7 +31,9 @@ public: }; enum Roles { - PASS_IN_ROLE = Qt::UserRole + 1 // For setting data: don't do any conversions + TEMP_ROLE = Qt::UserRole + 1, // Temporarily set data, but don't store in dive + COMMIT_ROLE, // Save the temporary data to the dive. Must be set with Column == TYPE. + REVERT_ROLE // Revert to original data from dive. Must be set with Column == TYPE. }; explicit CylindersModel(QObject *parent = 0); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -59,7 +61,14 @@ slots: private: dive *d; + // Used if we temporarily change a line because the user is selecting a weight type + int tempRow; + cylinder_t tempCyl; + cylinder_t *cylinderAt(const QModelIndex &index); + void initTempCyl(int row); + void clearTempCyl(); + void commitTempCyl(int row); }; // Cylinder model that hides unused cylinders if the pref.show_unused_cylinders flag is not set From 01fa983182062e00b2e661a963384289e4de1beb Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 1 Mar 2020 15:32:24 +0100 Subject: [PATCH 29/78] undo/cylinder: call undo command to edit cylinder fields Call an undo command when editing cylinders, but only if on the EquipmentTab. To keep code changes small, make a copy of the cylinder first, then edit the cylinder as before and then either call an undo command (EquipmentTab) or overwrite the old cylinder (Planner). The memory management here is a bit strange: Since the undo-command itself makes a deep-copy of the passed in cylinder, we only do a shallow copy. If we have to change the type, we allocate the string with an std::string, so that the memory is automatically freed at the end of the function. However, this means that in the planner we have to make a deep copy first, swap old and new cylinder and finally release the old cylinder. Certainly not ideal, but for now the pragmatic thing to do. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 91 ++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index f2ee0c12e..c2bd0198a 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -9,6 +9,7 @@ #include "core/gettextfromc.h" #include "core/subsurface-qt/divelistnotifier.h" #include "core/subsurface-string.h" +#include CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), d(nullptr), @@ -298,8 +299,8 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (!d) return false; - cylinder_t *cyl = cylinderAt(index); - if (!cyl) + int row = index.row(); + if (row < 0 || row >= d->cylinders.nr) return false; // Here we handle a few cases that allow us to set / commit / revert @@ -350,100 +351,118 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in QString vString = value.toString(); bool changed = vString != data(index, role).toString(); + std::string newType; // If we allocate a new type string, this makes sure that it is freed at the end of the function + + // First, we make a shallow copy of the old cylinder. Then we modify the fields inside that copy. + // At the end, we either place an EditCylinder undo command (EquipmentTab) or copy the cylinder back (planner). + // Yes, this is not ideal, but the pragmatic thing to do for now. + cylinder_t cyl = d->cylinders.cylinders[row]; + if (index.column() != TYPE && !changed) return false; switch (index.column()) { case TYPE: - if (!same_string(qPrintable(vString), cyl->type.description)) { - free((void *)cyl->type.description); - cyl->type.description = strdup(qPrintable(vString)); - } + newType = qPrintable(vString); + cyl.type.description = newType.c_str(); break; case SIZE: { TankInfoModel *tanks = TankInfoModel::instance(); - QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); + QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl.type.description); - cyl->type.size = string_to_volume(qPrintable(vString), cyl->type.workingpressure); + cyl.type.size = string_to_volume(qPrintable(vString), cyl.type.workingpressure); if (!matches.isEmpty()) - tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl->type.size.mliter); + tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl.type.size.mliter); } break; case WORKINGPRESS: { TankInfoModel *tanks = TankInfoModel::instance(); - QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); - cyl->type.workingpressure = string_to_pressure(qPrintable(vString)); + QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl.type.description); + cyl.type.workingpressure = string_to_pressure(qPrintable(vString)); if (!matches.isEmpty()) - tanks->setData(tanks->index(matches.first().row(), TankInfoModel::BAR), cyl->type.workingpressure.mbar / 1000.0); + tanks->setData(tanks->index(matches.first().row(), TankInfoModel::BAR), cyl.type.workingpressure.mbar / 1000.0); } break; case START: - cyl->start = string_to_pressure(qPrintable(vString)); + cyl.start = string_to_pressure(qPrintable(vString)); break; case END: //if (!cyl->start.mbar || string_to_pressure(qPrintable(vString)).mbar <= cyl->start.mbar) { - cyl->end = string_to_pressure(qPrintable(vString)); + cyl.end = string_to_pressure(qPrintable(vString)); break; case O2: { - cyl->gasmix.o2 = string_to_fraction(qPrintable(vString)); + cyl.gasmix.o2 = string_to_fraction(qPrintable(vString)); // fO2 + fHe must not be greater than 1 - if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) - cyl->gasmix.he.permille = 1000 - get_o2(cyl->gasmix); + if (get_o2(cyl.gasmix) + get_he(cyl.gasmix) > 1000) + cyl.gasmix.he.permille = 1000 - get_o2(cyl.gasmix); pressure_t modpO2; if (d->dc.divemode == PSCR) - modpO2.mbar = prefs.decopo2 + (1000 - get_o2(cyl->gasmix)) * SURFACE_PRESSURE * + modpO2.mbar = prefs.decopo2 + (1000 - get_o2(cyl.gasmix)) * SURFACE_PRESSURE * prefs.o2consumption / prefs.decosac / prefs.pscr_ratio; else modpO2.mbar = prefs.decopo2; - cyl->depth = gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(3, 10)); - cyl->bestmix_o2 = false; + cyl.depth = gas_mod(cyl.gasmix, modpO2, d, M_OR_FT(3, 10)); + cyl.bestmix_o2 = false; } break; case HE: - cyl->gasmix.he = string_to_fraction(qPrintable(vString)); + cyl.gasmix.he = string_to_fraction(qPrintable(vString)); // fO2 + fHe must not be greater than 1 - if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) - cyl->gasmix.o2.permille = 1000 - get_he(cyl->gasmix); - cyl->bestmix_he = false; + if (get_o2(cyl.gasmix) + get_he(cyl.gasmix) > 1000) + cyl.gasmix.o2.permille = 1000 - get_he(cyl.gasmix); + cyl.bestmix_he = false; break; case DEPTH: - cyl->depth = string_to_depth(qPrintable(vString)); + cyl.depth = string_to_depth(qPrintable(vString)); break; case MOD: { if (QString::compare(qPrintable(vString), "*") == 0) { - cyl->bestmix_o2 = true; + cyl.bestmix_o2 = true; // Calculate fO2 for max. depth - cyl->gasmix.o2 = best_o2(d->maxdepth, d); + cyl.gasmix.o2 = best_o2(d->maxdepth, d); } else { - cyl->bestmix_o2 = false; + cyl.bestmix_o2 = false; // Calculate fO2 for input depth - cyl->gasmix.o2 = best_o2(string_to_depth(qPrintable(vString)), d); + cyl.gasmix.o2 = best_o2(string_to_depth(qPrintable(vString)), d); } pressure_t modpO2; modpO2.mbar = prefs.decopo2; - cyl->depth = gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(3, 10)); + cyl.depth = gas_mod(cyl.gasmix, modpO2, d, M_OR_FT(3, 10)); } break; case MND: if (QString::compare(qPrintable(vString), "*") == 0) { - cyl->bestmix_he = true; + cyl.bestmix_he = true; // Calculate fO2 for max. depth - cyl->gasmix.he = best_he(d->maxdepth, d, prefs.o2narcotic, cyl->gasmix.o2); + cyl.gasmix.he = best_he(d->maxdepth, d, prefs.o2narcotic, cyl.gasmix.o2); } else { - cyl->bestmix_he = false; + cyl.bestmix_he = false; // Calculate fHe for input depth - cyl->gasmix.he = best_he(string_to_depth(qPrintable(vString)), d, prefs.o2narcotic, cyl->gasmix.o2); + cyl.gasmix.he = best_he(string_to_depth(qPrintable(vString)), d, prefs.o2narcotic, cyl.gasmix.o2); } break; case USE: { int use = vString.toInt(); if (use > NUM_GAS_USE - 1 || use < 0) use = 0; - cyl->cylinder_use = (enum cylinderuse)use; + cyl.cylinder_use = (enum cylinderuse)use; } break; } - dataChanged(index, index); + + if (in_planner()) { + // In the planner - simply overwrite the cylinder in the dive with the modified cylinder. + // We have only made a shallow copy, therefore copy the new cylinder first. + cylinder_t copy = clone_cylinder(cyl); + std::swap(copy, d->cylinders.cylinders[row]); + free_cylinder(copy); + dataChanged(index, index); + } else { +#ifndef SUBSURFACE_MOBILE + // On the EquipmentTab - place an editCylinder command. + Command::editCylinder(index.row(), cyl, false); +#endif + } return true; } From 24a7dbde16ce5f024f6137a2318e54815a495a94 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 1 Mar 2020 16:12:13 +0100 Subject: [PATCH 30/78] CylindersModel: use flag to decide whether we are in planner On desktop, we have two CylindersModel concurrently: One in the planner and one on the equipment-tab. They act differently, because the former modifies displayed_dive directly, the latter issues undo commands. To differentiate, we used the in_planner() function. However, that appears extremely brittle, especially when combined with undo-commands. Therefore when generating the model, pass in a parameter that says whether this is for the planner or the equipment tab and use that flag to decide how to act. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 22 ++++++++++++---------- qt-models/cylindermodel.h | 3 ++- qt-models/diveplannermodel.cpp | 1 + 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index c2bd0198a..8a79ff1e5 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -11,8 +11,9 @@ #include "core/subsurface-string.h" #include -CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), +CylindersModel::CylindersModel(bool planner, QObject *parent) : CleanerTableModel(parent), d(nullptr), + inPlanner(planner), tempRow(-1), tempCyl(empty_cylinder) { @@ -28,7 +29,7 @@ CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), QVariant CylindersModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::DisplayRole && orientation == Qt::Horizontal && in_planner() && section == WORKINGPRESS) + if (role == Qt::DisplayRole && orientation == Qt::Horizontal && inPlanner && section == WORKINGPRESS) return tr("Start press."); else return CleanerTableModel::headerData(section, orientation, role); @@ -249,8 +250,8 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const case Qt::DecorationRole: case Qt::SizeHintRole: if (index.column() == REMOVE) { - if ((in_planner() && DivePlannerPointsModel::instance()->tankInUse(index.row())) || - (!in_planner() && is_cylinder_prot(d, index.row()))) { + if ((inPlanner && DivePlannerPointsModel::instance()->tankInUse(index.row())) || + (!inPlanner && is_cylinder_prot(d, index.row()))) { return trashForbiddenIcon(); } return trashIcon(); @@ -259,8 +260,8 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const case Qt::ToolTipRole: switch (index.column()) { case REMOVE: - if ((in_planner() && DivePlannerPointsModel::instance()->tankInUse(index.row())) || - (!in_planner() && is_cylinder_prot(d, index.row()))) { + if ((inPlanner && DivePlannerPointsModel::instance()->tankInUse(index.row())) || + (!inPlanner && is_cylinder_prot(d, index.row()))) { return tr("This gas is in use. Only cylinders that are not used in the dive can be removed."); } return tr("Clicking here will remove this cylinder."); @@ -450,7 +451,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in break; } - if (in_planner()) { + if (inPlanner) { // In the planner - simply overwrite the cylinder in the dive with the modified cylinder. // We have only made a shallow copy, therefore copy the new cylinder first. cylinder_t copy = clone_cylinder(cyl); @@ -590,7 +591,7 @@ void CylindersModel::moveAtFirst(int cylid) mapping[cylid] = 0; std::iota(mapping.begin() + (cylid + 1), mapping.end(), cylid); cylinder_renumber(d, &mapping[0]); - if (in_planner()) + if (inPlanner) DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]); endMoveRows(); } @@ -705,7 +706,7 @@ void CylindersModel::commitTempCyl(int row) return; // Only submit a command if the type changed if (!same_string(cyl->type.description, tempCyl.type.description) || gettextFromC::tr(cyl->type.description) != QString(tempCyl.type.description)) { - if (in_planner()) + if (inPlanner) std::swap(*cyl, tempCyl); else Command::editCylinder(tempRow, tempCyl, false); @@ -715,7 +716,8 @@ void CylindersModel::commitTempCyl(int row) #endif } -CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent) +CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent), + source(false) // Currently, only the EquipmentTab uses the filtered model. { setSourceModel(&source); } diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index e4585b0ac..7b868b5b2 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -35,7 +35,7 @@ public: COMMIT_ROLE, // Save the temporary data to the dive. Must be set with Column == TYPE. REVERT_ROLE // Revert to original data from dive. Must be set with Column == TYPE. }; - explicit CylindersModel(QObject *parent = 0); + explicit CylindersModel(bool planner, QObject *parent = 0); // First argument: true if this model is used for the planner QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; @@ -61,6 +61,7 @@ slots: private: dive *d; + bool inPlanner; // Used if we temporarily change a line because the user is selecting a weight type int tempRow; cylinder_t tempCyl; diff --git a/qt-models/diveplannermodel.cpp b/qt-models/diveplannermodel.cpp index 4928900c4..09d08c79a 100644 --- a/qt-models/diveplannermodel.cpp +++ b/qt-models/diveplannermodel.cpp @@ -415,6 +415,7 @@ int DivePlannerPointsModel::rowCount(const QModelIndex&) const } DivePlannerPointsModel::DivePlannerPointsModel(QObject *parent) : QAbstractTableModel(parent), + cylinders(true), mode(NOTHING), recalc(false) { From fe926d35f421af4d671f53a794941e5f936bd14f Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 1 Mar 2020 17:16:08 +0100 Subject: [PATCH 31/78] undo: update profile on cylinder editing In the profile, catch cylinder-editing signals and redraw the profile if the currently displayed dive has changed. Signed-off-by: Berthold Stoeger --- profile-widget/profilewidget2.cpp | 9 +++++++++ profile-widget/profilewidget2.h | 1 + 2 files changed, 10 insertions(+) diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index a162f8782..1b74f9d88 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -29,6 +29,7 @@ #include "core/qthelper.h" #include "core/gettextfromc.h" #include "core/imagedownloader.h" +#include "core/subsurface-qt/divelistnotifier.h" #endif #include @@ -169,6 +170,7 @@ ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent), connect(DivePictureModel::instance(), &DivePictureModel::rowsInserted, this, &ProfileWidget2::plotPictures); connect(DivePictureModel::instance(), &DivePictureModel::picturesRemoved, this, &ProfileWidget2::removePictures); connect(DivePictureModel::instance(), &DivePictureModel::modelReset, this, &ProfileWidget2::plotPictures); + connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &ProfileWidget2::cylinderChanged); #endif // SUBSURFACE_MOBILE #if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE) @@ -2244,6 +2246,13 @@ void ProfileWidget2::removePictures(const QVector &fileUrls) calculatePictureYPositions(); } +void ProfileWidget2::cylinderChanged(dive *d) +{ + if (!d || d->id != displayed_dive.id) + return; // Cylinders of a differnt dive than the shown one changed. + replot(); +} + #endif void ProfileWidget2::dropEvent(QDropEvent *event) diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index 223a03c33..ebaa52696 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -129,6 +129,7 @@ slots: // Necessary to call from QAction's signals. void pointInserted(const QModelIndex &parent, int start, int end); void pointsRemoved(const QModelIndex &, int start, int end); void updateThumbnail(QString filename, QImage thumbnail, duration_t duration); + void cylinderChanged(dive *d); /* this is called for every move on the handlers. maybe we can speed up this a bit? */ void recreatePlannedDive(); From 3965ae2c89d30e424e52cb17cd6ecaf9e46ec0f0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 10:50:24 +0100 Subject: [PATCH 32/78] desktop: move undo-disabling from enterEditState to disableShortcuts When entering the edit state, we don't want the user to be able to undo/redo lest things become inconsistent. Since the only way to enter edit state is to edit the profile, we can simply use the disableShortcuts() function that is used by the profile when it goes into edit state. This has one desirable side-effect: Undo is now also disabled in the planner. Undo during planning likewise can lead to inconsistent state. Signed-off-by: Berthold Stoeger --- desktop-widgets/mainwindow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index e1d87b7e6..bbeb61f70 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -710,6 +710,8 @@ void MainWindow::on_actionPrint_triggered() void MainWindow::disableShortcuts(bool disablePaste) { + undoAction->setEnabled(false); + redoAction->setEnabled(false); ui.actionPreviousDC->setShortcut(QKeySequence()); ui.actionNextDC->setShortcut(QKeySequence()); ui.copy->setShortcut(QKeySequence()); @@ -719,6 +721,8 @@ void MainWindow::disableShortcuts(bool disablePaste) void MainWindow::enableShortcuts() { + undoAction->setEnabled(true); + redoAction->setEnabled(true); ui.actionPreviousDC->setShortcut(Qt::Key_Left); ui.actionNextDC->setShortcut(Qt::Key_Right); ui.copy->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C)); @@ -1090,8 +1094,6 @@ void MainWindow::on_actionViewAll_triggered() void MainWindow::enterEditState() { - undoAction->setEnabled(false); - redoAction->setEnabled(false); stateBeforeEdit = state; if (state == VIEWALL || state == INFO_MAXIMIZED) return; @@ -1115,8 +1117,6 @@ void MainWindow::enterEditState() void MainWindow::exitEditState() { - undoAction->setEnabled(true); - redoAction->setEnabled(true); if (stateBeforeEdit == state) return; enterState(stateBeforeEdit); From accf1fcc8f6ad84a662aeba71d582abedbcedfd8 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 10:57:18 +0100 Subject: [PATCH 33/78] desktop: remove EDIT mode The only way to enter edit mode is to edit the profile. However, that means that the profile is already visible, so there is no need to change the mode. Simply remove the EDIT mode. Signed-off-by: Berthold Stoeger --- desktop-widgets/mainwindow.cpp | 32 ------------------------- desktop-widgets/mainwindow.h | 3 --- desktop-widgets/tab-widgets/maintab.cpp | 3 --- 3 files changed, 38 deletions(-) diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index bbeb61f70..7796ddf04 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -1092,36 +1092,6 @@ void MainWindow::on_actionViewAll_triggered() ui.bottomSplitter->setCollapsible(1,false); } -void MainWindow::enterEditState() -{ - stateBeforeEdit = state; - if (state == VIEWALL || state == INFO_MAXIMIZED) - return; - toggleCollapsible(true); - beginChangeState(EDIT); - ui.topSplitter->setSizes({ EXPANDED, EXPANDED }); - ui.mainSplitter->setSizes({ EXPANDED, COLLAPSED }); - int appW = qApp->desktop()->size().width(); - QList infoProfileSizes { round_int(appW * 0.3), round_int(appW * 0.7) }; - - QSettings settings; - settings.beginGroup("MainWindow"); - if (settings.value("mainSplitter").isValid()) { - ui.topSplitter->restoreState(settings.value("topSplitter").toByteArray()); - if (ui.topSplitter->sizes().first() == 0 || ui.topSplitter->sizes().last() == 0) - ui.topSplitter->setSizes(infoProfileSizes); - } else { - ui.topSplitter->setSizes(infoProfileSizes); - } -} - -void MainWindow::exitEditState() -{ - if (stateBeforeEdit == state) - return; - enterState(stateBeforeEdit); -} - void MainWindow::enterState(CurrentState newState) { state = newState; @@ -1141,8 +1111,6 @@ void MainWindow::enterState(CurrentState newState) case PROFILE_MAXIMIZED: on_actionViewProfile_triggered(); break; - case EDIT: - break; } } diff --git a/desktop-widgets/mainwindow.h b/desktop-widgets/mainwindow.h index 247bddaf3..1fb20a7ce 100644 --- a/desktop-widgets/mainwindow.h +++ b/desktop-widgets/mainwindow.h @@ -58,7 +58,6 @@ public: INFO_MAXIMIZED, PROFILE_MAXIMIZED, LIST_MAXIMIZED, - EDIT, }; MainWindow(); @@ -81,8 +80,6 @@ public: NotificationWidget *getNotificationWidget(); void enableDisableCloudActions(); void enableDisableOtherDCsActions(); - void enterEditState(); - void exitEditState(); void editDiveSite(dive_site *ds); std::unique_ptr mainTab; diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 311887169..14db8ae28 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -214,7 +214,6 @@ void MainTab::enableEdition(EditMode newEditMode) ui.editDiveSiteButton->setEnabled(false); MainWindow::instance()->diveList->setEnabled(false); MainWindow::instance()->setEnabledToolbar(false); - MainWindow::instance()->enterEditState(); ui.dateEdit->setEnabled(true); displayMessage(tr("This dive is being edited.")); @@ -521,7 +520,6 @@ void MainTab::acceptChanges() DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING); MainWindow::instance()->diveList->verticalScrollBar()->setSliderPosition(scrolledBy); MainWindow::instance()->diveList->setFocus(); - MainWindow::instance()->exitEditState(); MainWindow::instance()->setEnabledToolbar(true); ui.editDiveSiteButton->setEnabled(!ui.location->text().isEmpty()); editMode = NONE; @@ -552,7 +550,6 @@ void MainTab::rejectChanges() // show the profile and dive info MainWindow::instance()->graphics->replot(); MainWindow::instance()->setEnabledToolbar(true); - MainWindow::instance()->exitEditState(); ui.editDiveSiteButton->setEnabled(!ui.location->text().isEmpty()); } From 13d339451ed9aa13588a452e19bdd59134107155 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 16:03:32 +0100 Subject: [PATCH 34/78] cleanup: remove parameter to MainTab::enableEdition All remaining callers were passing MANUALLY_ADDED_DIVE as a new mode, so we may just as well remove the parameter and thus simplify the logic. Signed-off-by: Berthold Stoeger --- desktop-widgets/mainwindow.cpp | 4 ++-- desktop-widgets/tab-widgets/maintab.cpp | 6 +++--- desktop-widgets/tab-widgets/maintab.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 7796ddf04..1c122a4fd 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -1767,13 +1767,13 @@ void MainWindow::editCurrentDive() graphics->setAddState(); setApplicationState(ApplicationState::EditDive); DivePlannerPointsModel::instance()->loadFromDive(d); - mainTab->enableEdition(MainTab::MANUALLY_ADDED_DIVE); + mainTab->enableEdition(); } else if (defaultDC == "planned dive") { disableShortcuts(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); setApplicationState(ApplicationState::EditPlannedDive); DivePlannerPointsModel::instance()->loadFromDive(d); - mainTab->enableEdition(MainTab::MANUALLY_ADDED_DIVE); + mainTab->enableEdition(); } } diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 14db8ae28..eaf19b7db 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -206,9 +206,9 @@ void MainTab::displayMessage(QString str) ui.diveNotesMessage->animatedShow(); } -void MainTab::enableEdition(EditMode newEditMode) +void MainTab::enableEdition() { - if (((newEditMode == DIVE || newEditMode == NONE) && current_dive == NULL) || editMode != NONE) + if (current_dive == NULL || editMode != NONE) return; ui.editDiveSiteButton->setEnabled(false); @@ -218,7 +218,7 @@ void MainTab::enableEdition(EditMode newEditMode) ui.dateEdit->setEnabled(true); displayMessage(tr("This dive is being edited.")); - editMode = newEditMode != NONE ? newEditMode : DIVE; + editMode = MANUALLY_ADDED_DIVE; } // This function gets called if a field gets updated by an undo command. diff --git a/desktop-widgets/tab-widgets/maintab.h b/desktop-widgets/tab-widgets/maintab.h index 6411f6b3a..2524ed2a7 100644 --- a/desktop-widgets/tab-widgets/maintab.h +++ b/desktop-widgets/tab-widgets/maintab.h @@ -72,7 +72,7 @@ slots: void closeMessage(); void closeWarning(); void displayMessage(QString str); - void enableEdition(EditMode newEditMode = NONE); + void enableEdition(); void escDetected(void); private: Ui::MainTab ui; From dcc1d3ed6389b983982063b3829633e2e8d180b6 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 16:05:58 +0100 Subject: [PATCH 35/78] cleanup: remove DIVE EditMode in MainTab That mode is not used anymore, since only the editing of profiles of manually added dives enters editing mode. For that case we have the MANUALLY_ADDED_DIVE edit mode. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.h | 1 - 1 file changed, 1 deletion(-) diff --git a/desktop-widgets/tab-widgets/maintab.h b/desktop-widgets/tab-widgets/maintab.h index 2524ed2a7..d6a5af593 100644 --- a/desktop-widgets/tab-widgets/maintab.h +++ b/desktop-widgets/tab-widgets/maintab.h @@ -28,7 +28,6 @@ class MainTab : public QTabWidget { public: enum EditMode { NONE, - DIVE, MANUALLY_ADDED_DIVE, IGNORE_MODE }; From dee7fd9f308caae16045038b67dedd604a4d0ac2 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 21:32:12 +0100 Subject: [PATCH 36/78] cleanup: use SAMPLE_EVENT_BOOKMARK in add_event() calls In two cases we were passing the magic value 8 instead of the symbolic SAMPLE_EVENT_BOOKMARK. Use the symbolic version instead. Signed-off-by: Berthold Stoeger --- core/planner.c | 2 +- profile-widget/profilewidget2.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/planner.c b/core/planner.c index 204958a7e..a5f0d36bc 100644 --- a/core/planner.c +++ b/core/planner.c @@ -284,7 +284,7 @@ static void create_dive_from_plan(struct diveplan *diveplan, struct dive *dive, } if (dp->divemode != type) { type = dp->divemode; - add_event(dc, lasttime, 8, 0, type, "modechange"); + add_event(dc, lasttime, SAMPLE_EVENT_BOOKMARK, 0, type, "modechange"); } /* Create sample */ diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 1b74f9d88..43ebf0777 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1653,7 +1653,7 @@ void ProfileWidget2::addDivemodeSwitch() QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); for (i = 0; i < NUM_DIVEMODE; i++) if (gettextFromC::tr(divemode_text_ui[i]) == action->text()) - add_event(current_dc, lrint(timeAxis->valueAt(scenePos)), 8, 0, i, + add_event(current_dc, lrint(timeAxis->valueAt(scenePos)), SAMPLE_EVENT_BOOKMARK, 0, i, QT_TRANSLATE_NOOP("gettextFromC", "modechange")); invalidate_dive_cache(current_dive); mark_divelist_changed(true); From c4c3e62ab0b3abed940ca16009b47b3dab61f068 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 21:48:53 +0100 Subject: [PATCH 37/78] profile: use lambda for addDivemodeSwitch calls The data was transported via the action in a most complicated way: The text was backtranslated. Simply use a lambda - perhaps hard to read, but much simpler to follow and less brittle. Signed-off-by: Berthold Stoeger --- profile-widget/profilewidget2.cpp | 19 +++++-------------- profile-widget/profilewidget2.h | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 43ebf0777..ebc760cac 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1472,22 +1472,19 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) if (divemode != OC) { QAction *action = new QAction(&m); action->setText(gettextFromC::tr(divemode_text_ui[OC])); - connect(action, SIGNAL(triggered(bool)), this, SLOT(addDivemodeSwitch())); - action->setData(event->globalPos()); + connect(action, &QAction::triggered, [this, seconds](){ addDivemodeSwitch(seconds, OC); }); changeMode->addAction(action); } if (divemode != CCR) { QAction *action = new QAction(&m); action->setText(gettextFromC::tr(divemode_text_ui[CCR])); - connect(action, SIGNAL(triggered(bool)), this, SLOT(addDivemodeSwitch())); - action->setData(event->globalPos()); + connect(action, &QAction::triggered, [this, seconds](){ addDivemodeSwitch(seconds, CCR); }); changeMode->addAction(action); } if (divemode != PSCR) { QAction *action = new QAction(&m); action->setText(gettextFromC::tr(divemode_text_ui[PSCR])); - connect(action, SIGNAL(triggered(bool)), this, SLOT(addDivemodeSwitch())); - action->setData(event->globalPos()); + connect(action, &QAction::triggered, [this, seconds](){ addDivemodeSwitch(seconds, PSCR); }); changeMode->addAction(action); } @@ -1646,15 +1643,9 @@ void ProfileWidget2::addBookmark() replot(); } -void ProfileWidget2::addDivemodeSwitch() +void ProfileWidget2::addDivemodeSwitch(int seconds, int divemode) { - int i; - QAction *action = qobject_cast(sender()); - QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); - for (i = 0; i < NUM_DIVEMODE; i++) - if (gettextFromC::tr(divemode_text_ui[i]) == action->text()) - add_event(current_dc, lrint(timeAxis->valueAt(scenePos)), SAMPLE_EVENT_BOOKMARK, 0, i, - QT_TRANSLATE_NOOP("gettextFromC", "modechange")); + add_event(current_dc, seconds, SAMPLE_EVENT_BOOKMARK, 0, divemode, QT_TRANSLATE_NOOP("gettextFromC", "modechange")); invalidate_dive_cache(current_dive); mark_divelist_changed(true); replot(); diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index ebaa52696..4d47cb622 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -118,7 +118,6 @@ slots: // Necessary to call from QAction's signals. void addSetpointChange(); void splitDive(); void addBookmark(); - void addDivemodeSwitch(); void hideEvents(); void unhideEvents(); void removeEvent(); @@ -175,6 +174,7 @@ private: const double *thresholdSettingsMin, const double *thresholdSettingsMax); void clearPictures(); void plotPicturesInternal(const struct dive *d, bool synchronous); + void addDivemodeSwitch(int seconds, int divemode); private: DivePlotDataModel *dataModel; int zoomLevel; From c2d98b378b06420600af479dc561bf5f55126ebb Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 21:58:09 +0100 Subject: [PATCH 38/78] cleanup: don't set unnecessary action userdata for unhideEvents The unhideEvents context menu action was fed with the click-position. However, that was not used. Therefore, remove it. Signed-off-by: Berthold Stoeger --- profile-widget/profilewidget2.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index ebc760cac..53baa917c 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1559,10 +1559,8 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) break; } } - if (some_hidden) { - action = m.addAction(tr("Unhide all events"), this, &ProfileWidget2::unhideEvents); - action->setData(event->globalPos()); - } + if (some_hidden) + m.addAction(tr("Unhide all events"), this, &ProfileWidget2::unhideEvents); m.exec(event->globalPos()); } From 83d10ce89a1544044ea907e39f498183adb554f2 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 22:02:33 +0100 Subject: [PATCH 39/78] cleanup: use lambda to transport event-time to context menu actions This is not such a big gain as for addDivemodeSwitch(), but still simpler. Therefore, let's do it for consistency. Signed-off-by: Berthold Stoeger --- profile-widget/profilewidget2.cpp | 32 ++++++++++--------------------- profile-widget/profilewidget2.h | 6 +++--- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 53baa917c..8da3de72c 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1457,15 +1457,11 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) gasChange->addAction(action); } } - QAction *setpointAction = m.addAction(tr("Add setpoint change"), this, &ProfileWidget2::addSetpointChange); - setpointAction->setData(event->globalPos()); - QAction *action = m.addAction(tr("Add bookmark"), this, &ProfileWidget2::addBookmark); - action->setData(event->globalPos()); - QAction *splitAction = m.addAction(tr("Split dive into two"), this, &ProfileWidget2::splitDive); - splitAction->setData(event->globalPos()); + m.addAction(tr("Add setpoint change"), [this, seconds]() { ProfileWidget2::addSetpointChange(seconds); }); + m.addAction(tr("Add bookmark"), [this, seconds]() { addBookmark(seconds); }); + m.addAction(tr("Split dive into two"), [this, seconds]() { splitDive(seconds); }); const struct event *ev = NULL; enum divemode_t divemode = UNDEF_COMP_TYPE; - QString gas = action->text(); get_current_divemode(current_dc, seconds, &ev, &divemode); QMenu *changeMode = m.addMenu(tr("Change divemode")); @@ -1492,7 +1488,7 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) m.addAction(tr("Edit the profile"), this, SIGNAL(editCurrentDive())); if (DiveEventItem *item = dynamic_cast(sceneItem)) { - action = new QAction(&m); + QAction *action = new QAction(&m); action->setText(tr("Remove event")); action->setData(QVariant::fromValue(item)); // so we know what to remove. connect(action, SIGNAL(triggered(bool)), this, SLOT(removeEvent())); @@ -1631,11 +1627,9 @@ void ProfileWidget2::removeEvent() } } -void ProfileWidget2::addBookmark() +void ProfileWidget2::addBookmark(int seconds) { - QAction *action = qobject_cast(sender()); - QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); - add_event(current_dc, lrint(timeAxis->valueAt(scenePos)), SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark"); + add_event(current_dc, seconds, SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark"); invalidate_dive_cache(current_dive); mark_divelist_changed(true); replot(); @@ -1649,26 +1643,20 @@ void ProfileWidget2::addDivemodeSwitch(int seconds, int divemode) replot(); } -void ProfileWidget2::addSetpointChange() +void ProfileWidget2::addSetpointChange(int seconds) { - QAction *action = qobject_cast(sender()); - QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); - SetpointDialog::instance()->setpointData(current_dc, lrint(timeAxis->valueAt(scenePos))); + SetpointDialog::instance()->setpointData(current_dc, seconds); SetpointDialog::instance()->show(); } -void ProfileWidget2::splitDive() +void ProfileWidget2::splitDive(int seconds) { #ifndef SUBSURFACE_MOBILE // Make sure that this is an actual dive and we're not in add mode dive *d = get_dive_by_uniq_id(displayed_dive.id); if (!d) return; - QAction *action = qobject_cast(sender()); - QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); - duration_t time; - time.seconds = lrint(timeAxis->valueAt(scenePos)); - Command::splitDives(d, time); + Command::splitDives(d, duration_t{ seconds }); #endif } diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index 4d47cb622..342032567 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -115,9 +115,6 @@ slots: // Necessary to call from QAction's signals. void removePictures(const QVector &fileUrls); void setPlanState(); void setAddState(); - void addSetpointChange(); - void splitDive(); - void addBookmark(); void hideEvents(); void unhideEvents(); void removeEvent(); @@ -175,6 +172,9 @@ private: void clearPictures(); void plotPicturesInternal(const struct dive *d, bool synchronous); void addDivemodeSwitch(int seconds, int divemode); + void addBookmark(int seconds); + void splitDive(int seconds); + void addSetpointChange(int seconds); private: DivePlotDataModel *dataModel; int zoomLevel; From 76a41f45c7e69375f13b3942523e0f8753b1f9b7 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 22:23:40 +0100 Subject: [PATCH 40/78] cleanup: use lambdas to transport DiveEventItem to actions The removeEvent(), hideEvents() and editName() actions need the DiveEventItem they are applied to. This was transported via QAction's user-data, which means casting to void and back. By using lambdas instead, this can be made perfectly type-safe: First we are 100% sure that we have a DiveEventItem because we check the result of a dynamic_cast<>. Then we can pass it to the even using its proper type. Signed-off-by: Berthold Stoeger --- profile-widget/profilewidget2.cpp | 21 ++++++--------------- profile-widget/profilewidget2.h | 6 +++--- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 8da3de72c..53a9e3e1f 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1490,20 +1490,17 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) if (DiveEventItem *item = dynamic_cast(sceneItem)) { QAction *action = new QAction(&m); action->setText(tr("Remove event")); - action->setData(QVariant::fromValue(item)); // so we know what to remove. - connect(action, SIGNAL(triggered(bool)), this, SLOT(removeEvent())); + connect(action, &QAction::triggered, [this,item] { removeEvent(item); }); m.addAction(action); action = new QAction(&m); action->setText(tr("Hide similar events")); - action->setData(QVariant::fromValue(item)); - connect(action, SIGNAL(triggered(bool)), this, SLOT(hideEvents())); + connect(action, &QAction::triggered, [this, item] { hideEvents(item); }); m.addAction(action); struct event *dcEvent = item->getEvent(); if (dcEvent->type == SAMPLE_EVENT_BOOKMARK) { action = new QAction(&m); action->setText(tr("Edit name")); - action->setData(QVariant::fromValue(item)); - connect(action, SIGNAL(triggered(bool)), this, SLOT(editName())); + connect(action, &QAction::triggered, [this, item] { editName(item); }); m.addAction(action); } #if 0 // TODO::: FINISH OR DISABLE @@ -1575,10 +1572,8 @@ void ProfileWidget2::makeFirstDC() Command::moveDiveComputerToFront(current_dive, dc_number); } -void ProfileWidget2::hideEvents() +void ProfileWidget2::hideEvents(DiveEventItem *item) { - QAction *action = qobject_cast(sender()); - DiveEventItem *item = static_cast(action->data().value()); struct event *event = item->getEvent(); if (QMessageBox::question(this, @@ -1610,10 +1605,8 @@ void ProfileWidget2::unhideEvents() item->show(); } -void ProfileWidget2::removeEvent() +void ProfileWidget2::removeEvent(DiveEventItem *item) { - QAction *action = qobject_cast(sender()); - DiveEventItem *item = static_cast(action->data().value()); struct event *event = item->getEvent(); if (QMessageBox::question(this, TITLE_OR_TEXT( @@ -1730,10 +1723,8 @@ double ProfileWidget2::getFontPrintScale() } #ifndef SUBSURFACE_MOBILE -void ProfileWidget2::editName() +void ProfileWidget2::editName(DiveEventItem *item) { - QAction *action = qobject_cast(sender()); - DiveEventItem *item = static_cast(action->data().value()); struct event *event = item->getEvent(); bool ok; QString newName = QInputDialog::getText(this, tr("Edit name of bookmark"), diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index 342032567..bc049781d 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -115,10 +115,7 @@ slots: // Necessary to call from QAction's signals. void removePictures(const QVector &fileUrls); void setPlanState(); void setAddState(); - void hideEvents(); void unhideEvents(); - void removeEvent(); - void editName(); void makeFirstDC(); void deleteCurrentDC(); void splitCurrentDC(); @@ -175,6 +172,9 @@ private: void addBookmark(int seconds); void splitDive(int seconds); void addSetpointChange(int seconds); + void removeEvent(DiveEventItem *item); + void hideEvents(DiveEventItem *item); + void editName(DiveEventItem *item); private: DivePlotDataModel *dataModel; int zoomLevel; From 70a93c130a44125b02d518f37f638356295e1781 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 22:34:39 +0100 Subject: [PATCH 41/78] cleanup: use QMenu::addAction() convenience overload Since we removed the setData() calls of the QActions in ProfileWidget2::contextMenuEvent(), we don't have to manually generate the QActions. We can simply use the convenience overload of addAction() that takes a string and a functional. Signed-off-by: Berthold Stoeger --- profile-widget/profilewidget2.cpp | 54 +++++++++---------------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 53a9e3e1f..566c97732 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1450,12 +1450,9 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) if (current_dive && current_dive->cylinders.nr > 1) { // if we have more than one gas, offer to switch to another one QMenu *gasChange = m.addMenu(tr("Add gas change")); - for (int i = 0; i < current_dive->cylinders.nr; i++) { - QAction *action = new QAction(&m); - action->setText(QString(current_dive->cylinders.cylinders[i].type.description) + tr(" (cyl. %1)").arg(i + 1)); - connect(action, &QAction::triggered, [this, i, seconds] { changeGas(i, seconds); } ); - gasChange->addAction(action); - } + for (int i = 0; i < current_dive->cylinders.nr; i++) + gasChange->addAction(QString(current_dive->cylinders.cylinders[i].type.description) + tr(" (cyl. %1)").arg(i + 1), + [this, i, seconds] { changeGas(i, seconds); }); } m.addAction(tr("Add setpoint change"), [this, seconds]() { ProfileWidget2::addSetpointChange(seconds); }); m.addAction(tr("Add bookmark"), [this, seconds]() { addBookmark(seconds); }); @@ -1465,44 +1462,25 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) get_current_divemode(current_dc, seconds, &ev, &divemode); QMenu *changeMode = m.addMenu(tr("Change divemode")); - if (divemode != OC) { - QAction *action = new QAction(&m); - action->setText(gettextFromC::tr(divemode_text_ui[OC])); - connect(action, &QAction::triggered, [this, seconds](){ addDivemodeSwitch(seconds, OC); }); - changeMode->addAction(action); - } - if (divemode != CCR) { - QAction *action = new QAction(&m); - action->setText(gettextFromC::tr(divemode_text_ui[CCR])); - connect(action, &QAction::triggered, [this, seconds](){ addDivemodeSwitch(seconds, CCR); }); - changeMode->addAction(action); - } - if (divemode != PSCR) { - QAction *action = new QAction(&m); - action->setText(gettextFromC::tr(divemode_text_ui[PSCR])); - connect(action, &QAction::triggered, [this, seconds](){ addDivemodeSwitch(seconds, PSCR); }); - changeMode->addAction(action); - } + if (divemode != OC) + changeMode->addAction(gettextFromC::tr(divemode_text_ui[OC]), + [this, seconds](){ addDivemodeSwitch(seconds, OC); }); + if (divemode != CCR) + changeMode->addAction(gettextFromC::tr(divemode_text_ui[CCR]), + [this, seconds](){ addDivemodeSwitch(seconds, CCR); }); + if (divemode != PSCR) + changeMode->addAction(gettextFromC::tr(divemode_text_ui[PSCR]), + [this, seconds](){ addDivemodeSwitch(seconds, PSCR); }); if (same_string(current_dc->model, "manually added dive")) m.addAction(tr("Edit the profile"), this, SIGNAL(editCurrentDive())); if (DiveEventItem *item = dynamic_cast(sceneItem)) { - QAction *action = new QAction(&m); - action->setText(tr("Remove event")); - connect(action, &QAction::triggered, [this,item] { removeEvent(item); }); - m.addAction(action); - action = new QAction(&m); - action->setText(tr("Hide similar events")); - connect(action, &QAction::triggered, [this, item] { hideEvents(item); }); - m.addAction(action); + m.addAction(tr("Remove event"), [this,item] { removeEvent(item); }); + m.addAction(tr("Hide similar events"), [this, item] { hideEvents(item); }); struct event *dcEvent = item->getEvent(); - if (dcEvent->type == SAMPLE_EVENT_BOOKMARK) { - action = new QAction(&m); - action->setText(tr("Edit name")); - connect(action, &QAction::triggered, [this, item] { editName(item); }); - m.addAction(action); - } + if (dcEvent->type == SAMPLE_EVENT_BOOKMARK) + m.addAction(tr("Edit name"), [this, item] { editName(item); }); #if 0 // TODO::: FINISH OR DISABLE QPointF scenePos = mapToScene(event->pos()); int idx = getEntryFromPos(scenePos); From e7b5955be08369ebb5245a53186600a9ebf1a0ed Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 2 Mar 2020 22:48:12 +0100 Subject: [PATCH 42/78] cleanup: demote slots in ProfileWidget2 to private functions Since we call these with lambdas, they don't need to be slots anymore. Signed-off-by: Berthold Stoeger --- profile-widget/profilewidget2.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index bc049781d..0c1db3f62 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -115,10 +115,6 @@ slots: // Necessary to call from QAction's signals. void removePictures(const QVector &fileUrls); void setPlanState(); void setAddState(); - void unhideEvents(); - void makeFirstDC(); - void deleteCurrentDC(); - void splitCurrentDC(); void pointInserted(const QModelIndex &parent, int start, int end); void pointsRemoved(const QModelIndex &, int start, int end); void updateThumbnail(QString filename, QImage thumbnail, duration_t duration); @@ -175,6 +171,10 @@ private: void removeEvent(DiveEventItem *item); void hideEvents(DiveEventItem *item); void editName(DiveEventItem *item); + void unhideEvents(); + void makeFirstDC(); + void deleteCurrentDC(); + void splitCurrentDC(); private: DivePlotDataModel *dataModel; int zoomLevel; From 2417a54675011885c699dbe8a77a427effcb26c5 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 20:03:56 +0100 Subject: [PATCH 43/78] core: split add_event() in two parts add_event() creates and adds an event from the given parameters. For undo, we want to do these separately, therefore split this function in two parts: create_event() and add_event_to_dc(). Keep the add_event() function for convenience. Moreover, keep the remember_event() call in there, so that undo-commands can call remember_event() once, not on every undo/redo action. Signed-off-by: Berthold Stoeger --- core/dive.c | 24 +++++++++++++++++++++--- core/dive.h | 2 ++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/core/dive.c b/core/dive.c index ffdf91ae3..25084a554 100644 --- a/core/dive.c +++ b/core/dive.c @@ -125,10 +125,10 @@ int event_is_gaschange(const struct event *ev) ev->type == SAMPLE_EVENT_GASCHANGE2; } -struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name) +struct event *create_event(unsigned int time, int type, int flags, int value, const char *name) { int gas_index = -1; - struct event *ev, **p; + struct event *ev; unsigned int size, len = strlen(name); size = sizeof(*ev) + len + 1; @@ -163,13 +163,31 @@ struct event *add_event(struct divecomputer *dc, unsigned int time, int type, in break; } + return ev; +} + +void add_event_to_dc(struct divecomputer *dc, struct event *ev) +{ + struct event **p; + p = &dc->events; /* insert in the sorted list of events */ - while (*p && (*p)->time.seconds <= time) + while (*p && (*p)->time.seconds <= ev->time.seconds) p = &(*p)->next; ev->next = *p; *p = ev; +} + +struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name) +{ + struct event *ev = create_event(time, type, flags, value, name); + + if (!ev) + return NULL; + + add_event_to_dc(dc, ev); + remember_event(name); return ev; } diff --git a/core/dive.h b/core/dive.h index 76eccb5e8..5bbce4b00 100644 --- a/core/dive.h +++ b/core/dive.h @@ -377,6 +377,8 @@ extern void copy_samples(const struct divecomputer *s, struct divecomputer *d); extern bool is_cylinder_used(const struct dive *dive, int idx); extern bool is_cylinder_prot(const struct dive *dive, int idx); extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx); +extern struct event *create_event(unsigned int time, int type, int flags, int value, const char *name); +extern void add_event_to_dc(struct divecomputer *dc, struct event *ev); extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); extern void remove_event(struct event *event); extern void update_event_name(struct dive *d, struct event *event, const char *name); From 3aa1bb5bfae9a7ebf8f5e6cdbf751afcafdb4baf Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 21:48:08 +0100 Subject: [PATCH 44/78] core: add remove_event_from_dc() function We have a remove_event() function that 1) frees the event 2) works on the current divecomputer 3) compares the events because the profile has copies of events However, for undo commands 1) we want to keep the event so that we can readd it later 2) we have to work on arbitrary divecomputers 3) we don't work with copies of events Therefore, create a new remove_event_from_dc() function that does all that. Moreover, make the event argument to remove_event() const to (slightly) point out the difference in the API. Signed-off-by: Berthold Stoeger --- core/dive.c | 16 +++++++++++++++- core/dive.h | 3 ++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/core/dive.c b/core/dive.c index 25084a554..3ec9b1a82 100644 --- a/core/dive.c +++ b/core/dive.c @@ -205,7 +205,21 @@ static int same_event(const struct event *a, const struct event *b) return !strcmp(a->name, b->name); } -void remove_event(struct event *event) +/* Remove given event from dive computer. Does *not* free the event. */ +void remove_event_from_dc(struct divecomputer *dc, struct event *event) +{ + for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) { + if (*ep == event) { + *ep = event->next; + event->next = NULL; // For good measure. + break; + } + } +} + +/* Remove an event from current dive computer that is identical to the passed in event. + * Frees the event. */ +void remove_event(const struct event *event) { struct event **ep = ¤t_dc->events; while (ep && !same_event(*ep, event)) diff --git a/core/dive.h b/core/dive.h index 5bbce4b00..1de68f4ff 100644 --- a/core/dive.h +++ b/core/dive.h @@ -380,7 +380,8 @@ extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int extern struct event *create_event(unsigned int time, int type, int flags, int value, const char *name); extern void add_event_to_dc(struct divecomputer *dc, struct event *ev); extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); -extern void remove_event(struct event *event); +extern void remove_event_from_dc(struct divecomputer *dc, struct event *event); +extern void remove_event(const struct event *event); extern void update_event_name(struct dive *d, struct event *event, const char *name); extern void add_extra_data(struct divecomputer *dc, const char *key, const char *value); extern void per_cylinder_mean_depth(const struct dive *dive, struct divecomputer *dc, int *mean, int *duration); From 30c7499a3c656784cc45825b4b1b2fca61081308 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 22:34:50 +0100 Subject: [PATCH 45/78] undo: implement addBookmark undo command Create a new translation unit for event-related undo commands. Create a base class of commands that add events and one subclass that adds a bookmark event. Use this command in the profile-widget. Signed-off-by: Berthold Stoeger --- commands/CMakeLists.txt | 2 ++ commands/command.cpp | 8 ++++++ commands/command.h | 4 +++ commands/command_base.h | 9 ++++--- commands/command_event.cpp | 43 ++++++++++++++++++++++++++++++ commands/command_event.h | 44 +++++++++++++++++++++++++++++++ profile-widget/profilewidget2.cpp | 5 +--- 7 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 commands/command_event.cpp create mode 100644 commands/command_event.h diff --git a/commands/CMakeLists.txt b/commands/CMakeLists.txt index 4b0c37dc6..ceb2740f0 100644 --- a/commands/CMakeLists.txt +++ b/commands/CMakeLists.txt @@ -14,6 +14,8 @@ set(SUBSURFACE_GENERIC_COMMANDS_SRCS command_edit.h command_edit_trip.cpp command_edit_trip.h + command_event.cpp + command_event.h ) add_library(subsurface_commands STATIC ${SUBSURFACE_GENERIC_COMMANDS_SRCS}) target_link_libraries(subsurface_commands ${QT_LIBRARIES}) diff --git a/commands/command.cpp b/commands/command.cpp index 5d71e6725..e9b4e0738 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -5,6 +5,7 @@ #include "command_divesite.h" #include "command_edit.h" #include "command_edit_trip.h" +#include "command_event.h" namespace Command { @@ -326,4 +327,11 @@ void editDive(dive *oldDive, dive *newDive, dive_site *createDs, dive_site *chan } #endif // SUBSURFACE_MOBILE +// Event commands + +void addEventBookmark(struct dive *d, int dcNr, int seconds) +{ + execute(new AddEventBookmark(d, dcNr, seconds)); +} + } // namespace Command diff --git a/commands/command.h b/commands/command.h index b8024c03d..88614b1e9 100644 --- a/commands/command.h +++ b/commands/command.h @@ -104,6 +104,10 @@ void editDive(dive *oldDive, dive *newDive, dive_site *createDs, dive_site *chan void editTripLocation(dive_trip *trip, const QString &s); void editTripNotes(dive_trip *trip, const QString &s); +// 6) Event commands + +void addEventBookmark(struct dive *d, int dcNr, int seconds); + } // namespace Command #endif // COMMAND_H diff --git a/commands/command_base.h b/commands/command_base.h index ffc6c4e91..be725d6a5 100644 --- a/commands/command_base.h +++ b/commands/command_base.h @@ -141,7 +141,7 @@ // We put everything in a namespace, so that we can shorten names without polluting the global namespace namespace Command { -// Classes used to automatically call free_dive()/free_trip for owning pointers that go out of scope. +// Classes used to automatically call the appropriate free_*() function for owning pointers that go out of scope. struct DiveDeleter { void operator()(dive *d) { free_dive(d); } }; @@ -151,11 +151,15 @@ struct TripDeleter { struct DiveSiteDeleter { void operator()(dive_site *ds) { free_dive_site(ds); } }; +struct EventDeleter { + void operator()(event *ev) { free(ev); } +}; -// Owning pointers to dive and dive_trip objects. +// Owning pointers to dive, dive_trip, dive_site and event objects. typedef std::unique_ptr OwningDivePtr; typedef std::unique_ptr OwningTripPtr; typedef std::unique_ptr OwningDiveSitePtr; +typedef std::unique_ptr OwningEventPtr; // This is the base class of all commands. // It defines the Qt-translation functions @@ -182,4 +186,3 @@ QString getListOfDives(QVector dives); } // namespace Command #endif // COMMAND_BASE_H - diff --git a/commands/command_event.cpp b/commands/command_event.cpp new file mode 100644 index 000000000..ea35402e4 --- /dev/null +++ b/commands/command_event.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "command_event.h" +#include "core/dive.h" +#include "core/subsurface-qt/divelistnotifier.h" +#include "core/libdivecomputer.h" + +namespace Command { + +AddEventBase::AddEventBase(struct dive *dIn, int dcNrIn, struct event *ev) : + d(dIn), dcNr(dcNrIn), eventToAdd(ev) +{ +} + +bool AddEventBase::workToBeDone() +{ + return true; +} + +void AddEventBase::redo() +{ + struct divecomputer *dc = get_dive_dc(d, dcNr); + eventToRemove = eventToAdd.get(); + add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend + invalidate_dive_cache(d); +} + +void AddEventBase::undo() +{ + struct divecomputer *dc = get_dive_dc(d, dcNr); + remove_event_from_dc(dc, eventToRemove); + eventToAdd.reset(eventToRemove); // take ownership of event + eventToRemove = nullptr; + invalidate_dive_cache(d); +} + +AddEventBookmark::AddEventBookmark(struct dive *d, int dcNr, int seconds) : + AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark")) +{ + setText(tr("Add bookmark")); +} + +} // namespace Command diff --git a/commands/command_event.h b/commands/command_event.h new file mode 100644 index 000000000..6e387d543 --- /dev/null +++ b/commands/command_event.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +// Note: this header file is used by the undo-machinery and should not be included elsewhere. + +#ifndef COMMAND_EVENT_H +#define COMMAND_EVENT_H + +#include "command_base.h" + + +// We put everything in a namespace, so that we can shorten names without polluting the global namespace +namespace Command { + +// Events are a strange thing: they contain there own description which means +// that on changing the description a new object must be allocated. Moreover, +// it means that these objects can't be collected in a table. +// Therefore, the undo commands work on events as they do with dives: using +// owning pointers. See comments in command_base.h + +class AddEventBase : public Base { +public: + AddEventBase(struct dive *d, int dcNr, struct event *ev); // Takes ownership of event! +private: + bool workToBeDone() override; + void undo() override; + void redo() override; + + // Note: we store dive and the divecomputer-number instead of a pointer to the divecomputer. + // Since one divecomputer is integrated into the dive structure, pointers to divecomputers + // are probably not stable. + struct dive *d; + int dcNr; + + OwningEventPtr eventToAdd; // for redo + event *eventToRemove; // for undo +}; + +class AddEventBookmark : public AddEventBase { +public: + AddEventBookmark(struct dive *d, int dcNr, int seconds); +}; + +} // namespace Command + +#endif // COMMAND_EVENT_H diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 566c97732..67962b3bb 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1600,10 +1600,7 @@ void ProfileWidget2::removeEvent(DiveEventItem *item) void ProfileWidget2::addBookmark(int seconds) { - add_event(current_dc, seconds, SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark"); - invalidate_dive_cache(current_dive); - mark_divelist_changed(true); - replot(); + Command::addEventBookmark(current_dive, dc_number, seconds); } void ProfileWidget2::addDivemodeSwitch(int seconds, int divemode) From 7018783f64f664af97032da4ba4d357c05b52aaf Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 22:42:51 +0100 Subject: [PATCH 46/78] undo: replot profile if event changed Add a DiveListNotifer::eventsChanged signal, which is emitted when the events changed. This is very coarse, at it doesn't differentiate between signal addition / editing / deletion. We might want to be finer in the future. Catch the signal in the profile-widget to replot the dive if this is the currently displayed dive. Reuse the cylindersChanged() slot, but rename it to the now more appropriate profileChanged(). Signed-off-by: Berthold Stoeger --- commands/command_event.cpp | 2 ++ core/subsurface-qt/divelistnotifier.h | 3 +++ profile-widget/profilewidget2.cpp | 5 +++-- profile-widget/profilewidget2.h | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/commands/command_event.cpp b/commands/command_event.cpp index ea35402e4..d945fbb02 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -23,6 +23,7 @@ void AddEventBase::redo() eventToRemove = eventToAdd.get(); add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend invalidate_dive_cache(d); + emit diveListNotifier.eventsChanged(d); } void AddEventBase::undo() @@ -32,6 +33,7 @@ void AddEventBase::undo() eventToAdd.reset(eventToRemove); // take ownership of event eventToRemove = nullptr; invalidate_dive_cache(d); + emit diveListNotifier.eventsChanged(d); } AddEventBookmark::AddEventBookmark(struct dive *d, int dcNr, int seconds) : diff --git a/core/subsurface-qt/divelistnotifier.h b/core/subsurface-qt/divelistnotifier.h index 2a72cdf49..63355fbcc 100644 --- a/core/subsurface-qt/divelistnotifier.h +++ b/core/subsurface-qt/divelistnotifier.h @@ -113,6 +113,9 @@ signals: void numShownChanged(); void filterReset(); + // Event-related signals. Currently, we're very blunt: only one signal for any changes to the events. + void eventsChanged(dive *d); + // This signal is emited every time a command is executed. // This is used to hide an old multi-dives-edited warning message. // This is necessary, so that the user can't click on the "undo" button and undo diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 67962b3bb..54881f71a 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -170,7 +170,8 @@ ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent), connect(DivePictureModel::instance(), &DivePictureModel::rowsInserted, this, &ProfileWidget2::plotPictures); connect(DivePictureModel::instance(), &DivePictureModel::picturesRemoved, this, &ProfileWidget2::removePictures); connect(DivePictureModel::instance(), &DivePictureModel::modelReset, this, &ProfileWidget2::plotPictures); - connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &ProfileWidget2::cylinderChanged); + connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &ProfileWidget2::profileChanged); + connect(&diveListNotifier, &DiveListNotifier::eventsChanged, this, &ProfileWidget2::profileChanged); #endif // SUBSURFACE_MOBILE #if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE) @@ -2189,7 +2190,7 @@ void ProfileWidget2::removePictures(const QVector &fileUrls) calculatePictureYPositions(); } -void ProfileWidget2::cylinderChanged(dive *d) +void ProfileWidget2::profileChanged(dive *d) { if (!d || d->id != displayed_dive.id) return; // Cylinders of a differnt dive than the shown one changed. diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index 0c1db3f62..4f60bf885 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -118,7 +118,7 @@ slots: // Necessary to call from QAction's signals. void pointInserted(const QModelIndex &parent, int start, int end); void pointsRemoved(const QModelIndex &, int start, int end); void updateThumbnail(QString filename, QImage thumbnail, duration_t duration); - void cylinderChanged(dive *d); + void profileChanged(dive *d); /* this is called for every move on the handlers. maybe we can speed up this a bit? */ void recreatePlannedDive(); From 33fb6461fbe7e389081e77853e63638a1a343623 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 22:54:54 +0100 Subject: [PATCH 47/78] undo: add undo command for dive-mode switch This basically copies the bookmark code, with the addition that the dive mode is recorded in the text of the undo command. Signed-off-by: Berthold Stoeger --- commands/command.cpp | 5 +++++ commands/command.h | 1 + commands/command_event.cpp | 7 +++++++ commands/command_event.h | 5 +++++ profile-widget/profilewidget2.cpp | 5 +---- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/commands/command.cpp b/commands/command.cpp index e9b4e0738..4f92038b9 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -334,4 +334,9 @@ void addEventBookmark(struct dive *d, int dcNr, int seconds) execute(new AddEventBookmark(d, dcNr, seconds)); } +void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode) +{ + execute(new AddEventDivemodeSwitch(d, dcNr, seconds, divemode)); +} + } // namespace Command diff --git a/commands/command.h b/commands/command.h index 88614b1e9..1644063a7 100644 --- a/commands/command.h +++ b/commands/command.h @@ -107,6 +107,7 @@ void editTripNotes(dive_trip *trip, const QString &s); // 6) Event commands void addEventBookmark(struct dive *d, int dcNr, int seconds); +void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode); } // namespace Command diff --git a/commands/command_event.cpp b/commands/command_event.cpp index d945fbb02..ddef502bc 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -4,6 +4,7 @@ #include "core/dive.h" #include "core/subsurface-qt/divelistnotifier.h" #include "core/libdivecomputer.h" +#include "core/gettextfromc.h" namespace Command { @@ -42,4 +43,10 @@ AddEventBookmark::AddEventBookmark(struct dive *d, int dcNr, int seconds) : setText(tr("Add bookmark")); } +AddEventDivemodeSwitch::AddEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode) : + AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_BOOKMARK, 0, divemode, QT_TRANSLATE_NOOP("gettextFromC", "modechange"))) +{ + setText(tr("Add dive mode switch to %1").arg(gettextFromC::tr(divemode_text_ui[divemode]))); +} + } // namespace Command diff --git a/commands/command_event.h b/commands/command_event.h index 6e387d543..3130a128f 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -39,6 +39,11 @@ public: AddEventBookmark(struct dive *d, int dcNr, int seconds); }; +class AddEventDivemodeSwitch : public AddEventBase { +public: + AddEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode); +}; + } // namespace Command #endif // COMMAND_EVENT_H diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 54881f71a..76822c196 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1606,10 +1606,7 @@ void ProfileWidget2::addBookmark(int seconds) void ProfileWidget2::addDivemodeSwitch(int seconds, int divemode) { - add_event(current_dc, seconds, SAMPLE_EVENT_BOOKMARK, 0, divemode, QT_TRANSLATE_NOOP("gettextFromC", "modechange")); - invalidate_dive_cache(current_dive); - mark_divelist_changed(true); - replot(); + Command::addEventDivemodeSwitch(current_dive, dc_number, seconds, divemode); } void ProfileWidget2::addSetpointChange(int seconds) From 806cfcee212fa89ecfccad0f78225b83159d131e Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 23:09:25 +0100 Subject: [PATCH 48/78] cleanup: un-singletonify SetpointDialog We have too many global objects. There is no reason why this dialog should be a persistent global object. Signed-off-by: Berthold Stoeger --- desktop-widgets/simplewidgets.cpp | 18 +++--------------- desktop-widgets/simplewidgets.h | 4 +--- profile-widget/profilewidget2.cpp | 4 ++-- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index fb4d3750b..cbf37c398 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -174,18 +174,6 @@ RenumberDialog::RenumberDialog(QWidget *parent) : QDialog(parent), selectedOnly( connect(quit, SIGNAL(activated()), parent, SLOT(close())); } -SetpointDialog *SetpointDialog::instance() -{ - static SetpointDialog *self = new SetpointDialog(MainWindow::instance()); - return self; -} - -void SetpointDialog::setpointData(struct divecomputer *divecomputer, int second) -{ - dc = divecomputer; - time = second < 0 ? 0 : second; -} - void SetpointDialog::buttonClicked(QAbstractButton *button) { if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole && dc) { @@ -197,15 +185,15 @@ void SetpointDialog::buttonClicked(QAbstractButton *button) MainWindow::instance()->graphics->replot(); } -SetpointDialog::SetpointDialog(QWidget *parent) : QDialog(parent), - dc(0), time(0) +SetpointDialog::SetpointDialog(struct divecomputer *dcIn, int seconds) : QDialog(MainWindow::instance()), + dc(dcIn), time(seconds < 0 ? 0 : seconds) { ui.setupUi(this); connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonClicked(QAbstractButton *))); QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), this); connect(close, SIGNAL(activated()), this, SLOT(close())); QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this); - connect(quit, SIGNAL(activated()), parent, SLOT(close())); + connect(quit, SIGNAL(activated()), parent(), SLOT(close())); } ShiftTimesDialog *ShiftTimesDialog::instance() diff --git a/desktop-widgets/simplewidgets.h b/desktop-widgets/simplewidgets.h index 99cfc0fa2..e293f352c 100644 --- a/desktop-widgets/simplewidgets.h +++ b/desktop-widgets/simplewidgets.h @@ -65,14 +65,12 @@ private: class SetpointDialog : public QDialog { Q_OBJECT public: - static SetpointDialog *instance(); - void setpointData(struct divecomputer *divecomputer, int time); + SetpointDialog(struct divecomputer *divecomputer, int time); private slots: void buttonClicked(QAbstractButton *button); private: - explicit SetpointDialog(QWidget *parent); Ui::SetpointDialog ui; struct divecomputer *dc; int time; diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 76822c196..555305817 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1611,8 +1611,8 @@ void ProfileWidget2::addDivemodeSwitch(int seconds, int divemode) void ProfileWidget2::addSetpointChange(int seconds) { - SetpointDialog::instance()->setpointData(current_dc, seconds); - SetpointDialog::instance()->show(); + SetpointDialog dialog(current_dc, seconds); + dialog.exec(); } void ProfileWidget2::splitDive(int seconds) From adb53f9c18070e524a2635c5a1d983556f21d5ea Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 23:13:07 +0100 Subject: [PATCH 49/78] cleanup: use pointer-to-member version of connect in SetpointDialog While touching this dialog, might as well change away from the MOC version of the connect() statements. Signed-off-by: Berthold Stoeger --- desktop-widgets/simplewidgets.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index cbf37c398..4b85a8b50 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -189,11 +189,11 @@ SetpointDialog::SetpointDialog(struct divecomputer *dcIn, int seconds) : QDialog dc(dcIn), time(seconds < 0 ? 0 : seconds) { ui.setupUi(this); - connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonClicked(QAbstractButton *))); + connect(ui.buttonBox, &QDialogButtonBox::clicked, this, &SetpointDialog::buttonClicked); QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), this); - connect(close, SIGNAL(activated()), this, SLOT(close())); + connect(close, &QShortcut::activated, this, &QDialog::close); QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this); - connect(quit, SIGNAL(activated()), parent(), SLOT(close())); + connect(quit, &QShortcut::activated, MainWindow::instance(), &QWidget::close); } ShiftTimesDialog *ShiftTimesDialog::instance() From 9a4718b46f5fa74c7c67a92d0c09ab805f364e12 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 23:17:08 +0100 Subject: [PATCH 50/78] undo: switch SetpointDialog from divecomputer to dive + dc-number Since pointers to divecomputers may not be stable, the undo commands take a dive + a divecomputer number. Update the SetpointDialog accordingly. Signed-off-by: Berthold Stoeger --- desktop-widgets/simplewidgets.cpp | 8 ++++---- desktop-widgets/simplewidgets.h | 5 +++-- profile-widget/profilewidget2.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index 4b85a8b50..c31c93676 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -176,8 +176,8 @@ RenumberDialog::RenumberDialog(QWidget *parent) : QDialog(parent), selectedOnly( void SetpointDialog::buttonClicked(QAbstractButton *button) { - if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole && dc) { - add_event(dc, time, SAMPLE_EVENT_PO2, 0, (int)(1000.0 * ui.spinbox->value()), + if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + add_event(get_dive_dc(d, dcNr), time, SAMPLE_EVENT_PO2, 0, (int)(1000.0 * ui.spinbox->value()), QT_TRANSLATE_NOOP("gettextFromC", "SP change")); invalidate_dive_cache(current_dive); } @@ -185,8 +185,8 @@ void SetpointDialog::buttonClicked(QAbstractButton *button) MainWindow::instance()->graphics->replot(); } -SetpointDialog::SetpointDialog(struct divecomputer *dcIn, int seconds) : QDialog(MainWindow::instance()), - dc(dcIn), time(seconds < 0 ? 0 : seconds) +SetpointDialog::SetpointDialog(struct dive *dIn, int dcNrIn, int seconds) : QDialog(MainWindow::instance()), + d(dIn), dcNr(dcNrIn), time(seconds < 0 ? 0 : seconds) { ui.setupUi(this); connect(ui.buttonBox, &QDialogButtonBox::clicked, this, &SetpointDialog::buttonClicked); diff --git a/desktop-widgets/simplewidgets.h b/desktop-widgets/simplewidgets.h index e293f352c..421695815 100644 --- a/desktop-widgets/simplewidgets.h +++ b/desktop-widgets/simplewidgets.h @@ -65,14 +65,15 @@ private: class SetpointDialog : public QDialog { Q_OBJECT public: - SetpointDialog(struct divecomputer *divecomputer, int time); + SetpointDialog(struct dive *d, int dcNr, int time); private slots: void buttonClicked(QAbstractButton *button); private: Ui::SetpointDialog ui; - struct divecomputer *dc; + struct dive *d; + int dcNr; int time; }; diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 555305817..76b3c9afb 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1611,7 +1611,7 @@ void ProfileWidget2::addDivemodeSwitch(int seconds, int divemode) void ProfileWidget2::addSetpointChange(int seconds) { - SetpointDialog dialog(current_dc, seconds); + SetpointDialog dialog(current_dive, dc_number, seconds); dialog.exec(); } From 1971cfad547792ba961f804605ccb12780e17de0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 3 Mar 2020 23:31:46 +0100 Subject: [PATCH 51/78] undo: implement set point change undo command This is a simple copy of the other add-event commands. It could be made more friendly by stating the pO2 value in the text. Signed-off-by: Berthold Stoeger --- commands/command.cpp | 5 +++++ commands/command.h | 1 + commands/command_event.cpp | 6 ++++++ commands/command_event.h | 5 +++++ desktop-widgets/simplewidgets.cpp | 10 ++-------- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/commands/command.cpp b/commands/command.cpp index 4f92038b9..44eb4b8f0 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -339,4 +339,9 @@ void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode) execute(new AddEventDivemodeSwitch(d, dcNr, seconds, divemode)); } +void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2) +{ + execute(new AddEventSetpointChange(d, dcNr, seconds, pO2)); +} + } // namespace Command diff --git a/commands/command.h b/commands/command.h index 1644063a7..aa7c75ab3 100644 --- a/commands/command.h +++ b/commands/command.h @@ -108,6 +108,7 @@ void editTripNotes(dive_trip *trip, const QString &s); void addEventBookmark(struct dive *d, int dcNr, int seconds); void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode); +void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2); } // namespace Command diff --git a/commands/command_event.cpp b/commands/command_event.cpp index ddef502bc..a1e13f252 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -49,4 +49,10 @@ AddEventDivemodeSwitch::AddEventDivemodeSwitch(struct dive *d, int dcNr, int sec setText(tr("Add dive mode switch to %1").arg(gettextFromC::tr(divemode_text_ui[divemode]))); } +AddEventSetpointChange::AddEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2) : + AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_PO2, 0, pO2.mbar, QT_TRANSLATE_NOOP("gettextFromC", "SP change"))) +{ + setText(tr("Add set point change")); // TODO: format pO2 value in bar or psi. +} + } // namespace Command diff --git a/commands/command_event.h b/commands/command_event.h index 3130a128f..8cceaeb25 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -44,6 +44,11 @@ public: AddEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode); }; +class AddEventSetpointChange : public AddEventBase { +public: + AddEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2); +}; + } // namespace Command #endif // COMMAND_EVENT_H diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index c31c93676..423f4b68f 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -23,7 +23,6 @@ #include "commands/command.h" #include "core/metadata.h" #include "core/tag.h" -#include "core/divelist.h" // for mark_divelist_changed double MinMaxAvgWidget::average() const { @@ -176,13 +175,8 @@ RenumberDialog::RenumberDialog(QWidget *parent) : QDialog(parent), selectedOnly( void SetpointDialog::buttonClicked(QAbstractButton *button) { - if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { - add_event(get_dive_dc(d, dcNr), time, SAMPLE_EVENT_PO2, 0, (int)(1000.0 * ui.spinbox->value()), - QT_TRANSLATE_NOOP("gettextFromC", "SP change")); - invalidate_dive_cache(current_dive); - } - mark_divelist_changed(true); - MainWindow::instance()->graphics->replot(); + if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) + Command::addEventSetpointChange(d, dcNr, time, pressure_t { (int)(1000.0 * ui.spinbox->value()) }); } SetpointDialog::SetpointDialog(struct dive *dIn, int dcNrIn, int seconds) : QDialog(MainWindow::instance()), From f9fe6d759f33c36f1c0c0d20a591f6517fc9071f Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 17:35:02 +0100 Subject: [PATCH 52/78] undo: split out EventBase class All event-based commands will work on a dive computer and need to replot the profile, etc. Therefore, in analogy to the dive-list commands create a base class with two virtual functions undoit() and redoit() that must be defined in the derived classes that do the actual work. Signed-off-by: Berthold Stoeger --- commands/command_event.cpp | 31 +++++++++++++++++++++++-------- commands/command_event.h | 19 ++++++++++++++----- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/commands/command_event.cpp b/commands/command_event.cpp index a1e13f252..7ecd4f070 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -8,8 +8,27 @@ namespace Command { -AddEventBase::AddEventBase(struct dive *dIn, int dcNrIn, struct event *ev) : - d(dIn), dcNr(dcNrIn), eventToAdd(ev) +EventBase::EventBase(struct dive *dIn, int dcNrIn) : + d(dIn), dcNr(dcNrIn) +{ +} + +void EventBase::redo() +{ + redoit(); // Call actual function in base class + invalidate_dive_cache(d); + emit diveListNotifier.eventsChanged(d); +} + +void EventBase::undo() +{ + undoit(); // Call actual function in base class + invalidate_dive_cache(d); + emit diveListNotifier.eventsChanged(d); +} + +AddEventBase::AddEventBase(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr), + eventToAdd(ev) { } @@ -18,23 +37,19 @@ bool AddEventBase::workToBeDone() return true; } -void AddEventBase::redo() +void AddEventBase::redoit() { struct divecomputer *dc = get_dive_dc(d, dcNr); eventToRemove = eventToAdd.get(); add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend - invalidate_dive_cache(d); - emit diveListNotifier.eventsChanged(d); } -void AddEventBase::undo() +void AddEventBase::undoit() { struct divecomputer *dc = get_dive_dc(d, dcNr); remove_event_from_dc(dc, eventToRemove); eventToAdd.reset(eventToRemove); // take ownership of event eventToRemove = nullptr; - invalidate_dive_cache(d); - emit diveListNotifier.eventsChanged(d); } AddEventBookmark::AddEventBookmark(struct dive *d, int dcNr, int seconds) : diff --git a/commands/command_event.h b/commands/command_event.h index 8cceaeb25..a674e258b 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -16,19 +16,28 @@ namespace Command { // Therefore, the undo commands work on events as they do with dives: using // owning pointers. See comments in command_base.h -class AddEventBase : public Base { -public: - AddEventBase(struct dive *d, int dcNr, struct event *ev); // Takes ownership of event! -private: - bool workToBeDone() override; +class EventBase : public Base { +protected: + EventBase(struct dive *d, int dcNr); void undo() override; void redo() override; + virtual void redoit() = 0; + virtual void undoit() = 0; // Note: we store dive and the divecomputer-number instead of a pointer to the divecomputer. // Since one divecomputer is integrated into the dive structure, pointers to divecomputers // are probably not stable. struct dive *d; int dcNr; +}; + +class AddEventBase : public EventBase { +public: + AddEventBase(struct dive *d, int dcNr, struct event *ev); // Takes ownership of event! +private: + bool workToBeDone() override; + void undoit() override; + void redoit() override; OwningEventPtr eventToAdd; // for redo event *eventToRemove; // for undo From ab8e317b28672cc19fd04e994b9adf9b63f0c603 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 18:13:02 +0100 Subject: [PATCH 53/78] undo: implement renaming of events There is a slight complexity here owing to the fact that the profile works on a copy of the current dive: We get a copy of the event and have to search for the original event in the current dive. This could be done in the undo command. Nevertheless, here we do it in the profile so that when in the future the profile can work on a non-copied dive we can simply remove this function. Signed-off-by: Berthold Stoeger --- commands/command.cpp | 5 +++++ commands/command.h | 1 + commands/command_event.cpp | 27 +++++++++++++++++++++++++++ commands/command_event.h | 12 ++++++++++++ core/dive.c | 20 +++++++++++++++++++- core/dive.h | 3 +++ profile-widget/profilewidget2.cpp | 29 ++++++++++++++++++++--------- 7 files changed, 87 insertions(+), 10 deletions(-) diff --git a/commands/command.cpp b/commands/command.cpp index 44eb4b8f0..09e85f3b2 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -344,4 +344,9 @@ void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO execute(new AddEventSetpointChange(d, dcNr, seconds, pO2)); } +void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name) +{ + execute(new RenameEvent(d, dcNr, ev, name)); +} + } // namespace Command diff --git a/commands/command.h b/commands/command.h index aa7c75ab3..eeb400bc4 100644 --- a/commands/command.h +++ b/commands/command.h @@ -109,6 +109,7 @@ void editTripNotes(dive_trip *trip, const QString &s); void addEventBookmark(struct dive *d, int dcNr, int seconds); void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode); void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2); +void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name); } // namespace Command diff --git a/commands/command_event.cpp b/commands/command_event.cpp index 7ecd4f070..1114182aa 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -70,4 +70,31 @@ AddEventSetpointChange::AddEventSetpointChange(struct dive *d, int dcNr, int sec setText(tr("Add set point change")); // TODO: format pO2 value in bar or psi. } +RenameEvent::RenameEvent(struct dive *d, int dcNr, struct event *ev, const char *name) : EventBase(d, dcNr), + eventToAdd(clone_event_rename(ev, name)), + eventToRemove(ev) +{ + setText(tr("Rename bookmark to %1").arg(name)); +} + +bool RenameEvent::workToBeDone() +{ + return true; +} + +void RenameEvent::redoit() +{ + struct divecomputer *dc = get_dive_dc(d, dcNr); + swap_event(dc, eventToRemove, eventToAdd.get()); + event *tmp = eventToRemove; + eventToRemove = eventToAdd.release(); + eventToAdd.reset(tmp); +} + +void RenameEvent::undoit() +{ + // Undo and redo do the same thing - they simply swap events + redoit(); +} + } // namespace Command diff --git a/commands/command_event.h b/commands/command_event.h index a674e258b..ddfe6f7d4 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -58,6 +58,18 @@ public: AddEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2); }; +class RenameEvent : public EventBase { +public: + RenameEvent(struct dive *d, int dcNr, struct event *ev, const char *name); +private: + bool workToBeDone() override; + void undoit() override; + void redoit() override; + + OwningEventPtr eventToAdd; // for undo and redo + event *eventToRemove; // for undo and redo +}; + } // namespace Command #endif // COMMAND_EVENT_H diff --git a/core/dive.c b/core/dive.c index 3ec9b1a82..6d2645cef 100644 --- a/core/dive.c +++ b/core/dive.c @@ -166,6 +166,11 @@ struct event *create_event(unsigned int time, int type, int flags, int value, co return ev; } +struct event *clone_event_rename(const struct event *ev, const char *name) +{ + return create_event(ev->time.seconds, ev->type, ev->flags, ev->value, name); +} + void add_event_to_dc(struct divecomputer *dc, struct event *ev) { struct event **p; @@ -192,7 +197,20 @@ struct event *add_event(struct divecomputer *dc, unsigned int time, int type, in return ev; } -static int same_event(const struct event *a, const struct event *b) +/* Substitutes an event in a divecomputer for another. No reordering is performed! */ +void swap_event(struct divecomputer *dc, struct event *from, struct event *to) +{ + for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) { + if (*ep == from) { + to->next = from->next; + *ep = to; + from->next = NULL; // For good measure. + break; + } + } +} + +bool same_event(const struct event *a, const struct event *b) { if (a->time.seconds != b->time.seconds) return 0; diff --git a/core/dive.h b/core/dive.h index 1de68f4ff..727d4bbff 100644 --- a/core/dive.h +++ b/core/dive.h @@ -378,7 +378,10 @@ extern bool is_cylinder_used(const struct dive *dive, int idx); extern bool is_cylinder_prot(const struct dive *dive, int idx); extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx); extern struct event *create_event(unsigned int time, int type, int flags, int value, const char *name); +extern struct event *clone_event_rename(const struct event *ev, const char *name); extern void add_event_to_dc(struct divecomputer *dc, struct event *ev); +extern void swap_event(struct divecomputer *dc, struct event *from, struct event *to); +extern bool same_event(const struct event *a, const struct event *b); extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); extern void remove_event_from_dc(struct divecomputer *dc, struct event *event); extern void remove_event(const struct event *event); diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 76b3c9afb..824c4ccb6 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1584,6 +1584,22 @@ void ProfileWidget2::unhideEvents() item->show(); } +// The profile displays a copy of the current_dive, namely displayed_dive. +// Therefore, the events we get are likewise copies. This function finds +// the original event. TODO: Remove function once the profile can display +// arbitrary dives. +static event *find_event(const struct event *ev) +{ + struct divecomputer *dc = current_dc; + if (!dc) + return nullptr; + for (struct event *act = current_dc->events; act; act = act->next) { + if (same_event(act, ev)) + return act; + } + return nullptr; +} + void ProfileWidget2::removeEvent(DiveEventItem *item) { struct event *event = item->getEvent(); @@ -1698,7 +1714,9 @@ double ProfileWidget2::getFontPrintScale() #ifndef SUBSURFACE_MOBILE void ProfileWidget2::editName(DiveEventItem *item) { - struct event *event = item->getEvent(); + struct event *event = find_event(item->getEvent()); + if (!event) + return; bool ok; QString newName = QInputDialog::getText(this, tr("Edit name of bookmark"), tr("Custom name:"), QLineEdit::Normal, @@ -1710,14 +1728,7 @@ void ProfileWidget2::editName(DiveEventItem *item) lengthWarning.exec(); return; } - // order is important! first update the current dive (by matching the unchanged event), - // then update the displayed dive (as event is part of the events on displayed dive - // and will be freed as part of changing the name! - update_event_name(current_dive, event, qPrintable(newName)); - update_event_name(&displayed_dive, event, qPrintable(newName)); - invalidate_dive_cache(current_dive); - mark_divelist_changed(true); - replot(); + Command::renameEvent(current_dive, dc_number, event, qPrintable(newName)); } } #endif From 3d511b069f5e2c659ed5af039485b1c72c348b8a Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 18:25:47 +0100 Subject: [PATCH 54/78] undo: add event removal undo command This was a trivial copy & past of the event-adding undo command with a switch of the undo() and redo() actions. Signed-off-by: Berthold Stoeger --- commands/command.cpp | 5 +++++ commands/command.h | 1 + commands/command_event.cpp | 26 ++++++++++++++++++++++++++ commands/command_event.h | 12 ++++++++++++ profile-widget/profilewidget2.cpp | 12 +++++------- 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/commands/command.cpp b/commands/command.cpp index 09e85f3b2..54f9b22f9 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -349,4 +349,9 @@ void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name) execute(new RenameEvent(d, dcNr, ev, name)); } +void removeEvent(struct dive *d, int dcNr, struct event *ev) +{ + execute(new RemoveEvent(d, dcNr, ev)); +} + } // namespace Command diff --git a/commands/command.h b/commands/command.h index eeb400bc4..82931747c 100644 --- a/commands/command.h +++ b/commands/command.h @@ -110,6 +110,7 @@ void addEventBookmark(struct dive *d, int dcNr, int seconds); void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode); void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2); void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name); +void removeEvent(struct dive *d, int dcNr, struct event *ev); } // namespace Command diff --git a/commands/command_event.cpp b/commands/command_event.cpp index 1114182aa..e32edfc41 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -97,4 +97,30 @@ void RenameEvent::undoit() redoit(); } +RemoveEvent::RemoveEvent(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr), + eventToRemove(ev) +{ + setText(tr("Remove %1 event").arg(ev->name)); +} + +bool RemoveEvent::workToBeDone() +{ + return true; +} + +void RemoveEvent::redoit() +{ + struct divecomputer *dc = get_dive_dc(d, dcNr); + remove_event_from_dc(dc, eventToRemove); + eventToAdd.reset(eventToRemove); // take ownership of event + eventToRemove = nullptr; +} + +void RemoveEvent::undoit() +{ + struct divecomputer *dc = get_dive_dc(d, dcNr); + eventToRemove = eventToAdd.get(); + add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend +} + } // namespace Command diff --git a/commands/command_event.h b/commands/command_event.h index ddfe6f7d4..eec5cb262 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -70,6 +70,18 @@ private: event *eventToRemove; // for undo and redo }; +class RemoveEvent : public EventBase { +public: + RemoveEvent(struct dive *d, int dcNr, struct event *ev); +private: + bool workToBeDone() override; + void undoit() override; + void redoit() override; + + OwningEventPtr eventToAdd; // for undo + event *eventToRemove; // for redo +}; + } // namespace Command #endif // COMMAND_EVENT_H diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 824c4ccb6..f236ed550 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1602,17 +1602,15 @@ static event *find_event(const struct event *ev) void ProfileWidget2::removeEvent(DiveEventItem *item) { - struct event *event = item->getEvent(); + struct event *event = find_event(item->getEvent()); + if (!event) + return; if (QMessageBox::question(this, TITLE_OR_TEXT( tr("Remove the selected event?"), tr("%1 @ %2:%3").arg(event->name).arg(event->time.seconds / 60).arg(event->time.seconds % 60, 2, 10, QChar('0'))), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { - remove_event(event); - invalidate_dive_cache(current_dive); - mark_divelist_changed(true); - replot(); - } + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) + Command::removeEvent(current_dive, dc_number, event); } void ProfileWidget2::addBookmark(int seconds) From b1906dd04f0162619df5710cde9248aa4b342a2b Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 19:29:59 +0100 Subject: [PATCH 55/78] cleanup: move add_gas_switch_event to dive.c Since all the other event-functions are also defined there. Ultimately, we should probably move them to their own event.c translation unit. Signed-off-by: Berthold Stoeger --- core/dive.c | 26 ++++++++++++++++++++++++++ core/parse-xml.c | 25 ------------------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/core/dive.c b/core/dive.c index 6d2645cef..d46f2de1d 100644 --- a/core/dive.c +++ b/core/dive.c @@ -11,6 +11,7 @@ #include "device.h" #include "divelist.h" #include "divesite.h" +#include "errorhelper.h" #include "qthelper.h" #include "metadata.h" #include "membuffer.h" @@ -197,6 +198,31 @@ struct event *add_event(struct divecomputer *dc, unsigned int time, int type, in return ev; } +void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) +{ + /* sanity check so we don't crash */ + if (idx < 0 || idx >= dive->cylinders.nr) { + report_error("Unknown cylinder index: %d", idx); + return; + } + /* The gas switch event format is insane for historical reasons */ + struct gasmix mix = get_cylinder(dive, idx)->gasmix; + int o2 = get_o2(mix); + int he = get_he(mix); + struct event *ev; + int value; + + o2 = (o2 + 5) / 10; + he = (he + 5) / 10; + value = o2 + (he << 16); + + ev = add_event(dc, seconds, he ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE, 0, value, "gaschange"); + if (ev) { + ev->gas.index = idx; + ev->gas.mix = mix; + } +} + /* Substitutes an event in a divecomputer for another. No reordering is performed! */ void swap_event(struct divecomputer *dc, struct event *from, struct event *to) { diff --git a/core/parse-xml.c b/core/parse-xml.c index 6218fc74d..2f3d7a283 100644 --- a/core/parse-xml.c +++ b/core/parse-xml.c @@ -697,31 +697,6 @@ static void try_to_match_autogroup(const char *name, char *buf) nonmatch("autogroup", name, buf); } -void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) -{ - /* sanity check so we don't crash */ - if (idx < 0 || idx >= dive->cylinders.nr) { - report_error("Unknown cylinder index: %d", idx); - return; - } - /* The gas switch event format is insane for historical reasons */ - struct gasmix mix = get_cylinder(dive, idx)->gasmix; - int o2 = get_o2(mix); - int he = get_he(mix); - struct event *ev; - int value; - - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - value = o2 + (he << 16); - - ev = add_event(dc, seconds, he ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE, 0, value, "gaschange"); - if (ev) { - ev->gas.index = idx; - ev->gas.mix = mix; - } -} - static void get_cylinderindex(char *buffer, uint8_t *i, struct parser_state *state) { *i = atoi(buffer); From 9fe1951db29a3470e525c9a4e03612d6f7d5ecbb Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 19:41:40 +0100 Subject: [PATCH 56/78] core: split out create_gas_change_event() from add_gas_change_event() For undo, we want to create gas change events without adding them immediately to the dive computer. Signed-off-by: Berthold Stoeger --- core/dive.c | 38 ++++++++++++++++++++++---------------- core/dive.h | 1 + 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/core/dive.c b/core/dive.c index d46f2de1d..849425389 100644 --- a/core/dive.c +++ b/core/dive.c @@ -167,6 +167,26 @@ struct event *create_event(unsigned int time, int type, int flags, int value, co return ev; } +/* warning: does not test idx for validity */ +struct event *create_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) +{ + /* The gas switch event format is insane for historical reasons */ + struct gasmix mix = get_cylinder(dive, idx)->gasmix; + int o2 = get_o2(mix); + int he = get_he(mix); + struct event *ev; + int value; + + o2 = (o2 + 5) / 10; + he = (he + 5) / 10; + value = o2 + (he << 16); + + ev = create_event(seconds, he ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE, 0, value, "gaschange"); + ev->gas.index = idx; + ev->gas.mix = mix; + return ev; +} + struct event *clone_event_rename(const struct event *ev, const char *name) { return create_event(ev->time.seconds, ev->type, ev->flags, ev->value, name); @@ -205,22 +225,8 @@ void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int second report_error("Unknown cylinder index: %d", idx); return; } - /* The gas switch event format is insane for historical reasons */ - struct gasmix mix = get_cylinder(dive, idx)->gasmix; - int o2 = get_o2(mix); - int he = get_he(mix); - struct event *ev; - int value; - - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - value = o2 + (he << 16); - - ev = add_event(dc, seconds, he ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE, 0, value, "gaschange"); - if (ev) { - ev->gas.index = idx; - ev->gas.mix = mix; - } + struct event *ev = create_gas_switch_event(dive, dc, seconds, idx); + add_event_to_dc(dc, ev); } /* Substitutes an event in a divecomputer for another. No reordering is performed! */ diff --git a/core/dive.h b/core/dive.h index 727d4bbff..2f4e96504 100644 --- a/core/dive.h +++ b/core/dive.h @@ -378,6 +378,7 @@ extern bool is_cylinder_used(const struct dive *dive, int idx); extern bool is_cylinder_prot(const struct dive *dive, int idx); extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx); extern struct event *create_event(unsigned int time, int type, int flags, int value, const char *name); +extern struct event *create_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx); extern struct event *clone_event_rename(const struct event *ev, const char *name); extern void add_event_to_dc(struct divecomputer *dc, struct event *ev); extern void swap_event(struct divecomputer *dc, struct event *from, struct event *to); From c585fd9f8ee0c1f27f997b4e19984b50d150f6cb Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 19:43:18 +0100 Subject: [PATCH 57/78] cleanup: remove vintage report_error() function declaration Remove an old commented-out declaration. Signed-off-by: Berthold Stoeger --- core/dive.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/dive.h b/core/dive.h index 2f4e96504..77e6973c9 100644 --- a/core/dive.h +++ b/core/dive.h @@ -396,8 +396,6 @@ extern int nr_weightsystems(const struct dive *dive); /* UI related protopypes */ -// extern void report_error(GError* error); - extern void remember_event(const char *eventname); extern void invalidate_dive_cache(struct dive *dc); From 0bd821183d715f709d378e8ac1a75aa0c3d0f2cc Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 21:10:05 +0100 Subject: [PATCH 58/78] undo: implement gas switch This is a bit hairy as - in theory - one gas switch can remove other gas switch(es) at the same timestamp. However, I did not find a way to test it. Moreover, it is not clear whether the dive-tabs are properly updated on undo/redo. Signed-off-by: Berthold Stoeger --- CHANGELOG.md | 1 + commands/command.cpp | 5 +++ commands/command.h | 1 + commands/command_event.cpp | 62 +++++++++++++++++++++++++++++++ commands/command_event.h | 13 +++++++ profile-widget/profilewidget2.cpp | 21 +---------- 6 files changed, 83 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8888b1ab1..d3fefd712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Mobile: make sure filter header and virtual keyboard are shown when tapping on f Mobile: fix issue where in some circumstances changes weren't written to storage Mobile: fix issues with filtering while editing dives Desktop: implement dive invalidation +Undo: implement undo of event handling (viz. bookmarks, setpoints, gas switches) Desktop: fix tab-order in filter widget Desktop: implement fulltext search Desktop: add starts-with and exact filter modes for textual search diff --git a/commands/command.cpp b/commands/command.cpp index 54f9b22f9..c173494c0 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -354,4 +354,9 @@ void removeEvent(struct dive *d, int dcNr, struct event *ev) execute(new RemoveEvent(d, dcNr, ev)); } +void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank) +{ + execute(new AddGasSwitch(d, dcNr, seconds, tank)); +} + } // namespace Command diff --git a/commands/command.h b/commands/command.h index 82931747c..66122ac99 100644 --- a/commands/command.h +++ b/commands/command.h @@ -111,6 +111,7 @@ void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode) void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2); void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name); void removeEvent(struct dive *d, int dcNr, struct event *ev); +void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank); } // namespace Command diff --git a/commands/command_event.cpp b/commands/command_event.cpp index e32edfc41..6d217da65 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -5,6 +5,7 @@ #include "core/subsurface-qt/divelistnotifier.h" #include "core/libdivecomputer.h" #include "core/gettextfromc.h" +#include namespace Command { @@ -123,4 +124,65 @@ void RemoveEvent::undoit() add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend } +AddGasSwitch::AddGasSwitch(struct dive *d, int dcNr, int seconds, int tank) : EventBase(d, dcNr) +{ + // If there is a gas change at this time stamp, remove it before adding the new one. + // There shouldn't be more than one gas change per time stamp. Just in case we'll + // support that anyway. + struct divecomputer *dc = get_dive_dc(d, dcNr); + struct event *gasChangeEvent = dc->events; + while ((gasChangeEvent = get_next_event_mutable(gasChangeEvent, "gaschange")) != NULL) { + if (gasChangeEvent->time.seconds == seconds) { + eventsToRemove.push_back(gasChangeEvent); + int idx = gasChangeEvent->gas.index; + if (std::find(cylinders.begin(), cylinders.end(), idx) == cylinders.end()) + cylinders.push_back(idx); // cylinders might have changed their status + } + gasChangeEvent = gasChangeEvent->next; + } + + eventsToAdd.emplace_back(create_gas_switch_event(d, dc, seconds, tank)); +} + +bool AddGasSwitch::workToBeDone() +{ + return true; +} + +void AddGasSwitch::redoit() +{ + std::vector newEventsToAdd; + std::vector newEventsToRemove; + newEventsToAdd.reserve(eventsToRemove.size()); + newEventsToRemove.reserve(eventsToAdd.size()); + struct divecomputer *dc = get_dive_dc(d, dcNr); + + for (event *ev: eventsToRemove) { + remove_event_from_dc(dc, ev); + newEventsToAdd.emplace_back(ev); // take ownership of event + } + for (OwningEventPtr &ev: eventsToAdd) { + newEventsToRemove.push_back(ev.get()); + add_event_to_dc(dc, ev.release()); // return ownership to backend + } + eventsToAdd = std::move(newEventsToAdd); + eventsToRemove = std::move(newEventsToRemove); + + // this means we potentially have a new tank that is being used and needs to be shown + fixup_dive(d); + + for (int idx: cylinders) + emit diveListNotifier.cylinderEdited(d, idx); + + // TODO: This is silly we send a DURATION change event so that the statistics are recalculated. + // We should instead define a proper DiveField that expresses the change caused by a gas switch. + emit diveListNotifier.divesChanged(QVector{ d }, DiveField::DURATION | DiveField::DEPTH); +} + +void AddGasSwitch::undoit() +{ + // Undo and redo do the same thing, as the dives-to-be-added and dives-to-be-removed are exchanged. + redoit(); +} + } // namespace Command diff --git a/commands/command_event.h b/commands/command_event.h index eec5cb262..8e75dee6c 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -82,6 +82,19 @@ private: event *eventToRemove; // for redo }; +class AddGasSwitch : public EventBase { +public: + AddGasSwitch(struct dive *d, int dcNr, int seconds, int tank); +private: + bool workToBeDone() override; + void undoit() override; + void redoit() override; + + std::vector cylinders; // cylinders that are modified + std::vector eventsToAdd; + std::vector eventsToRemove; +}; + } // namespace Command #endif // COMMAND_EVENT_H diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index f236ed550..6072d4c0d 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1645,26 +1645,7 @@ void ProfileWidget2::changeGas(int tank, int seconds) if (!current_dive || tank < 0 || tank >= current_dive->cylinders.nr) return; - // if there is a gas change at this time stamp, remove it before adding the new one - struct event *gasChangeEvent = current_dc->events; - while ((gasChangeEvent = get_next_event_mutable(gasChangeEvent, "gaschange")) != NULL) { - if (gasChangeEvent->time.seconds == seconds) { - remove_event(gasChangeEvent); - gasChangeEvent = current_dc->events; - } else { - gasChangeEvent = gasChangeEvent->next; - } - } - add_gas_switch_event(current_dive, current_dc, seconds, tank); - // this means we potentially have a new tank that is being used and needs to be shown - fixup_dive(current_dive); - invalidate_dive_cache(current_dive); - - // FIXME - this no longer gets written to the dive list - so we need to enableEdition() here - - emit updateDiveInfo(); - mark_divelist_changed(true); - replot(); + Command::addGasSwitch(current_dive, dc_number, seconds, tank); } #endif From 7081674b672b23b6c22feb4bae94cb3febf3fe1c Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 21:40:45 +0100 Subject: [PATCH 59/78] cleanup: remove remove_event() function No user left. Signed-off-by: Berthold Stoeger --- core/dive.c | 18 ------------------ core/dive.h | 1 - 2 files changed, 19 deletions(-) diff --git a/core/dive.c b/core/dive.c index 849425389..411607926 100644 --- a/core/dive.c +++ b/core/dive.c @@ -267,24 +267,6 @@ void remove_event_from_dc(struct divecomputer *dc, struct event *event) } } -/* Remove an event from current dive computer that is identical to the passed in event. - * Frees the event. */ -void remove_event(const struct event *event) -{ - struct event **ep = ¤t_dc->events; - while (ep && !same_event(*ep, event)) - ep = &(*ep)->next; - if (ep) { - /* we can't link directly with event->next - * because 'event' can be a copy from another - * dive (for instance the displayed_dive - * that we use on the interface to show things). */ - struct event *temp = (*ep)->next; - free(*ep); - *ep = temp; - } -} - /* since the name is an array as part of the structure (how silly is that?) we * have to actually remove the existing event and replace it with a new one. * WARNING, WARNING... this may end up freeing event in case that event is indeed diff --git a/core/dive.h b/core/dive.h index 77e6973c9..829575194 100644 --- a/core/dive.h +++ b/core/dive.h @@ -385,7 +385,6 @@ extern void swap_event(struct divecomputer *dc, struct event *from, struct event extern bool same_event(const struct event *a, const struct event *b); extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); extern void remove_event_from_dc(struct divecomputer *dc, struct event *event); -extern void remove_event(const struct event *event); extern void update_event_name(struct dive *d, struct event *event, const char *name); extern void add_extra_data(struct divecomputer *dc, const char *key, const char *value); extern void per_cylinder_mean_depth(const struct dive *dive, struct divecomputer *dc, int *mean, int *duration); From 48b4dc9c845f147d97c6f7e13582b921654a227c Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 4 Mar 2020 21:58:46 +0100 Subject: [PATCH 60/78] cleanup: remove commented-out evn_foreach() function Apparently this was used to hide events in pre-Qt times. However, that has already been reimplemented in different ways. Let's remove that commented-out code. Signed-off-by: Berthold Stoeger --- core/dive.h | 4 ---- core/profile.c | 13 ------------- 2 files changed, 17 deletions(-) diff --git a/core/dive.h b/core/dive.h index 829575194..42c3cdaf9 100644 --- a/core/dive.h +++ b/core/dive.h @@ -398,10 +398,6 @@ extern int nr_weightsystems(const struct dive *dive); extern void remember_event(const char *eventname); extern void invalidate_dive_cache(struct dive *dc); -#if WE_DONT_USE_THIS /* this is a missing feature in Qt - selecting which events to display */ -extern int evn_foreach(void (*callback)(const char *, bool *, void *), void *data); -#endif /* WE_DONT_USE_THIS */ - extern void clear_events(void); extern void set_dc_nickname(struct dive *dive); diff --git a/core/profile.c b/core/profile.c index 88b48d1cd..26488cc30 100644 --- a/core/profile.c +++ b/core/profile.c @@ -120,19 +120,6 @@ struct ev_select *ev_namelist; int evn_allocated; int evn_used; -#if WE_DONT_USE_THIS /* we need to implement event filters in Qt */ -int evn_foreach (void (*callback)(const char *, bool *, void *), void *data) -{ - int i; - - for (i = 0; i < evn_used; i++) { - /* here we display an event name on screen - so translate */ - callback(translate("gettextFromC", ev_namelist[i].ev_name), &ev_namelist[i].plot_ev, data); - } - return i; -} -#endif /* WE_DONT_USE_THIS */ - void clear_events(void) { for (int i = 0; i < evn_used; i++) From e39063f6df269facaad1430229d8b330385f68ff Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 5 Mar 2020 23:10:44 +0100 Subject: [PATCH 61/78] undo: switch to affected dive on undo/redo of event-changes Select and make current the affected dive. And also switch to the divecomputer that was affected. Signed-off-by: Berthold Stoeger --- commands/command_event.cpp | 11 +++++++++-- commands/command_event.h | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/commands/command_event.cpp b/commands/command_event.cpp index 6d217da65..2b9176af5 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -2,6 +2,7 @@ #include "command_event.h" #include "core/dive.h" +#include "core/selection.h" #include "core/subsurface-qt/divelistnotifier.h" #include "core/libdivecomputer.h" #include "core/gettextfromc.h" @@ -17,15 +18,21 @@ EventBase::EventBase(struct dive *dIn, int dcNrIn) : void EventBase::redo() { redoit(); // Call actual function in base class - invalidate_dive_cache(d); - emit diveListNotifier.eventsChanged(d); + updateDive(); } void EventBase::undo() { undoit(); // Call actual function in base class + updateDive(); +} + +void EventBase::updateDive() +{ invalidate_dive_cache(d); emit diveListNotifier.eventsChanged(d); + dc_number = dcNr; + setSelection({ d }, d); } AddEventBase::AddEventBase(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr), diff --git a/commands/command_event.h b/commands/command_event.h index 8e75dee6c..a363540d5 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -29,6 +29,8 @@ protected: // are probably not stable. struct dive *d; int dcNr; +private: + void updateDive(); }; class AddEventBase : public EventBase { From 4ae87da58cda895f3573704966ef2e0dd8161864 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 6 Mar 2020 23:20:58 +0100 Subject: [PATCH 62/78] undo: reload dive on removal of gas-switch If a gas-switch is removed we have to perform the same action as if a gas-switch is added: fixup the dive and signal the changed cylinder and stats. Adapt the RemoveEvent command accordingly. Copy the code of the AddGasSwitch command and simplify for the fact that only ony cylinder can be affected. Signed-off-by: Berthold Stoeger --- commands/command_event.cpp | 17 ++++++++++++++++- commands/command_event.h | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/commands/command_event.cpp b/commands/command_event.cpp index 2b9176af5..391dfe0a9 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -106,7 +106,9 @@ void RenameEvent::undoit() } RemoveEvent::RemoveEvent(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr), - eventToRemove(ev) + eventToRemove(ev), + cylinder(ev->type == SAMPLE_EVENT_GASCHANGE2 || ev->type == SAMPLE_EVENT_GASCHANGE ? + ev->gas.index : -1) { setText(tr("Remove %1 event").arg(ev->name)); } @@ -131,6 +133,19 @@ void RemoveEvent::undoit() add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend } +void RemoveEvent::post() const +{ + if (cylinder < 0) + return; + + fixup_dive(d); + emit diveListNotifier.cylinderEdited(d, cylinder); + + // TODO: This is silly we send a DURATION change event so that the statistics are recalculated. + // We should instead define a proper DiveField that expresses the change caused by a gas switch. + emit diveListNotifier.divesChanged(QVector{ d }, DiveField::DURATION | DiveField::DEPTH); +} + AddGasSwitch::AddGasSwitch(struct dive *d, int dcNr, int seconds, int tank) : EventBase(d, dcNr) { // If there is a gas change at this time stamp, remove it before adding the new one. diff --git a/commands/command_event.h b/commands/command_event.h index a363540d5..b7e67f218 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -79,9 +79,11 @@ private: bool workToBeDone() override; void undoit() override; void redoit() override; + void post() const; // Called to fix up dives should a gas-change have happened. OwningEventPtr eventToAdd; // for undo event *eventToRemove; // for redo + int cylinder; // affected cylinder (if removing gas switch). <0: not a gas switch. }; class AddGasSwitch : public EventBase { From bbd3f0dd6d019245b7708b867e7f4c6522332443 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 15 Mar 2020 19:07:44 +0100 Subject: [PATCH 63/78] cylinders: use preferences modpO2 preferences value Use the user-editable MOD-pO2 preferences value when creating a default cylinder. It is not clear to me, when that even has a consequence, but it looks like the right thing to do. Reported-by: Robert C. Helling Signed-off-by: Berthold Stoeger --- core/equipment.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/equipment.c b/core/equipment.c index 24d1d826b..494baf5c1 100644 --- a/core/equipment.c +++ b/core/equipment.c @@ -397,7 +397,7 @@ void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl) { const char *cyl_name = prefs.default_cylinder; struct tank_info_t *ti = tank_info; - pressure_t pO2 = {.mbar = 1600}; + pressure_t pO2 = {.mbar = lrint(prefs.modpO2 * 1000.0)}; if (!cyl_name) return; From e8beeb6767b387204fcaae4a066f226ca37f8a82 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 15 Mar 2020 21:51:27 +0100 Subject: [PATCH 64/78] desktop: properly initialize activeText of model-delegates For reasons that I don't understand, we keep track of the current combo-box text for our model-delegates. However, that text was not initialized when the editor was generated, leading to a UI bug in the cylinder and weight widgets: Activate a field, click somewhere else -> either the empty string or the previous string was set. Reported-by: Robert C. Helling Signed-off-by: Berthold Stoeger --- desktop-widgets/modeldelegates.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index 65e8f377e..d7dd28b0e 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -129,6 +129,7 @@ QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewI currCombo.comboEditor = comboDelegate; currCombo.currRow = index.row(); currCombo.model = const_cast(index.model()); + currCombo.activeText = currCombo.model->data(index).toString(); keyboardFinished = false; // Current display of things on Gnome3 looks like shit, so From 3b9913d82893ca023eaea2e25cdbc86765aeb40a Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 18 Mar 2020 22:37:18 +0100 Subject: [PATCH 65/78] desktop/tabwidgets: replace IGNORE_MODE by flag The editMode was set to IGNORE_MODE when programatically setting fields so that we can ignore changed-signals. That seems to be orthogonal to whether we are in edit mode and indeed when setting IGNORE_MODE the edit mode was saved and restored. Therefore, replace the IGNORE_MODE by an independent ignoreInput flag. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.cpp | 32 ++++++++++++------------- desktop-widgets/tab-widgets/maintab.h | 4 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index eaf19b7db..87e9854d5 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -50,6 +50,7 @@ struct Completers { MainTab::MainTab(QWidget *parent) : QTabWidget(parent), editMode(NONE), + ignoreInput(false), lastSelectedDive(true), lastTabSelectedDive(0), lastTabSelectedDiveTrip(0), @@ -341,7 +342,6 @@ void MainTab::updateDiveSite(struct dive *d) void MainTab::updateDiveInfo() { ui.location->refreshDiveSiteCache(); - EditMode rememberEM = editMode; // don't execute this while adding / planning a dive if (editMode == MANUALLY_ADDED_DIVE || MainWindow::instance()->graphics->isPlanner()) return; @@ -353,7 +353,7 @@ void MainTab::updateDiveInfo() for (int i = 0; i < extraWidgets.size() - 1; ++i) extraWidgets[i]->setEnabled(enabled); - editMode = IGNORE_MODE; // don't trigger on changes to the widgets + ignoreInput = true; // don't trigger on changes to the widgets for (TabBase *widget: extraWidgets) widget->updateData(); @@ -476,7 +476,7 @@ void MainTab::updateDiveInfo() ui.timeEdit->setTime(QTime(0, 0, 0, 0)); ui.tagWidget->clear(); } - editMode = rememberEM; + ignoreInput = false; if (verbose && current_dive && current_dive->dive_site) qDebug() << "Set the current dive site:" << current_dive->dive_site->uuid; @@ -499,18 +499,17 @@ void MainTab::acceptChanges() if (ui.location->hasFocus()) stealFocus(); - EditMode lastMode = editMode; - editMode = IGNORE_MODE; + ignoreInput = true; ui.dateEdit->setEnabled(true); hideMessage(); - if (lastMode == MANUALLY_ADDED_DIVE) { + if (editMode == MANUALLY_ADDED_DIVE) { MainWindow::instance()->showProfile(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING); Command::editProfile(&displayed_dive); } int scrolledBy = MainWindow::instance()->diveList->verticalScrollBar()->sliderPosition(); - if (lastMode == MANUALLY_ADDED_DIVE) { + if (editMode == MANUALLY_ADDED_DIVE) { MainWindow::instance()->diveList->reload(); MainWindow::instance()->refreshDisplay(); MainWindow::instance()->graphics->replot(); @@ -522,6 +521,7 @@ void MainTab::acceptChanges() MainWindow::instance()->diveList->setFocus(); MainWindow::instance()->setEnabledToolbar(true); ui.editDiveSiteButton->setEnabled(!ui.location->text().isEmpty()); + ignoreInput = false; editMode = NONE; } @@ -565,7 +565,7 @@ void MainTab::divesEdited(int i) void MainTab::on_buddy_editingFinished() { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; divesEdited(Command::editBuddies(stringToList(ui.buddy->toPlainText()), false)); @@ -573,7 +573,7 @@ void MainTab::on_buddy_editingFinished() void MainTab::on_divemaster_editingFinished() { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; divesEdited(Command::editDiveMaster(stringToList(ui.divemaster->toPlainText()), false)); @@ -581,7 +581,7 @@ void MainTab::on_divemaster_editingFinished() void MainTab::on_duration_editingFinished() { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; // Duration editing is special: we only edit the current dive. @@ -590,7 +590,7 @@ void MainTab::on_duration_editingFinished() void MainTab::on_depth_editingFinished() { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; // Depth editing is special: we only edit the current dive. @@ -610,7 +610,7 @@ static void shiftTime(QDateTime &dateTime) void MainTab::on_dateEdit_dateChanged(const QDate &date) { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(1000*current_dive->when, Qt::UTC); dateTime.setTimeSpec(Qt::UTC); @@ -620,7 +620,7 @@ void MainTab::on_dateEdit_dateChanged(const QDate &date) void MainTab::on_timeEdit_timeChanged(const QTime &time) { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(1000*current_dive->when, Qt::UTC); dateTime.setTimeSpec(Qt::UTC); @@ -630,7 +630,7 @@ void MainTab::on_timeEdit_timeChanged(const QTime &time) void MainTab::on_tagWidget_editingFinished() { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; divesEdited(Command::editTags(ui.tagWidget->getBlockStringList(), false)); @@ -638,7 +638,7 @@ void MainTab::on_tagWidget_editingFinished() void MainTab::on_location_diveSiteSelected() { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; struct dive_site *newDs = ui.location->currDiveSite(); @@ -676,7 +676,7 @@ void MainTab::on_notes_editingFinished() void MainTab::on_rating_valueChanged(int value) { - if (editMode == IGNORE_MODE || !current_dive) + if (ignoreInput || !current_dive) return; divesEdited(Command::editRating(value, false)); diff --git a/desktop-widgets/tab-widgets/maintab.h b/desktop-widgets/tab-widgets/maintab.h index d6a5af593..bf071619b 100644 --- a/desktop-widgets/tab-widgets/maintab.h +++ b/desktop-widgets/tab-widgets/maintab.h @@ -28,8 +28,7 @@ class MainTab : public QTabWidget { public: enum EditMode { NONE, - MANUALLY_ADDED_DIVE, - IGNORE_MODE + MANUALLY_ADDED_DIVE }; MainTab(QWidget *parent = 0); @@ -76,6 +75,7 @@ slots: private: Ui::MainTab ui; EditMode editMode; + bool ignoreInput; // When computionally editing fields, we have to ignore changed-signals BuddyCompletionModel buddyModel; DiveMasterCompletionModel diveMasterModel; TagCompletionModel tagModel; From 20e70646584e71f03e5983660c21e7a7e097cf39 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Wed, 18 Mar 2020 22:42:47 +0100 Subject: [PATCH 66/78] desktop/tabwidgets: replace editMode by boolean There was only one editMode left (MANUALLY_ADDED_DIVE). Therefore replace by a flag. This makes the code more consistent, because the conditions "editMode != NONE" and "editMode == MANUALLY_ADDED_DIVE) actually meant the same thing. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/maintab.cpp | 22 +++++++++++----------- desktop-widgets/tab-widgets/maintab.h | 7 +------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 87e9854d5..7fde2e1b6 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -49,7 +49,7 @@ struct Completers { }; MainTab::MainTab(QWidget *parent) : QTabWidget(parent), - editMode(NONE), + editMode(false), ignoreInput(false), lastSelectedDive(true), lastTabSelectedDive(0), @@ -209,7 +209,7 @@ void MainTab::displayMessage(QString str) void MainTab::enableEdition() { - if (current_dive == NULL || editMode != NONE) + if (current_dive == NULL || editMode) return; ui.editDiveSiteButton->setEnabled(false); @@ -219,7 +219,7 @@ void MainTab::enableEdition() ui.dateEdit->setEnabled(true); displayMessage(tr("This dive is being edited.")); - editMode = MANUALLY_ADDED_DIVE; + editMode = true; } // This function gets called if a field gets updated by an undo command. @@ -284,7 +284,7 @@ void MainTab::nextInputField(QKeyEvent *event) bool MainTab::isEditing() { - return editMode != NONE; + return editMode; } static bool isHtml(const QString &s) @@ -343,7 +343,7 @@ void MainTab::updateDiveInfo() { ui.location->refreshDiveSiteCache(); // don't execute this while adding / planning a dive - if (editMode == MANUALLY_ADDED_DIVE || MainWindow::instance()->graphics->isPlanner()) + if (editMode || MainWindow::instance()->graphics->isPlanner()) return; // If there is no current dive, disable all widgets except the last, which is the dive site tab. @@ -503,13 +503,13 @@ void MainTab::acceptChanges() ui.dateEdit->setEnabled(true); hideMessage(); - if (editMode == MANUALLY_ADDED_DIVE) { + if (editMode) { MainWindow::instance()->showProfile(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING); Command::editProfile(&displayed_dive); } int scrolledBy = MainWindow::instance()->diveList->verticalScrollBar()->sliderPosition(); - if (editMode == MANUALLY_ADDED_DIVE) { + if (editMode) { MainWindow::instance()->diveList->reload(); MainWindow::instance()->refreshDisplay(); MainWindow::instance()->graphics->replot(); @@ -522,12 +522,12 @@ void MainTab::acceptChanges() MainWindow::instance()->setEnabledToolbar(true); ui.editDiveSiteButton->setEnabled(!ui.location->text().isEmpty()); ignoreInput = false; - editMode = NONE; + editMode = false; } void MainTab::rejectChanges() { - if (editMode != NONE && current_dive) { + if (editMode && current_dive) { if (QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT(tr("Discard the changes?"), tr("You are about to discard your changes.")), QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Discard) != QMessageBox::Discard) { @@ -535,7 +535,7 @@ void MainTab::rejectChanges() } } ui.dateEdit->setEnabled(true); - editMode = NONE; + editMode = false; hideMessage(); // no harm done to call cancelPlan even if we were not PLAN mode... DivePlannerPointsModel::instance()->cancelPlan(); @@ -693,7 +693,7 @@ void MainTab::escDetected() { // In edit mode, pressing escape cancels the current changes. // In standard mode, remove focus of any active widget to - if (editMode != NONE) + if (editMode) rejectChanges(); else stealFocus(); diff --git a/desktop-widgets/tab-widgets/maintab.h b/desktop-widgets/tab-widgets/maintab.h index bf071619b..49003be4e 100644 --- a/desktop-widgets/tab-widgets/maintab.h +++ b/desktop-widgets/tab-widgets/maintab.h @@ -26,11 +26,6 @@ class TabBase; class MainTab : public QTabWidget { Q_OBJECT public: - enum EditMode { - NONE, - MANUALLY_ADDED_DIVE - }; - MainTab(QWidget *parent = 0); ~MainTab(); void clearTabs(); @@ -74,7 +69,7 @@ slots: void escDetected(void); private: Ui::MainTab ui; - EditMode editMode; + bool editMode; bool ignoreInput; // When computionally editing fields, we have to ignore changed-signals BuddyCompletionModel buddyModel; DiveMasterCompletionModel diveMasterModel; From 79d117b5bc801038366e245a601bea20d8a5b71b Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 22 Mar 2020 23:27:47 +0100 Subject: [PATCH 67/78] undo: be more flexible about which cylinders to edit Currently we use the same_cylinder() function to determine which cylinders should be edited in a multi-dive edit. Make this more flexible by introducing a flag-set, such that the undo-command can select which cylinders are considered as equal: - same type - same pressure - same gas mix - same size Currently both undo commands use same type, pressure and gas so that the behavior stays unchanged. The future goal is to split the cylinder-edit undo command into different commands so that when, for example, editing the type only the type is considered by not the gas mix. Signed-off-by: Berthold Stoeger --- commands/command_edit.cpp | 46 +++++++++++++++++++++++++++++++++------ commands/command_edit.h | 2 +- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index fc5200b52..8914485c6 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -1044,16 +1044,48 @@ void AddCylinder::redo() } } -static int find_cylinder_index(const struct dive *d, const cylinder_t &cyl) +static bool same_cylinder_type(const cylinder_t &cyl1, const cylinder_t &cyl2) +{ + return same_string(cyl1.type.description, cyl2.type.description) && + cyl1.cylinder_use == cyl2.cylinder_use; +} + +static bool same_cylinder_size(const cylinder_t &cyl1, const cylinder_t &cyl2) +{ + return cyl1.type.size.mliter == cyl2.type.size.mliter && + cyl1.type.workingpressure.mbar == cyl2.type.workingpressure.mbar; +} + +static bool same_cylinder_pressure(const cylinder_t &cyl1, const cylinder_t &cyl2) +{ + return cyl1.start.mbar == cyl2.start.mbar && + cyl1.end.mbar == cyl2.end.mbar; +} + +// Flags for comparing cylinders +static const constexpr int SAME_TYPE = 1 << 0; +static const constexpr int SAME_SIZE = 1 << 1; +static const constexpr int SAME_PRESS = 1 << 2; +static const constexpr int SAME_GAS = 1 << 3; + +static bool same_cylinder_with_flags(const cylinder_t &cyl1, const cylinder_t &cyl2, int sameCylinderFlags) +{ + return (((sameCylinderFlags & SAME_TYPE) == 0 || same_cylinder_type(cyl1, cyl2)) && + ((sameCylinderFlags & SAME_SIZE) == 0 || same_cylinder_size(cyl1, cyl2)) && + ((sameCylinderFlags & SAME_PRESS) == 0 || same_cylinder_pressure(cyl1, cyl2)) && + ((sameCylinderFlags & SAME_GAS) == 0 || same_gasmix(cyl1.gasmix, cyl2.gasmix))); +} + +static int find_cylinder_index(const struct dive *d, const cylinder_t &cyl, int sameCylinderFlags) { for (int idx = 0; idx < d->cylinders.nr; ++idx) { - if (same_cylinder(d->cylinders.cylinders[idx], cyl)) + if (same_cylinder_with_flags(cyl, d->cylinders.cylinders[idx], sameCylinderFlags)) return idx; } return -1; } -EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly) : +EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly, int sameCylinderFlags) : EditDivesBase(currentDiveOnly), cyl(empty_cylinder) { @@ -1076,7 +1108,7 @@ EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProt indexes.push_back(index); continue; } - int idx = find_cylinder_index(d, cyl); + int idx = find_cylinder_index(d, cyl, sameCylinderFlags); if (idx < 0 || (nonProtectedOnly && is_cylinder_prot(d, idx))) continue; divesNew.push_back(d); @@ -1097,7 +1129,7 @@ bool EditCylinderBase::workToBeDone() // ***** Remove Cylinder ***** RemoveCylinder::RemoveCylinder(int index, bool currentDiveOnly) : - EditCylinderBase(index, currentDiveOnly, true) + EditCylinderBase(index, currentDiveOnly, true, SAME_TYPE | SAME_PRESS | SAME_GAS) { if (dives.size() == 1) setText(tr("Remove cylinder")); @@ -1126,7 +1158,7 @@ void RemoveCylinder::redo() // ***** Edit Cylinder ***** EditCylinder::EditCylinder(int index, cylinder_t cylIn, bool currentDiveOnly) : - EditCylinderBase(index, currentDiveOnly, false), + EditCylinderBase(index, currentDiveOnly, false, SAME_TYPE | SAME_PRESS | SAME_GAS), new_cyl(empty_cylinder) { if (dives.empty()) @@ -1149,7 +1181,7 @@ EditCylinder::EditCylinder(int index, cylinder_t cylIn, bool currentDiveOnly) : } // If that doesn't change anything, do nothing - if (same_cylinder(cyl, new_cyl)) { + if (same_cylinder_with_flags(cyl, new_cyl, SAME_TYPE | SAME_PRESS | SAME_GAS)) { dives.clear(); return; } diff --git a/commands/command_edit.h b/commands/command_edit.h index c0b79d073..5cf6e328a 100644 --- a/commands/command_edit.h +++ b/commands/command_edit.h @@ -390,7 +390,7 @@ private: class EditCylinderBase : public EditDivesBase { protected: - EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly); + EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly, int sameCylinderFlags); ~EditCylinderBase(); cylinder_t cyl; From 5b65776e4313bb088e3eb3dcb1e8d5732be3b1d0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 22 Mar 2020 23:58:19 +0100 Subject: [PATCH 68/78] cleanup: remove same_cylinder The last user was uses a more general function. Signed-off-by: Berthold Stoeger --- core/equipment.c | 9 --------- core/equipment.h | 1 - 2 files changed, 10 deletions(-) diff --git a/core/equipment.c b/core/equipment.c index 494baf5c1..27d01692e 100644 --- a/core/equipment.c +++ b/core/equipment.c @@ -157,15 +157,6 @@ bool same_weightsystem(weightsystem_t w1, weightsystem_t w2) same_string(w1.description, w2.description); } -bool same_cylinder(cylinder_t cyl1, cylinder_t cyl2) -{ - return same_string(cyl1.type.description, cyl2.type.description) && - same_gasmix(cyl1.gasmix, cyl2.gasmix) && - cyl1.start.mbar == cyl2.start.mbar && - cyl1.end.mbar == cyl2.end.mbar && - cyl1.cylinder_use == cyl2.cylinder_use; -} - void get_gas_string(struct gasmix gasmix, char *text, int len) { if (gasmix_is_air(gasmix)) diff --git a/core/equipment.h b/core/equipment.h index 494f8dc2b..af410467f 100644 --- a/core/equipment.h +++ b/core/equipment.h @@ -84,7 +84,6 @@ extern cylinder_t *get_or_create_cylinder(struct dive *d, int idx); extern void add_cylinder_description(const cylinder_type_t *); extern void add_weightsystem_description(const weightsystem_t *); extern bool same_weightsystem(weightsystem_t w1, weightsystem_t w2); -extern bool same_cylinder(cylinder_t cyl1, cylinder_t cyl2); extern void remove_cylinder(struct dive *dive, int idx); extern void set_cylinder(struct dive *dive, int idx, cylinder_t ws); extern void remove_weightsystem(struct dive *dive, int idx); From 4e8a838f746d7319b97b56a209651a65655aca7f Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 27 Mar 2020 09:22:11 +0100 Subject: [PATCH 69/78] undo: store all cylinders in EditCylinderBase We stored only one cylinder in EditCylinderBase, which is the base class of RemoveCylinder and EditCylinder. This turns out to be too crude: when removing the "same" cylinder from multiple dives, there are some "hidden variables" such as bestmix_o2 or manually_added, which might actually be different. We don't want to overwrite those on undo of delete. Moreover, the cylinder edit is way too crude at the moment, as it overwrites the whole cylinder even when the user edited only a single field. To enable a more refined edit, we have to store each changed cylinder. Signed-off-by: Berthold Stoeger --- commands/command_edit.cpp | 62 ++++++++++++++++----------------------- commands/command_edit.h | 4 +-- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index 8914485c6..d5e2d12d0 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -1086,40 +1086,35 @@ static int find_cylinder_index(const struct dive *d, const cylinder_t &cyl, int } EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly, int sameCylinderFlags) : - EditDivesBase(currentDiveOnly), - cyl(empty_cylinder) + EditDivesBase(currentDiveOnly) { // Get the old cylinder, bail if index is invalid if (!current || index < 0 || index >= current->cylinders.nr) { dives.clear(); return; } - cyl = clone_cylinder(current->cylinders.cylinders[index]); + const cylinder_t &orig = current->cylinders.cylinders[index]; std::vector divesNew; divesNew.reserve(dives.size()); indexes.reserve(dives.size()); + cyl.reserve(dives.size()); for (dive *d: dives) { - if (d == current) { - if (nonProtectedOnly && is_cylinder_prot(d, index)) - continue; - divesNew.push_back(d); - indexes.push_back(index); - continue; - } - int idx = find_cylinder_index(d, cyl, sameCylinderFlags); + int idx = d == current ? index : find_cylinder_index(d, orig, sameCylinderFlags); if (idx < 0 || (nonProtectedOnly && is_cylinder_prot(d, idx))) continue; divesNew.push_back(d); indexes.push_back(idx); + cyl.push_back(clone_cylinder(d->cylinders.cylinders[idx])); } dives = std::move(divesNew); } EditCylinderBase::~EditCylinderBase() { - free_cylinder(cyl); + for (cylinder_t c: cyl) + free_cylinder(c); } bool EditCylinderBase::workToBeDone() @@ -1141,7 +1136,7 @@ void RemoveCylinder::undo() { for (size_t i = 0; i < dives.size(); ++i) { std::vector mapping = get_cylinder_map_for_add(dives[i]->cylinders.nr, indexes[i]); - add_to_cylinder_table(&dives[i]->cylinders, indexes[i], clone_cylinder(cyl)); + add_to_cylinder_table(&dives[i]->cylinders, indexes[i], clone_cylinder(cyl[i])); emit diveListNotifier.cylinderAdded(dives[i], indexes[i]); } } @@ -1158,8 +1153,7 @@ void RemoveCylinder::redo() // ***** Edit Cylinder ***** EditCylinder::EditCylinder(int index, cylinder_t cylIn, bool currentDiveOnly) : - EditCylinderBase(index, currentDiveOnly, false, SAME_TYPE | SAME_PRESS | SAME_GAS), - new_cyl(empty_cylinder) + EditCylinderBase(index, currentDiveOnly, false, SAME_TYPE | SAME_PRESS | SAME_GAS) { if (dives.empty()) return; @@ -1170,44 +1164,38 @@ EditCylinder::EditCylinder(int index, cylinder_t cylIn, bool currentDiveOnly) : setText(tr("Edit cylinder (%n dive(s))", "", dives.size())); // Try to untranslate the cylinder type - new_cyl = clone_cylinder(cylIn); - QString vString(new_cyl.type.description); + QString description = cylIn.type.description; for (int i = 0; i < MAX_TANK_INFO && tank_info[i].name; ++i) { - if (gettextFromC::tr(tank_info[i].name) == vString) { - free_cylinder(new_cyl); - new_cyl.type.description = copy_string(tank_info[i].name); + if (gettextFromC::tr(tank_info[i].name) == description) { + description = tank_info[i].name; break; } } - // If that doesn't change anything, do nothing - if (same_cylinder_with_flags(cyl, new_cyl, SAME_TYPE | SAME_PRESS | SAME_GAS)) { - dives.clear(); - return; - } - + // Update the tank info model TankInfoModel *tim = TankInfoModel::instance(); - QModelIndexList matches = tim->match(tim->index(0, 0), Qt::DisplayRole, gettextFromC::tr(new_cyl.type.description)); + QModelIndexList matches = tim->match(tim->index(0, 0), Qt::DisplayRole, gettextFromC::tr(cylIn.type.description)); if (!matches.isEmpty()) { - if (new_cyl.type.size.mliter != cyl.type.size.mliter) - tim->setData(tim->index(matches.first().row(), TankInfoModel::ML), new_cyl.type.size.mliter); - if (new_cyl.type.workingpressure.mbar != cyl.type.workingpressure.mbar) - tim->setData(tim->index(matches.first().row(), TankInfoModel::BAR), new_cyl.type.workingpressure.mbar / 1000.0); + if (cylIn.type.size.mliter != cyl[0].type.size.mliter) + tim->setData(tim->index(matches.first().row(), TankInfoModel::ML), cylIn.type.size.mliter); + if (cylIn.type.workingpressure.mbar != cyl[0].type.workingpressure.mbar) + tim->setData(tim->index(matches.first().row(), TankInfoModel::BAR), cylIn.type.workingpressure.mbar / 1000.0); } -} -EditCylinder::~EditCylinder() -{ - free_cylinder(new_cyl); + // The base class copied the cylinders for us, let's edit them + for (int i = 0; i < (int)indexes.size(); ++i) { + free_cylinder(cyl[i]); + cyl[i] = cylIn; + cyl[i].type.description = copy_qstring(description); + } } void EditCylinder::redo() { for (size_t i = 0; i < dives.size(); ++i) { - set_cylinder(dives[i], indexes[i], new_cyl); + std::swap(dives[i]->cylinders.cylinders[indexes[i]], cyl[i]); emit diveListNotifier.cylinderEdited(dives[i], indexes[i]); } - std::swap(cyl, new_cyl); } // Undo and redo do the same as just the stored value is exchanged diff --git a/commands/command_edit.h b/commands/command_edit.h index 5cf6e328a..d7c31f7f2 100644 --- a/commands/command_edit.h +++ b/commands/command_edit.h @@ -393,7 +393,7 @@ protected: EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly, int sameCylinderFlags); ~EditCylinderBase(); - cylinder_t cyl; + std::vector cyl; std::vector indexes; // An index for each dive in the dives vector. bool workToBeDone() override; }; @@ -409,9 +409,7 @@ private: class EditCylinder : public EditCylinderBase { public: EditCylinder(int index, cylinder_t cyl, bool currentDiveOnly); // Clones cylinder - ~EditCylinder(); private: - cylinder_t new_cyl; void undo() override; void redo() override; }; From 2eeb5f4fc2d6da8a8c8950f0b479b7cb2055af07 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 27 Mar 2020 21:09:59 +0100 Subject: [PATCH 70/78] undo: more fine-grained editing of cylinder Don't overwrite the full cylinder when editing a single field. Implement three "modes": editing of type, pressure and gasmix. Don't consider individual fields, because some of them are related. E.g. you can change the gasmix by setting the MOD. Signed-off-by: Berthold Stoeger --- commands/command.cpp | 4 ++-- commands/command.h | 7 ++++++- commands/command_edit.cpp | 38 ++++++++++++++++++++++++++++++++----- commands/command_edit.h | 9 ++++++++- core/equipment.c | 9 --------- core/equipment.h | 1 - qt-models/cylindermodel.cpp | 18 +++++++++++++++--- 7 files changed, 64 insertions(+), 22 deletions(-) diff --git a/commands/command.cpp b/commands/command.cpp index c173494c0..33c866e72 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -304,9 +304,9 @@ int removeCylinder(int index, bool currentDiveOnly) return execute_edit(new RemoveCylinder(index, currentDiveOnly)); } -int editCylinder(int index, cylinder_t cyl, bool currentDiveOnly) +int editCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentDiveOnly) { - return execute_edit(new EditCylinder(index, cyl, currentDiveOnly)); + return execute_edit(new EditCylinder(index, cyl, type, currentDiveOnly)); } // Trip editing related commands diff --git a/commands/command.h b/commands/command.h index 66122ac99..fc1ddf582 100644 --- a/commands/command.h +++ b/commands/command.h @@ -92,7 +92,12 @@ int removeWeight(int index, bool currentDiveOnly); int editWeight(int index, weightsystem_t ws, bool currentDiveOnly); int addCylinder(bool currentDiveOnly); int removeCylinder(int index, bool currentDiveOnly); -int editCylinder(int index, cylinder_t cyl, bool currentDiveOnly); +enum class EditCylinderType { + TYPE, + PRESSURE, + GASMIX +}; +int editCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentDiveOnly); #ifdef SUBSURFACE_MOBILE // Edits a dive and creates a divesite (if createDs != NULL) or edits a divesite (if changeDs != NULL). // Takes ownership of newDive and createDs! diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index d5e2d12d0..c81e67394 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -1151,9 +1151,23 @@ void RemoveCylinder::redo() } } +static int editCylinderTypeToFlags(EditCylinderType type) +{ + switch (type) { + default: + case EditCylinderType::TYPE: + return SAME_TYPE | SAME_SIZE; + case EditCylinderType::PRESSURE: + return SAME_PRESS; + case EditCylinderType::GASMIX: + return SAME_GAS; + } +} + // ***** Edit Cylinder ***** -EditCylinder::EditCylinder(int index, cylinder_t cylIn, bool currentDiveOnly) : - EditCylinderBase(index, currentDiveOnly, false, SAME_TYPE | SAME_PRESS | SAME_GAS) +EditCylinder::EditCylinder(int index, cylinder_t cylIn, EditCylinderType typeIn, bool currentDiveOnly) : + EditCylinderBase(index, currentDiveOnly, false, editCylinderTypeToFlags(typeIn)), + type(typeIn) { if (dives.empty()) return; @@ -1184,9 +1198,23 @@ EditCylinder::EditCylinder(int index, cylinder_t cylIn, bool currentDiveOnly) : // The base class copied the cylinders for us, let's edit them for (int i = 0; i < (int)indexes.size(); ++i) { - free_cylinder(cyl[i]); - cyl[i] = cylIn; - cyl[i].type.description = copy_qstring(description); + switch (type) { + case EditCylinderType::TYPE: + free((void *)cyl[i].type.description); + cyl[i].type = cylIn.type; + cyl[i].type.description = copy_qstring(description); + cyl[i].cylinder_use = cylIn.cylinder_use; + break; + case EditCylinderType::PRESSURE: + cyl[i].start.mbar = cylIn.start.mbar; + cyl[i].end.mbar = cylIn.end.mbar; + break; + case EditCylinderType::GASMIX: + cyl[i].gasmix = cylIn.gasmix; + cyl[i].bestmix_o2 = cylIn.bestmix_o2; + cyl[i].bestmix_he = cylIn.bestmix_he; + break; + } } } diff --git a/commands/command_edit.h b/commands/command_edit.h index d7c31f7f2..ab842a13c 100644 --- a/commands/command_edit.h +++ b/commands/command_edit.h @@ -5,6 +5,7 @@ #define COMMAND_EDIT_H #include "command_base.h" +#include "command.h" // for EditCylinderType #include "core/subsurface-qt/divelistnotifier.h" #include @@ -406,10 +407,16 @@ private: void redo() override; }; +// Instead of implementing an undo command for every single field in a cylinder, +// we only have one and pass an edit "type". We either edit the type, pressure +// or gasmix fields. This has mostly historical reasons rooted in the way the +// CylindersModel code works. The model works for undo and also in the planner +// without undo. Having a single undo-command simplifies the code there. class EditCylinder : public EditCylinderBase { public: - EditCylinder(int index, cylinder_t cyl, bool currentDiveOnly); // Clones cylinder + EditCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentDiveOnly); // Clones cylinder private: + EditCylinderType type; void undo() override; void redo() override; }; diff --git a/core/equipment.c b/core/equipment.c index 27d01692e..4be46ed1e 100644 --- a/core/equipment.c +++ b/core/equipment.c @@ -282,15 +282,6 @@ void remove_cylinder(struct dive *dive, int idx) remove_from_cylinder_table(&dive->cylinders, idx); } -// cyl is cloned. -void set_cylinder(struct dive *dive, int idx, cylinder_t cyl) -{ - if (idx < 0 || idx >= dive->cylinders.nr) - return; - free_cylinder(dive->cylinders.cylinders[idx]); - dive->cylinders.cylinders[idx] = clone_cylinder(cyl); -} - void remove_weightsystem(struct dive *dive, int idx) { remove_from_weightsystem_table(&dive->weightsystems, idx); diff --git a/core/equipment.h b/core/equipment.h index af410467f..081a13835 100644 --- a/core/equipment.h +++ b/core/equipment.h @@ -85,7 +85,6 @@ extern void add_cylinder_description(const cylinder_type_t *); extern void add_weightsystem_description(const weightsystem_t *); extern bool same_weightsystem(weightsystem_t w1, weightsystem_t w2); extern void remove_cylinder(struct dive *dive, int idx); -extern void set_cylinder(struct dive *dive, int idx, cylinder_t ws); extern void remove_weightsystem(struct dive *dive, int idx); extern void set_weightsystem(struct dive *dive, int idx, weightsystem_t ws); extern void reset_cylinders(struct dive *dive, bool track_gas); diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index 8a79ff1e5..c7e1b186d 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -296,7 +296,6 @@ cylinder_t *CylindersModel::cylinderAt(const QModelIndex &index) bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!d) return false; @@ -324,6 +323,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in tempCyl.type.description = strdup(qPrintable(type)); dataChanged(index, index); } + return true; } case SIZE: if (tempCyl.type.size.mliter != value.toInt()) { @@ -362,10 +362,12 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (index.column() != TYPE && !changed) return false; + Command::EditCylinderType type = Command::EditCylinderType::TYPE; switch (index.column()) { case TYPE: newType = qPrintable(vString); cyl.type.description = newType.c_str(); + type = Command::EditCylinderType::TYPE; break; case SIZE: { TankInfoModel *tanks = TankInfoModel::instance(); @@ -375,6 +377,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (!matches.isEmpty()) tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl.type.size.mliter); } + type = Command::EditCylinderType::TYPE; break; case WORKINGPRESS: { TankInfoModel *tanks = TankInfoModel::instance(); @@ -383,13 +386,16 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (!matches.isEmpty()) tanks->setData(tanks->index(matches.first().row(), TankInfoModel::BAR), cyl.type.workingpressure.mbar / 1000.0); } + type = Command::EditCylinderType::TYPE; break; case START: cyl.start = string_to_pressure(qPrintable(vString)); + type = Command::EditCylinderType::PRESSURE; break; case END: //if (!cyl->start.mbar || string_to_pressure(qPrintable(vString)).mbar <= cyl->start.mbar) { cyl.end = string_to_pressure(qPrintable(vString)); + type = Command::EditCylinderType::PRESSURE; break; case O2: { cyl.gasmix.o2 = string_to_fraction(qPrintable(vString)); @@ -405,6 +411,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in cyl.depth = gas_mod(cyl.gasmix, modpO2, d, M_OR_FT(3, 10)); cyl.bestmix_o2 = false; } + type = Command::EditCylinderType::GASMIX; break; case HE: cyl.gasmix.he = string_to_fraction(qPrintable(vString)); @@ -412,9 +419,11 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (get_o2(cyl.gasmix) + get_he(cyl.gasmix) > 1000) cyl.gasmix.o2.permille = 1000 - get_he(cyl.gasmix); cyl.bestmix_he = false; + type = Command::EditCylinderType::GASMIX; break; case DEPTH: cyl.depth = string_to_depth(qPrintable(vString)); + type = Command::EditCylinderType::GASMIX; break; case MOD: { if (QString::compare(qPrintable(vString), "*") == 0) { @@ -430,6 +439,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in modpO2.mbar = prefs.decopo2; cyl.depth = gas_mod(cyl.gasmix, modpO2, d, M_OR_FT(3, 10)); } + type = Command::EditCylinderType::GASMIX; break; case MND: if (QString::compare(qPrintable(vString), "*") == 0) { @@ -441,6 +451,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in // Calculate fHe for input depth cyl.gasmix.he = best_he(string_to_depth(qPrintable(vString)), d, prefs.o2narcotic, cyl.gasmix.o2); } + type = Command::EditCylinderType::GASMIX; break; case USE: { int use = vString.toInt(); @@ -448,6 +459,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in use = 0; cyl.cylinder_use = (enum cylinderuse)use; } + type = Command::EditCylinderType::TYPE; break; } @@ -461,7 +473,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in } else { #ifndef SUBSURFACE_MOBILE // On the EquipmentTab - place an editCylinder command. - Command::editCylinder(index.row(), cyl, false); + Command::editCylinder(index.row(), cyl, type, false); #endif } return true; @@ -709,7 +721,7 @@ void CylindersModel::commitTempCyl(int row) if (inPlanner) std::swap(*cyl, tempCyl); else - Command::editCylinder(tempRow, tempCyl, false); + Command::editCylinder(tempRow, tempCyl, Command::EditCylinderType::TYPE, false); } free_cylinder(tempCyl); tempRow = -1; From 63414fc82394031a9ce9c25d635430107fde19e0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 27 Mar 2020 21:49:19 +0100 Subject: [PATCH 71/78] undo: show multiple dive warning when editing equipment When editing cylinders or weights directly in the table widgets, no warning was shown if multiple dives were affected. To solve this, emit signals from the respective models and catch them in dive equipment tab. Not very nice, but it works for now. Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/TabDiveEquipment.cpp | 2 ++ desktop-widgets/tab-widgets/TabDiveEquipment.h | 2 +- qt-models/cylindermodel.cpp | 11 +++++++---- qt-models/cylindermodel.h | 3 +++ qt-models/weightmodel.cpp | 9 ++++++--- qt-models/weightmodel.h | 3 +++ 6 files changed, 22 insertions(+), 8 deletions(-) diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp index 13cfe77c4..78973b6c1 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp @@ -35,6 +35,8 @@ TabDiveEquipment::TabDiveEquipment(QWidget *parent) : TabBase(parent), connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &TabDiveEquipment::divesChanged); connect(ui.cylinders, &TableView::itemClicked, this, &TabDiveEquipment::editCylinderWidget); connect(ui.weights, &TableView::itemClicked, this, &TabDiveEquipment::editWeightWidget); + connect(cylindersModel->model(), &CylindersModel::divesEdited, this, &TabDiveEquipment::divesEdited); + connect(weightModel, &WeightModel::divesEdited, this, &TabDiveEquipment::divesEdited); // Current display of things on Gnome3 looks like shit, so // let's fix that. diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.h b/desktop-widgets/tab-widgets/TabDiveEquipment.h index 3576b449e..55eb21d1e 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.h +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.h @@ -21,7 +21,6 @@ public: ~TabDiveEquipment(); void updateData() override; void clear() override; - void divesEdited(int i); void closeWarning(); private slots: @@ -32,6 +31,7 @@ private slots: void editCylinderWidget(const QModelIndex &index); void editWeightWidget(const QModelIndex &index); void on_suit_editingFinished(); + void divesEdited(int count); private: Ui::TabDiveEquipment ui; diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index c7e1b186d..e519d347b 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -473,7 +473,8 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in } else { #ifndef SUBSURFACE_MOBILE // On the EquipmentTab - place an editCylinder command. - Command::editCylinder(index.row(), cyl, type, false); + int count = Command::editCylinder(index.row(), cyl, type, false); + emit divesEdited(count); #endif } return true; @@ -718,10 +719,12 @@ void CylindersModel::commitTempCyl(int row) return; // Only submit a command if the type changed if (!same_string(cyl->type.description, tempCyl.type.description) || gettextFromC::tr(cyl->type.description) != QString(tempCyl.type.description)) { - if (inPlanner) + if (inPlanner) { std::swap(*cyl, tempCyl); - else - Command::editCylinder(tempRow, tempCyl, Command::EditCylinderType::TYPE, false); + } else { + int count = Command::editCylinder(tempRow, tempCyl, Command::EditCylinderType::TYPE, false); + emit divesEdited(count); + } } free_cylinder(tempCyl); tempRow = -1; diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index 7b868b5b2..099a3beb2 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -51,6 +51,9 @@ public: bool updateBestMixes(); bool cylinderUsed(int i) const; +signals: + void divesEdited(int num); + public slots: void remove(QModelIndex index); diff --git a/qt-models/weightmodel.cpp b/qt-models/weightmodel.cpp index 6e56071e2..6b5f9e70d 100644 --- a/qt-models/weightmodel.cpp +++ b/qt-models/weightmodel.cpp @@ -112,8 +112,10 @@ void WeightModel::commitTempWS() return; // Only submit a command if the type changed weightsystem_t ws = d->weightsystems.weightsystems[tempRow]; - if (!same_string(ws.description, tempWS.description) || gettextFromC::tr(ws.description) != QString(tempWS.description)) - Command::editWeight(tempRow, tempWS, false); + if (!same_string(ws.description, tempWS.description) || gettextFromC::tr(ws.description) != QString(tempWS.description)) { + int count = Command::editWeight(tempRow, tempWS, false); + emit divesEdited(count); + } tempRow = -1; #endif } @@ -126,7 +128,8 @@ bool WeightModel::setData(const QModelIndex &index, const QVariant &value, int r switch (index.column()) { case WEIGHT: ws.weight = string_to_weight(qPrintable(vString)); - Command::editWeight(index.row(), ws, false); + int count = Command::editWeight(index.row(), ws, false); + emit divesEdited(count); return true; } return false; diff --git a/qt-models/weightmodel.h b/qt-models/weightmodel.h index 950e96d2b..b1df4fc8e 100644 --- a/qt-models/weightmodel.h +++ b/qt-models/weightmodel.h @@ -29,6 +29,9 @@ public: void updateDive(dive *d); weightsystem_t weightSystemAt(const QModelIndex &index) const; +signals: + void divesEdited(int num); + public slots: void weightsystemsReset(const QVector &dives); From 39bc6e3bddbb45bec8933723b4e9fc64a865236b Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 27 Mar 2020 21:53:32 +0100 Subject: [PATCH 72/78] cleanup: remove unneeded includes from TabDiveEquipment.cpp Signed-off-by: Berthold Stoeger --- desktop-widgets/tab-widgets/TabDiveEquipment.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp index 78973b6c1..9399d3871 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp @@ -1,18 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 #include "TabDiveEquipment.h" #include "maintab.h" -#include "desktop-widgets/mainwindow.h" // TODO: Only used temporarilly for edit mode changes #include "desktop-widgets/simplewidgets.h" // For isGnome3Session() #include "desktop-widgets/modeldelegates.h" #include "commands/command.h" -#include "profile-widget/profilewidget2.h" #include "qt-models/cylindermodel.h" #include "qt-models/weightmodel.h" -#include "core/subsurface-string.h" -#include "core/divelist.h" - #include #include From be7365755e8d194d839afbde55e3040489bf38b9 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 27 Mar 2020 22:19:50 +0100 Subject: [PATCH 73/78] cleanup: remove TankInfoDelegate::reenableReplot Update of the profile is now done by the undo-commands. If the planner needs this, it is probably better to connect directly to the model, not the delegate. Signed-off-by: Berthold Stoeger --- desktop-widgets/modeldelegates.cpp | 10 ---------- desktop-widgets/modeldelegates.h | 1 - 2 files changed, 11 deletions(-) diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index d7dd28b0e..f03008324 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -256,16 +256,6 @@ void TankInfoDelegate::setModelData(QWidget*, QAbstractItemModel*, const QModelI TankInfoDelegate::TankInfoDelegate(QObject *parent) : ComboBoxDelegate(TankInfoModel::instance(), parent, true) { - connect(this, SIGNAL(closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint)), - this, SLOT(reenableReplot(QWidget *, QAbstractItemDelegate::EndEditHint))); -} - -void TankInfoDelegate::reenableReplot(QWidget*, QAbstractItemDelegate::EndEditHint) -{ - MainWindow::instance()->graphics->setReplot(true); - // FIXME: We need to replot after a cylinder is selected but the replot below overwrites - // the newly selected cylinder. - // MainWindow::instance()->graphics->replot(); } void TankInfoDelegate::editorClosed(QWidget*, QAbstractItemDelegate::EndEditHint hint) diff --git a/desktop-widgets/modeldelegates.h b/desktop-widgets/modeldelegates.h index 277bf994e..d539c0384 100644 --- a/desktop-widgets/modeldelegates.h +++ b/desktop-widgets/modeldelegates.h @@ -61,7 +61,6 @@ public: public slots: void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint); - void reenableReplot(QWidget *widget, QAbstractItemDelegate::EndEditHint hint); }; class TankUseDelegate : public QStyledItemDelegate { From f9e246fed2433d137e6d30c95608440eadf18770 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 27 Mar 2020 22:33:17 +0100 Subject: [PATCH 74/78] cleanup: make static fields local to ComboBoxDelegate All combobox-delegates shared a number of static status fields. In a quest to make the code more reentrant, move that to the actual object. The fields have to be defined as mutable, since they are set in const member functions. Signed-off-by: Berthold Stoeger --- desktop-widgets/modeldelegates.cpp | 9 --------- desktop-widgets/modeldelegates.h | 8 ++++++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index f03008324..417ae2cdb 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -38,7 +38,6 @@ QSize DiveListDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex& // Gets the index of the model in the currentRow and column. // currCombo is defined below. #define IDX(_XX) mymodel->index(currCombo.currRow, (_XX)) -static bool keyboardFinished = false; StarWidgetsDelegate::StarWidgetsDelegate(QWidget *parent) : QStyledItemDelegate(parent), parentWidget(parent) @@ -101,14 +100,6 @@ void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) c->lineEdit()->setSelection(0, c->lineEdit()->text().length()); } -static struct CurrSelected { - QComboBox *comboEditor; - int currRow; - QString activeText; - QAbstractItemModel *model; - bool ignoreSelection; -} currCombo; - QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex &index) const { QComboBox *comboDelegate = new QComboBox(parent); diff --git a/desktop-widgets/modeldelegates.h b/desktop-widgets/modeldelegates.h index d539c0384..1b4c525d0 100644 --- a/desktop-widgets/modeldelegates.h +++ b/desktop-widgets/modeldelegates.h @@ -48,8 +48,16 @@ slots: virtual void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) = 0; private: bool editable; + mutable bool keyboardFinished; protected: QAbstractItemModel *model; + mutable struct CurrSelected { + QComboBox *comboEditor; + int currRow; + QString activeText; + QAbstractItemModel *model; + bool ignoreSelection; + } currCombo; }; class TankInfoDelegate : public ComboBoxDelegate { From e2f77f9238134c7d45944cb5cf62c2c15f202870 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 3 Apr 2020 14:25:43 +0200 Subject: [PATCH 75/78] undo: call invalidate_dive_cache() when editing cylinders Signed-off-by: Berthold Stoeger --- commands/command_edit.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index c81e67394..bec6cb99c 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -1033,6 +1033,7 @@ void AddCylinder::undo() continue; remove_cylinder(d, d->cylinders.nr - 1); emit diveListNotifier.cylinderRemoved(d, d->cylinders.nr); + invalidate_dive_cache(d); // Ensure that dive is written in git_save() } } @@ -1041,6 +1042,7 @@ void AddCylinder::redo() for (dive *d: dives) { add_cloned_cylinder(&d->cylinders, cyl); emit diveListNotifier.cylinderAdded(d, d->cylinders.nr - 1); + invalidate_dive_cache(d); // Ensure that dive is written in git_save() } } @@ -1138,6 +1140,7 @@ void RemoveCylinder::undo() std::vector mapping = get_cylinder_map_for_add(dives[i]->cylinders.nr, indexes[i]); add_to_cylinder_table(&dives[i]->cylinders, indexes[i], clone_cylinder(cyl[i])); emit diveListNotifier.cylinderAdded(dives[i], indexes[i]); + invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() } } @@ -1148,6 +1151,7 @@ void RemoveCylinder::redo() remove_cylinder(dives[i], indexes[i]); cylinder_renumber(dives[i], &mapping[0]); emit diveListNotifier.cylinderRemoved(dives[i], indexes[i]); + invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() } } @@ -1223,6 +1227,7 @@ void EditCylinder::redo() for (size_t i = 0; i < dives.size(); ++i) { std::swap(dives[i]->cylinders.cylinders[indexes[i]], cyl[i]); emit diveListNotifier.cylinderEdited(dives[i], indexes[i]); + invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() } } From 4d5f25ccf44eb3a55def84aac43aed344afe791f Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 3 Apr 2020 21:37:52 +0200 Subject: [PATCH 76/78] cleanup: remove conditional compilation in cylindermodel.cpp Parts of the code were not compiled on mobile, because they used the undo-command infrastructure. However, since mobile now also compiles that, we might as well remove the conditional compilation. Signed-off-by: Berthold Stoeger --- qt-models/cylindermodel.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index e519d347b..04abe2fdb 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -471,11 +471,9 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in free_cylinder(copy); dataChanged(index, index); } else { -#ifndef SUBSURFACE_MOBILE // On the EquipmentTab - place an editCylinder command. int count = Command::editCylinder(index.row(), cyl, type, false); emit divesEdited(count); -#endif } return true; } @@ -709,7 +707,6 @@ void CylindersModel::clearTempCyl() void CylindersModel::commitTempCyl(int row) { -#ifndef SUBSURFACE_MOBILE if (tempRow < 0) return; if (row != tempRow) @@ -728,7 +725,6 @@ void CylindersModel::commitTempCyl(int row) } free_cylinder(tempCyl); tempRow = -1; -#endif } CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent), From e5c4dee7f65e8f603ad5207ee6fd8a4de28321fe Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sat, 4 Apr 2020 19:05:25 +0200 Subject: [PATCH 77/78] delegates: call fixTabBehavior *before* closing the editor fixTabBehavior() set the editor text *after* closing the editor. This left us in an inconsistent state where we thought that the editor is active. By reversing two connects, this problem is resolved. Signed-off-by: Berthold Stoeger --- desktop-widgets/modeldelegates.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index 417ae2cdb..1908cc543 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -84,8 +84,8 @@ const QSize& StarWidgetsDelegate::starSize() const ComboBoxDelegate::ComboBoxDelegate(QAbstractItemModel *model, QObject *parent, bool allowEdit) : QStyledItemDelegate(parent), model(model) { editable = allowEdit; - connect(this, &ComboBoxDelegate::closeEditor, this, &ComboBoxDelegate::editorClosed); connect(this, &ComboBoxDelegate::closeEditor, this, &ComboBoxDelegate::fixTabBehavior); + connect(this, &ComboBoxDelegate::closeEditor, this, &ComboBoxDelegate::editorClosed); } void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const From 7dc04b4437c7aa1788f7d85a513a246ce502dea4 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sat, 4 Apr 2020 23:02:32 +0200 Subject: [PATCH 78/78] delegates: remove ComboBoxTemplate::fixTabBehavior The comment states that Qt treats TAB as cancel when in the combobox. However, testing shows that this use-case works without this hack. Since it caused weird behavior (the data was set *after* the editor was closed, leading to inconsistent state), remove it. Note: this overrides the previous commit, which is therefore redundant from a history point of view. However, I'll leave the previous commit in so that if something turns out to break, we can figure out which of the two changes it was. Signed-off-by: Berthold Stoeger --- desktop-widgets/modeldelegates.cpp | 16 +--------------- desktop-widgets/modeldelegates.h | 2 -- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index 1908cc543..5480a41db 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -84,7 +84,6 @@ const QSize& StarWidgetsDelegate::starSize() const ComboBoxDelegate::ComboBoxDelegate(QAbstractItemModel *model, QObject *parent, bool allowEdit) : QStyledItemDelegate(parent), model(model) { editable = allowEdit; - connect(this, &ComboBoxDelegate::closeEditor, this, &ComboBoxDelegate::fixTabBehavior); connect(this, &ComboBoxDelegate::closeEditor, this, &ComboBoxDelegate::editorClosed); } @@ -121,7 +120,6 @@ QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewI currCombo.currRow = index.row(); currCombo.model = const_cast(index.model()); currCombo.activeText = currCombo.model->data(index).toString(); - keyboardFinished = false; // Current display of things on Gnome3 looks like shit, so // let`s fix that. @@ -169,16 +167,6 @@ void ComboBoxDelegate::fakeActivation() QStyledItemDelegate::eventFilter(currCombo.comboEditor, &ev); } -// This 'reverts' the model data to what we actually choosed, -// becaus e a TAB is being understood by Qt as 'cancel' while -// we are on a QComboBox ( but not on a QLineEdit. -void ComboBoxDelegate::fixTabBehavior() -{ - if (keyboardFinished) { - setModelData(0, 0, QModelIndex()); - } -} - bool ComboBoxDelegate::eventFilter(QObject *object, QEvent *event) { // Reacts on Key_UP and Key_DOWN to show the QComboBox - list of choices. @@ -192,10 +180,8 @@ bool ComboBoxDelegate::eventFilter(QObject *object, QEvent *event) return true; } } - if (ev->key() == Qt::Key_Tab || ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) { + if (ev->key() == Qt::Key_Tab || ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) currCombo.activeText = currCombo.comboEditor->currentText(); - keyboardFinished = true; - } } else { // the 'Drop Down Menu' part. QKeyEvent *ev = static_cast(event); if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return || diff --git a/desktop-widgets/modeldelegates.h b/desktop-widgets/modeldelegates.h index 1b4c525d0..95e9c5fef 100644 --- a/desktop-widgets/modeldelegates.h +++ b/desktop-widgets/modeldelegates.h @@ -44,11 +44,9 @@ slots: void testActivation(const QModelIndex &currIndex); //HACK: try to get rid of this in the future. void fakeActivation(); - void fixTabBehavior(); virtual void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) = 0; private: bool editable; - mutable bool keyboardFinished; protected: QAbstractItemModel *model; mutable struct CurrSelected {