summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--commands/CMakeLists.txt2
-rw-r--r--commands/command.cpp48
-rw-r--r--commands/command.h17
-rw-r--r--commands/command_base.h9
-rw-r--r--commands/command_edit.cpp238
-rw-r--r--commands/command_edit.h45
-rw-r--r--commands/command_event.cpp210
-rw-r--r--commands/command_event.h104
-rw-r--r--core/dive.c96
-rw-r--r--core/dive.h15
-rw-r--r--core/equipment.c61
-rw-r--r--core/equipment.h5
-rw-r--r--core/parse-xml.c25
-rw-r--r--core/planner.c33
-rw-r--r--core/profile.c13
-rw-r--r--core/qthelper.cpp33
-rw-r--r--core/qthelper.h2
-rw-r--r--core/subsurface-qt/divelistnotifier.h6
-rw-r--r--desktop-widgets/mainwindow.cpp50
-rw-r--r--desktop-widgets/mainwindow.h3
-rw-r--r--desktop-widgets/modeldelegates.cpp70
-rw-r--r--desktop-widgets/modeldelegates.h9
-rw-r--r--desktop-widgets/simplewidgets.cpp32
-rw-r--r--desktop-widgets/simplewidgets.h7
-rw-r--r--desktop-widgets/tab-widgets/TabDiveEquipment.cpp105
-rw-r--r--desktop-widgets/tab-widgets/TabDiveEquipment.h4
-rw-r--r--desktop-widgets/tab-widgets/maintab.cpp123
-rw-r--r--desktop-widgets/tab-widgets/maintab.h12
-rw-r--r--profile-widget/profilewidget2.cpp198
-rw-r--r--profile-widget/profilewidget2.h23
-rw-r--r--qt-models/cleanertablemodel.h4
-rw-r--r--qt-models/cylindermodel.cpp461
-rw-r--r--qt-models/cylindermodel.h31
-rw-r--r--qt-models/diveplannermodel.cpp9
-rw-r--r--qt-models/weightmodel.cpp9
-rw-r--r--qt-models/weightmodel.h3
37 files changed, 1328 insertions, 788 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 861db1680..e03303496 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,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/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 1fb968778..33c866e72 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 {
@@ -293,6 +294,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, EditCylinderType type, bool currentDiveOnly)
+{
+ return execute_edit(new EditCylinder(index, cyl, type, currentDiveOnly));
+}
+
// Trip editing related commands
void editTripLocation(dive_trip *trip, const QString &s)
{
@@ -311,4 +327,36 @@ 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));
+}
+
+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));
+}
+
+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));
+}
+
+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 e19d093cb..fc1ddf582 100644
--- a/commands/command.h
+++ b/commands/command.h
@@ -90,6 +90,14 @@ 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);
+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!
@@ -101,6 +109,15 @@ 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);
+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
#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<dive, DiveDeleter> OwningDivePtr;
typedef std::unique_ptr<dive_trip, TripDeleter> OwningTripPtr;
typedef std::unique_ptr<dive_site, DiveSiteDeleter> OwningDiveSitePtr;
+typedef std::unique_ptr<event, EventDeleter> OwningEventPtr;
// This is the base class of all commands.
// It defines the Qt-translation functions
@@ -182,4 +186,3 @@ QString getListOfDives(QVector<struct dive *> dives);
} // namespace Command
#endif // COMMAND_BASE_H
-
diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp
index a5eb46bc1..0e1159f6d 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
@@ -946,6 +947,7 @@ bool EditWeightBase::workToBeDone()
return !dives.empty();
}
+// ***** Remove Weight *****
RemoveWeight::RemoveWeight(int index, bool currentDiveOnly) :
EditWeightBase(index, currentDiveOnly)
{
@@ -972,6 +974,7 @@ void RemoveWeight::redo()
}
}
+// ***** Edit Weight *****
EditWeight::EditWeight(int index, weightsystem_t wsIn, bool currentDiveOnly) :
EditWeightBase(index, currentDiveOnly),
new_ws(empty_weightsystem)
@@ -1028,6 +1031,241 @@ 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);
+ invalidate_dive_cache(d); // Ensure that dive is written in git_save()
+ }
+}
+
+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()
+ }
+}
+
+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_with_flags(cyl, d->cylinders.cylinders[idx], sameCylinderFlags))
+ return idx;
+ }
+ return -1;
+}
+
+EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProtectedOnly, int sameCylinderFlags) :
+ EditDivesBase(currentDiveOnly)
+{
+ // Get the old cylinder, bail if index is invalid
+ if (!current || index < 0 || index >= current->cylinders.nr) {
+ dives.clear();
+ return;
+ }
+ const cylinder_t &orig = current->cylinders.cylinders[index];
+
+ std::vector<dive *> divesNew;
+ divesNew.reserve(dives.size());
+ indexes.reserve(dives.size());
+ cyl.reserve(dives.size());
+
+ for (dive *d: dives) {
+ 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()
+{
+ for (cylinder_t c: cyl)
+ free_cylinder(c);
+}
+
+bool EditCylinderBase::workToBeDone()
+{
+ return !dives.empty();
+}
+
+// ***** Remove Cylinder *****
+RemoveCylinder::RemoveCylinder(int index, bool currentDiveOnly) :
+ EditCylinderBase(index, currentDiveOnly, true, SAME_TYPE | SAME_PRESS | SAME_GAS)
+{
+ 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) {
+ std::vector<int> 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()
+ }
+}
+
+void RemoveCylinder::redo()
+{
+ for (size_t i = 0; i < dives.size(); ++i) {
+ std::vector<int> 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]);
+ invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save()
+ }
+}
+
+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, EditCylinderType typeIn, bool currentDiveOnly) :
+ EditCylinderBase(index, currentDiveOnly, false, editCylinderTypeToFlags(typeIn)),
+ type(typeIn)
+{
+ 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
+ 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) == description) {
+ description = tank_info[i].name;
+ break;
+ }
+ }
+
+ // Update the tank info model
+ TankInfoModel *tim = TankInfoModel::instance();
+ QModelIndexList matches = tim->match(tim->index(0, 0), Qt::DisplayRole, gettextFromC::tr(cylIn.type.description));
+ if (!matches.isEmpty()) {
+ 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);
+ }
+
+ // The base class copied the cylinders for us, let's edit them
+ for (int i = 0; i < (int)indexes.size(); ++i) {
+ 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;
+ }
+ }
+}
+
+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()
+ }
+}
+
+// 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..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 <QVector>
@@ -377,6 +378,49 @@ 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, bool nonProtectedOnly, int sameCylinderFlags);
+ ~EditCylinderBase();
+
+ std::vector<cylinder_t> cyl;
+ std::vector<int> 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;
+};
+
+// 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, EditCylinderType type, bool currentDiveOnly); // Clones cylinder
+private:
+ EditCylinderType type;
+ 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 +450,4 @@ private:
#endif // SUBSURFACE_MOBILE
} // namespace Command
-
#endif
diff --git a/commands/command_event.cpp b/commands/command_event.cpp
new file mode 100644
index 000000000..391dfe0a9
--- /dev/null
+++ b/commands/command_event.cpp
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#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"
+#include <QVector>
+
+namespace Command {
+
+EventBase::EventBase(struct dive *dIn, int dcNrIn) :
+ d(dIn), dcNr(dcNrIn)
+{
+}
+
+void EventBase::redo()
+{
+ redoit(); // Call actual function in base class
+ 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),
+ eventToAdd(ev)
+{
+}
+
+bool AddEventBase::workToBeDone()
+{
+ return true;
+}
+
+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
+}
+
+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;
+}
+
+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"));
+}
+
+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])));
+}
+
+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.
+}
+
+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();
+}
+
+RemoveEvent::RemoveEvent(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr),
+ 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));
+}
+
+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
+}
+
+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<dive *>{ 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.
+ // 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<OwningEventPtr> newEventsToAdd;
+ std::vector<event *> 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<dive *>{ 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
new file mode 100644
index 000000000..b7e67f218
--- /dev/null
+++ b/commands/command_event.h
@@ -0,0 +1,104 @@
+// 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 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;
+private:
+ void updateDive();
+};
+
+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
+};
+
+class AddEventBookmark : public AddEventBase {
+public:
+ AddEventBookmark(struct dive *d, int dcNr, int seconds);
+};
+
+class AddEventDivemodeSwitch : public AddEventBase {
+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);
+};
+
+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
+};
+
+class RemoveEvent : public EventBase {
+public:
+ RemoveEvent(struct dive *d, int dcNr, struct event *ev);
+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 {
+public:
+ AddGasSwitch(struct dive *d, int dcNr, int seconds, int tank);
+private:
+ bool workToBeDone() override;
+ void undoit() override;
+ void redoit() override;
+
+ std::vector<int> cylinders; // cylinders that are modified
+ std::vector<OwningEventPtr> eventsToAdd;
+ std::vector<event *> eventsToRemove;
+};
+
+} // namespace Command
+
+#endif // COMMAND_EVENT_H
diff --git a/core/dive.c b/core/dive.c
index c97c229f6..df5d7951d 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"
@@ -126,10 +127,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;
@@ -164,18 +165,85 @@ struct event *add_event(struct divecomputer *dc, unsigned int time, int type, in
break;
}
+ 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);
+}
+
+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;
}
-static int same_event(const struct event *a, const struct event *b)
+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;
+ }
+ 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! */
+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;
@@ -188,19 +256,15 @@ 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)
{
- struct event **ep = &current_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;
+ for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) {
+ if (*ep == event) {
+ *ep = event->next;
+ event->next = NULL; // For good measure.
+ break;
+ }
}
}
diff --git a/core/dive.h b/core/dive.h
index 13de360ed..0d2dbc2a6 100644
--- a/core/dive.h
+++ b/core/dive.h
@@ -358,10 +358,15 @@ 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 *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);
+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(struct event *event);
+extern void remove_event_from_dc(struct divecomputer *dc, 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);
@@ -372,15 +377,9 @@ 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);
-#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/equipment.c b/core/equipment.c
index c9c0539c3..4be46ed1e 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;
@@ -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)
@@ -151,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))
@@ -377,6 +374,46 @@ 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 = lrint(prefs.modpO2 * 1000.0)};
+
+ 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;
+ 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 b62587447..081a13835 100644
--- a/core/equipment.h
+++ b/core/equipment.h
@@ -75,6 +75,8 @@ 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 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);
@@ -82,13 +84,14 @@ 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 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);
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);
#endif
diff --git a/core/parse-xml.c b/core/parse-xml.c
index 490e56275..50639d1d1 100644
--- a/core/parse-xml.c
+++ b/core/parse-xml.c
@@ -698,31 +698,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);
diff --git a/core/planner.c b/core/planner.c
index 0e26a9a57..a5f0d36bc 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)
@@ -315,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/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++)
diff --git a/core/qthelper.cpp b/core/qthelper.cpp
index eb755bf20..91b3a3ee7 100644
--- a/core/qthelper.cpp
+++ b/core/qthelper.cpp
@@ -1662,3 +1662,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<int> 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<int> 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<int> get_cylinder_map_for_add(int count, int n)
+{
+ std::vector<int> 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<QPair<QString, int>> selectedDivesGasUsed();
QString getUserAgent();
QString printGPSCoords(const location_t *loc);
+std::vector<int> get_cylinder_map_for_remove(int count, int n);
+std::vector<int> get_cylinder_map_for_add(int count, int n);
extern QString (*changesCallback)();
void uiNotification(const QString &msg);
diff --git a/core/subsurface-qt/divelistnotifier.h b/core/subsurface-qt/divelistnotifier.h
index aafe29c0c..63355fbcc 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<dive *> &dives);
void cylindersReset(const QVector<dive *> &dives);
+ void cylinderAdded(dive *d, int pos);
+ void cylinderRemoved(dive *d, int pos);
+ void cylinderEdited(dive *d, int pos);
void weightsystemsReset(const QVector<dive *> &dives);
void weightAdded(dive *d, int pos);
void weightRemoved(dive *d, int pos);
@@ -110,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/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp
index 8474eee71..1c122a4fd 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));
@@ -918,7 +922,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()
@@ -1088,40 +1092,6 @@ void MainWindow::on_actionViewAll_triggered()
ui.bottomSplitter->setCollapsible(1,false);
}
-void MainWindow::enterEditState()
-{
- undoAction->setEnabled(false);
- redoAction->setEnabled(false);
- 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<int> 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()
-{
- undoAction->setEnabled(true);
- redoAction->setEnabled(true);
- 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;
}
}
@@ -1793,20 +1761,18 @@ 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);
+ 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);
- } else {
- setApplicationState(ApplicationState::EditDive);
mainTab->enableEdition();
}
}
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> mainTab;
diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp
index ed7278b4c..5480a41db 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)
@@ -86,7 +85,6 @@ ComboBoxDelegate::ComboBoxDelegate(QAbstractItemModel *model, QObject *parent, b
{
editable = allowEdit;
connect(this, &ComboBoxDelegate::closeEditor, this, &ComboBoxDelegate::editorClosed);
- connect(this, &ComboBoxDelegate::closeEditor, this, &ComboBoxDelegate::fixTabBehavior);
}
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
@@ -101,14 +99,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);
@@ -129,7 +119,7 @@ QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
currCombo.comboEditor = comboDelegate;
currCombo.currRow = index.row();
currCombo.model = const_cast<QAbstractItemModel *>(index.model());
- keyboardFinished = false;
+ currCombo.activeText = currCombo.model->data(index).toString();
// Current display of things on Gnome3 looks like shit, so
// let`s fix that.
@@ -177,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.
@@ -200,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<QKeyEvent *>(event);
if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return ||
@@ -230,12 +208,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,46 +226,30 @@ 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)
{
- 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)
{
- 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<QString>();
- currCylinderData.pressure = model->data(model->index(row, CylindersModel::WORKINGPRESS_INT)).value<int>();
- currCylinderData.size = model->data(model->index(row, CylindersModel::SIZE_INT)).value<int>();
MainWindow::instance()->graphics->setReplot(false);
return delegate;
}
diff --git a/desktop-widgets/modeldelegates.h b/desktop-widgets/modeldelegates.h
index 277bf994e..95e9c5fef 100644
--- a/desktop-widgets/modeldelegates.h
+++ b/desktop-widgets/modeldelegates.h
@@ -44,12 +44,18 @@ 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;
protected:
QAbstractItemModel *model;
+ mutable struct CurrSelected {
+ QComboBox *comboEditor;
+ int currRow;
+ QString activeText;
+ QAbstractItemModel *model;
+ bool ignoreSelection;
+ } currCombo;
};
class TankInfoDelegate : public ComboBoxDelegate {
@@ -61,7 +67,6 @@ public:
public
slots:
void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint);
- void reenableReplot(QWidget *widget, QAbstractItemDelegate::EndEditHint hint);
};
class TankUseDelegate : public QStyledItemDelegate {
diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp
index fb4d3750b..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
{
@@ -174,38 +173,21 @@ 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) {
- add_event(dc, 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(QWidget *parent) : QDialog(parent),
- dc(0), time(0)
+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, 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()
diff --git a/desktop-widgets/simplewidgets.h b/desktop-widgets/simplewidgets.h
index 99cfc0fa2..421695815 100644
--- a/desktop-widgets/simplewidgets.h
+++ b/desktop-widgets/simplewidgets.h
@@ -65,16 +65,15 @@ private:
class SetpointDialog : public QDialog {
Q_OBJECT
public:
- static SetpointDialog *instance();
- void setpointData(struct divecomputer *divecomputer, int time);
+ SetpointDialog(struct dive *d, int dcNr, int time);
private
slots:
void buttonClicked(QAbstractButton *button);
private:
- explicit SetpointDialog(QWidget *parent);
Ui::SetpointDialog ui;
- struct divecomputer *dc;
+ struct dive *d;
+ int dcNr;
int time;
};
diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp
index 33a8e5a3c..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 <QSettings>
#include <QCompleter>
@@ -33,9 +28,10 @@ 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);
+ 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.
@@ -129,7 +125,7 @@ void TabDiveEquipment::toggleTriggeredColumn()
void TabDiveEquipment::updateData()
{
- cylindersModel->updateDive();
+ cylindersModel->updateDive(current_dive);
weightModel->updateDive(current_dive);
suitModel.updateModel();
@@ -153,8 +149,7 @@ void TabDiveEquipment::clear()
void TabDiveEquipment::addCylinder_clicked()
{
- MainWindow::instance()->mainTab->enableEdition();
- cylindersModel->add();
+ divesEdited(Command::addCylinder(false));
}
void TabDiveEquipment::addWeight_clicked()
@@ -164,14 +159,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)
@@ -185,87 +179,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<dive *> getSelectedDivesCurrentLast()
-{
- QVector<dive *> 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<dive *> 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 = &current_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();
- 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..55eb21d1e 100644
--- a/desktop-widgets/tab-widgets/TabDiveEquipment.h
+++ b/desktop-widgets/tab-widgets/TabDiveEquipment.h
@@ -21,9 +21,6 @@ public:
~TabDiveEquipment();
void updateData() override;
void clear() override;
- void acceptChanges();
- void rejectChanges();
- void divesEdited(int i);
void closeWarning();
private slots:
@@ -34,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/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp
index 252e135a6..7fde2e1b6 100644
--- a/desktop-widgets/tab-widgets/maintab.cpp
+++ b/desktop-widgets/tab-widgets/maintab.cpp
@@ -49,7 +49,8 @@ struct Completers {
};
MainTab::MainTab(QWidget *parent) : QTabWidget(parent),
- editMode(NONE),
+ editMode(false),
+ ignoreInput(false),
lastSelectedDive(true),
lastTabSelectedDive(0),
lastTabSelectedDiveTrip(0),
@@ -206,41 +207,19 @@ 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)
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);
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);
- if (amount_selected > 1) {
- displayMessage(tr("Multiple dives are being edited."));
- } else {
- displayMessage(tr("This dive is being edited."));
- }
- editMode = newEditMode != NONE ? newEditMode : DIVE;
+ displayMessage(tr("This dive is being edited."));
+
+ editMode = true;
}
// This function gets called if a field gets updated by an undo command.
@@ -305,7 +284,7 @@ void MainTab::nextInputField(QKeyEvent *event)
bool MainTab::isEditing()
{
- return editMode != NONE;
+ return editMode;
}
static bool isHtml(const QString &s)
@@ -363,9 +342,8 @@ 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())
+ 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.
@@ -375,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();
@@ -393,9 +371,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);
@@ -434,11 +409,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);
@@ -506,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;
@@ -529,22 +499,17 @@ void MainTab::acceptChanges()
if (ui.location->hasFocus())
stealFocus();
- EditMode lastMode = editMode;
- editMode = IGNORE_MODE;
+ ignoreInput = true;
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) {
+ if (editMode) {
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) {
MainWindow::instance()->diveList->reload();
MainWindow::instance()->refreshDisplay();
MainWindow::instance()->graphics->replot();
@@ -554,39 +519,15 @@ 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;
-}
-
-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;
+ ignoreInput = false;
+ editMode = false;
}
void MainTab::rejectChanges()
{
- EditMode lastMode = editMode;
-
- if (lastMode != NONE && current_dive && !cylinders_equal(current_dive, &displayed_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) {
@@ -594,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();
@@ -606,17 +547,9 @@ 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);
// show the profile and dive info
MainWindow::instance()->graphics->replot();
MainWindow::instance()->setEnabledToolbar(true);
- MainWindow::instance()->exitEditState();
ui.editDiveSiteButton->setEnabled(!ui.location->text().isEmpty());
}
@@ -632,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));
@@ -640,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));
@@ -648,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.
@@ -657,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.
@@ -677,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);
@@ -687,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);
@@ -697,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));
@@ -705,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();
@@ -743,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));
@@ -760,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 6411f6b3a..49003be4e 100644
--- a/desktop-widgets/tab-widgets/maintab.h
+++ b/desktop-widgets/tab-widgets/maintab.h
@@ -26,13 +26,6 @@ class TabBase;
class MainTab : public QTabWidget {
Q_OBJECT
public:
- enum EditMode {
- NONE,
- DIVE,
- MANUALLY_ADDED_DIVE,
- IGNORE_MODE
- };
-
MainTab(QWidget *parent = 0);
~MainTab();
void clearTabs();
@@ -72,11 +65,12 @@ slots:
void closeMessage();
void closeWarning();
void displayMessage(QString str);
- void enableEdition(EditMode newEditMode = NONE);
+ void enableEdition();
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;
TagCompletionModel tagModel;
diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp
index fb5680eb5..8fbd00b49 100644
--- a/profile-widget/profilewidget2.cpp
+++ b/profile-widget/profilewidget2.cpp
@@ -30,6 +30,7 @@
#include "core/qthelper.h"
#include "core/gettextfromc.h"
#include "core/imagedownloader.h"
+#include "core/subsurface-qt/divelistnotifier.h"
#endif
#include <libdivecomputer/parser.h>
@@ -170,6 +171,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::profileChanged);
+ connect(&diveListNotifier, &DiveListNotifier::eventsChanged, this, &ProfileWidget2::profileChanged);
#endif // SUBSURFACE_MOBILE
#if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE)
@@ -1449,69 +1452,37 @@ 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); });
}
- 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"));
- 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());
- 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());
- 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());
- 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<DiveEventItem *>(sceneItem)) {
- action = new QAction(&m);
- action->setText(tr("Remove event"));
- action->setData(QVariant::fromValue<void *>(item)); // so we know what to remove.
- connect(action, SIGNAL(triggered(bool)), this, SLOT(removeEvent()));
- m.addAction(action);
- action = new QAction(&m);
- action->setText(tr("Hide similar events"));
- action->setData(QVariant::fromValue<void *>(item));
- connect(action, SIGNAL(triggered(bool)), this, SLOT(hideEvents()));
- 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"));
- action->setData(QVariant::fromValue<void *>(item));
- connect(action, SIGNAL(triggered(bool)), this, SLOT(editName()));
- 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);
@@ -1561,10 +1532,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());
}
@@ -1583,10 +1552,8 @@ void ProfileWidget2::makeFirstDC()
Command::moveDiveComputerToFront(current_dive, dc_number);
}
-void ProfileWidget2::hideEvents()
+void ProfileWidget2::hideEvents(DiveEventItem *item)
{
- QAction *action = qobject_cast<QAction *>(sender());
- DiveEventItem *item = static_cast<DiveEventItem *>(action->data().value<void *>());
struct event *event = item->getEvent();
if (QMessageBox::question(this,
@@ -1618,67 +1585,59 @@ void ProfileWidget2::unhideEvents()
item->show();
}
-void ProfileWidget2::removeEvent()
+// 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)
{
- QAction *action = qobject_cast<QAction *>(sender());
- DiveEventItem *item = static_cast<DiveEventItem *>(action->data().value<void *>());
- struct event *event = item->getEvent();
+ 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 = 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()
+void ProfileWidget2::addBookmark(int seconds)
{
- QAction *action = qobject_cast<QAction *>(sender());
- QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint()));
- add_event(current_dc, lrint(timeAxis->valueAt(scenePos)), 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()
+void ProfileWidget2::addDivemodeSwitch(int seconds, int divemode)
{
- int i;
- QAction *action = qobject_cast<QAction *>(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)), 8, 0, i,
- 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()
+void ProfileWidget2::addSetpointChange(int seconds)
{
- QAction *action = qobject_cast<QAction *>(sender());
- QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint()));
- SetpointDialog::instance()->setpointData(current_dc, lrint(timeAxis->valueAt(scenePos)));
- SetpointDialog::instance()->show();
+ SetpointDialog dialog(current_dive, dc_number, seconds);
+ dialog.exec();
}
-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<QAction *>(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
}
@@ -1687,26 +1646,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
@@ -1752,11 +1692,11 @@ double ProfileWidget2::getFontPrintScale()
}
#ifndef SUBSURFACE_MOBILE
-void ProfileWidget2::editName()
+void ProfileWidget2::editName(DiveEventItem *item)
{
- QAction *action = qobject_cast<QAction *>(sender());
- DiveEventItem *item = static_cast<DiveEventItem *>(action->data().value<void *>());
- 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,
@@ -1768,14 +1708,7 @@ void ProfileWidget2::editName()
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
@@ -2245,6 +2178,13 @@ void ProfileWidget2::removePictures(const QVector<QString> &fileUrls)
calculatePictureYPositions();
}
+void ProfileWidget2::profileChanged(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..4f60bf885 100644
--- a/profile-widget/profilewidget2.h
+++ b/profile-widget/profilewidget2.h
@@ -115,20 +115,10 @@ slots: // Necessary to call from QAction's signals.
void removePictures(const QVector<QString> &fileUrls);
void setPlanState();
void setAddState();
- void addSetpointChange();
- void splitDive();
- void addBookmark();
- void addDivemodeSwitch();
- void hideEvents();
- void unhideEvents();
- void removeEvent();
- void editName();
- 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);
+ void profileChanged(dive *d);
/* this is called for every move on the handlers. maybe we can speed up this a bit? */
void recreatePlannedDive();
@@ -174,6 +164,17 @@ private:
const double *thresholdSettingsMin, const double *thresholdSettingsMax);
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);
+ 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;
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 da8b2eb1e..04abe2fdb 100644
--- a/qt-models/cylindermodel.cpp
+++ b/qt-models/cylindermodel.cpp
@@ -2,29 +2,34 @@
#include "cylindermodel.h"
#include "tankinfomodel.h"
#include "models.h"
+#include "commands/command.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"
#include "core/subsurface-qt/divelistnotifier.h"
#include "core/subsurface-string.h"
+#include <string>
-CylindersModel::CylindersModel(QObject *parent) :
- CleanerTableModel(parent),
- changed(false),
- rows(0)
+CylindersModel::CylindersModel(bool planner, QObject *parent) : CleanerTableModel(parent),
+ d(nullptr),
+ inPlanner(planner),
+ 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%")
<< tr("Deco switch at") <<tr("Bot. MOD") <<tr("MND") << tr("Use"));
connect(&diveListNotifier, &DiveListNotifier::cylindersReset, this, &CylindersModel::cylindersReset);
+ connect(&diveListNotifier, &DiveListNotifier::cylinderAdded, this, &CylindersModel::cylinderAdded);
+ connect(&diveListNotifier, &DiveListNotifier::cylinderRemoved, this, &CylindersModel::cylinderRemoved);
+ connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &CylindersModel::cylinderEdited);
}
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);
@@ -126,13 +131,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 +152,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() >= d->cylinders.nr)
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 = index.row() == tempRow ? &tempCyl : get_cylinder(d, index.row());
switch (role) {
case Qt::BackgroundRole: {
@@ -227,13 +231,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]);
@@ -246,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(&displayed_dive, index.row()))) {
+ if ((inPlanner && DivePlannerPointsModel::instance()->tankInUse(index.row())) ||
+ (!inPlanner && is_cylinder_prot(d, index.row()))) {
return trashForbiddenIcon();
}
return trashIcon();
@@ -256,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(&displayed_dive, 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.");
@@ -285,197 +289,227 @@ 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)
+ int row = index.row();
+ if (row < 0 || row >= d->cylinders.nr)
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);
+ }
+ return true;
+ }
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();
+ 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;
+
+ Command::EditCylinderType type = Command::EditCylinderType::TYPE;
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));
- changed = true;
- }
- }
+ case TYPE:
+ newType = qPrintable(vString);
+ cyl.type.description = newType.c_str();
+ type = Command::EditCylinderType::TYPE;
break;
- case SIZE:
- if (CHANGED()) {
+ 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);
- mark_divelist_changed(true);
+ 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);
- changed = true;
+ tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl.type.size.mliter);
}
+ type = Command::EditCylinderType::TYPE;
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));
+ 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);
- changed = true;
+ tanks->setData(tanks->index(matches.first().row(), TankInfoModel::BAR), cyl.type.workingpressure.mbar / 1000.0);
}
+ type = Command::EditCylinderType::TYPE;
break;
case START:
- if (CHANGED()) {
- cyl->start = string_to_pressure(qPrintable(vString));
- changed = true;
- }
+ cyl.start = string_to_pressure(qPrintable(vString));
+ type = Command::EditCylinderType::PRESSURE;
break;
case END:
- if (CHANGED()) {
- //&& (!cyl->start.mbar || string_to_pressure(qPrintable(vString)).mbar <= cyl->start.mbar)) {
- cyl->end = string_to_pressure(qPrintable(vString));
- changed = true;
- }
+ //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:
- if (CHANGED()) {
- cyl->gasmix.o2 = string_to_fraction(qPrintable(vString));
+ 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)
- 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 (displayed_dive.dc.divemode == PSCR)
- modpO2.mbar = prefs.decopo2 + (1000 - get_o2(cyl->gasmix)) * SURFACE_PRESSURE *
+ 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->bestmix_o2 = false;
- changed = true;
+ cyl.depth = gas_mod(cyl.gasmix, modpO2, d, M_OR_FT(3, 10));
+ cyl.bestmix_o2 = false;
}
+ type = Command::EditCylinderType::GASMIX;
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;
- changed = true;
- }
+ 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;
+ type = Command::EditCylinderType::GASMIX;
break;
case DEPTH:
- if (CHANGED()) {
- cyl->depth = string_to_depth(qPrintable(vString));
- changed = true;
- }
+ cyl.depth = string_to_depth(qPrintable(vString));
+ type = Command::EditCylinderType::GASMIX;
break;
- case MOD:
- if (CHANGED()) {
+ 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(displayed_dive.maxdepth, &displayed_dive);
+ 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)), &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));
- changed = true;
+ cyl.depth = gas_mod(cyl.gasmix, modpO2, d, M_OR_FT(3, 10));
}
+ type = Command::EditCylinderType::GASMIX;
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(displayed_dive.maxdepth, &displayed_dive, 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);
- }
- changed = true;
+ 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);
}
+ type = Command::EditCylinderType::GASMIX;
break;
- case USE:
- if (CHANGED()) {
+ case USE: {
int use = vString.toInt();
if (use > NUM_GAS_USE - 1 || use < 0)
use = 0;
- cyl->cylinder_use = (enum cylinderuse)use;
- changed = true;
+ cyl.cylinder_use = (enum cylinderuse)use;
}
+ type = Command::EditCylinderType::TYPE;
break;
}
- dataChanged(index, index);
+
+ 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);
+ std::swap(copy, d->cylinders.cylinders[row]);
+ free_cylinder(copy);
+ dataChanged(index, index);
+ } else {
+ // On the EquipmentTab - place an editCylinder command.
+ int count = Command::editCylinder(index.row(), cyl, type, false);
+ emit divesEdited(count);
+ }
return true;
}
int CylindersModel::rowCount(const QModelIndex&) const
{
- return rows;
+ return d ? d->cylinders.nr : 0;
}
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;
+ if (!d)
+ return;
+ int row = d->cylinders.nr;
+ cylinder_t cyl = create_new_cylinder(d);
beginInsertRows(QModelIndex(), row, row);
- add_to_cylinder_table(&displayed_dive.cylinders, row, cyl);
- rows++;
- changed = true;
+ add_to_cylinder_table(&d->cylinders, row, cyl);
endInsertRows();
emit dataChanged(createIndex(row, 0), createIndex(row, COLUMNS - 1));
}
void CylindersModel::clear()
{
- if (rows > 0) {
- beginRemoveRows(QModelIndex(), 0, rows - 1);
- endRemoveRows();
- }
+ beginResetModel();
+ d = nullptr;
+ endResetModel();
}
-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;
endResetModel();
}
@@ -486,15 +520,19 @@ 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)
+ return;
if (index.column() == USE) {
cylinder_t *cyl = cylinderAt(index);
if (cyl->cylinder_use == OC_GAS)
cyl->cylinder_use = NOT_USED;
else if (cyl->cylinder_use == NOT_USED)
cyl->cylinder_use = OC_GAS;
- changed = true;
dataChanged(index, index);
return;
}
@@ -502,94 +540,120 @@ void CylindersModel::remove(QModelIndex index)
if (index.column() != REMOVE)
return;
- if ((in_planner() && DivePlannerPointsModel::instance()->tankInUse(index.row())) ||
- (!in_planner() && is_cylinder_prot(&displayed_dive, index.row())))
+ if (DivePlannerPointsModel::instance()->tankInUse(index.row()))
return;
beginRemoveRows(QModelIndex(), index.row(), index.row());
- rows--;
- remove_cylinder(&displayed_dive, index.row());
- changed = true;
+ remove_cylinder(d, index.row());
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<int> mapping(displayed_dive.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]);
- if (in_planner())
- DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]);
- changed = true;
+ std::vector<int> mapping = get_cylinder_map_for_remove(d->cylinders.nr + 1, index.row());
+ cylinder_renumber(d, &mapping[0]);
+ DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]);
+}
+
+void CylindersModel::cylinderAdded(struct dive *changed, int pos)
+{
+ if (d != changed)
+ return;
+
+ // The row was already inserted by the undo command. Just inform the model.
+ beginInsertRows(QModelIndex(), pos, pos);
+ endInsertRows();
+}
+
+void CylindersModel::cylinderRemoved(struct dive *changed, int pos)
+{
+ if (d != changed)
+ return;
+
+ // The row was already deleted by the undo command. Just inform the model.
+ beginRemoveRows(QModelIndex(), pos, pos);
+ endRemoveRows();
+}
+
+void CylindersModel::cylinderEdited(struct dive *changed, int pos)
+{
+ if (d != changed)
+ return;
+
+ dataChanged(index(pos, TYPE), index(pos, USE));
}
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<int> mapping(displayed_dive.cylinders.nr);
+ std::vector<int> 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]);
- if (in_planner())
+ cylinder_renumber(d, &mapping[0]);
+ if (inPlanner)
DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]);
- changed = true;
endMoveRows();
}
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);
@@ -599,7 +663,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;
}
@@ -607,44 +671,81 @@ void CylindersModel::cylindersReset(const QVector<dive *> &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(&current_dive->cylinders, &displayed_dive.cylinders);
+ // And update the model (the actual change was already performed in the backend)..
+ beginResetModel();
+ 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;
- // And update the model..
- updateDive();
+ tempRow = row;
+ tempCyl = clone_cylinder(*cyl);
+
+ dataChanged(index(row, TYPE), index(row, USE));
}
-CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent)
+void CylindersModel::clearTempCyl()
{
- setSourceModel(&source);
+ if (tempRow < 0)
+ return;
+ int oldRow = tempRow;
+ tempRow = -1;
+ free_cylinder(tempCyl);
+ dataChanged(index(oldRow, TYPE), index(oldRow, USE));
}
-void CylindersModelFiltered::updateDive()
+void CylindersModel::commitTempCyl(int row)
{
- source.updateDive();
+ 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 (inPlanner) {
+ std::swap(*cyl, tempCyl);
+ } else {
+ int count = Command::editCylinder(tempRow, tempCyl, Command::EditCylinderType::TYPE, false);
+ emit divesEdited(count);
+ }
+ }
+ free_cylinder(tempCyl);
+ tempRow = -1;
}
-void CylindersModelFiltered::clear()
+CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent),
+ source(false) // Currently, only the EquipmentTab uses the filtered model.
{
- source.clear();
+ setSourceModel(&source);
}
-void CylindersModelFiltered::add()
+void CylindersModelFiltered::updateDive(dive *d)
{
- source.add();
+ source.updateDive(d);
}
-CylindersModel *CylindersModelFiltered::model()
+void CylindersModelFiltered::clear()
{
- return &source;
+ source.clear();
}
-void CylindersModelFiltered::remove(QModelIndex index)
+CylindersModel *CylindersModelFiltered::model()
{
- source.remove(mapToSource(index));
+ return &source;
}
bool CylindersModelFiltered::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h
index 5cfe72d2d..099a3beb2 100644
--- a/qt-models/cylindermodel.h
+++ b/qt-models/cylindermodel.h
@@ -31,9 +31,11 @@ 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);
+ 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;
@@ -41,23 +43,36 @@ public:
void add();
void clear();
- void updateDive();
+ void updateDive(dive *d);
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;
+signals:
+ void divesEdited(int num);
+
public
slots:
void remove(QModelIndex index);
void cylindersReset(const QVector<dive *> &dives);
+ void cylinderAdded(dive *d, int pos);
+ void cylinderRemoved(dive *d, int pos);
+ void cylinderEdited(dive *d, int pos);
private:
- int rows;
+ dive *d;
+ bool inPlanner;
+ // 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
@@ -68,11 +83,7 @@ public:
CylindersModel *model(); // Access to unfiltered base model
void clear();
- void add();
- void updateDive();
-public
-slots:
- void remove(QModelIndex index);
+ void updateDive(dive *d);
private:
CylindersModel source;
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
diff --git a/qt-models/diveplannermodel.cpp b/qt-models/diveplannermodel.cpp
index df7affe8b..09d08c79a 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
@@ -415,6 +415,7 @@ int DivePlannerPointsModel::rowCount(const QModelIndex&) const
}
DivePlannerPointsModel::DivePlannerPointsModel(QObject *parent) : QAbstractTableModel(parent),
+ cylinders(true),
mode(NOTHING),
recalc(false)
{
@@ -908,7 +909,7 @@ void DivePlannerPointsModel::clear()
{
bool oldRecalc = setRecalc(false);
- cylinders.updateDive();
+ cylinders.updateDive(&displayed_dive);
if (rowCount() > 0) {
beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
divepoints.clear();
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<dive *> &dives);