Merge pull request #2643 from bstoeger/cylinder4

First steps of cylinder-editing undo
This commit is contained in:
Dirk Hohndel 2020-04-11 11:03:05 -07:00 committed by GitHub
commit 6d187b5f4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1335 additions and 795 deletions

View file

@ -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

View file

@ -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})

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

210
commands/command_event.cpp Normal file
View file

@ -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

104
commands/command_event.h Normal file
View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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)
{

View file

@ -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

View file

@ -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);

View file

@ -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 */

View file

@ -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++)

View file

@ -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;
}

View file

@ -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);

View file

@ -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

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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 {

View file

@ -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()

View file

@ -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;
};

View file

@ -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

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -31,8 +31,4 @@ private:
QStringList headers;
};
/* Has the string value changed */
#define CHANGED() \
(vString = value.toString()) != data(index, role).toString()
#endif

View file

@ -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;
cylinder_t *cyl = cylinderAt(index);
if (!cyl)
if (!d)
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
int row = index.row();
if (row < 0 || row >= d->cylinders.nr)
return false;
// Here we handle a few cases that allow us to set / commit / revert
// 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;
}
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;
}
}
break;
case SIZE:
if (CHANGED()) {
TankInfoModel *tanks = TankInfoModel::instance();
QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description);
QString vString = value.toString();
bool changed = vString != data(index, role).toString();
cyl->type.size = string_to_volume(qPrintable(vString), cyl->type.workingpressure);
mark_divelist_changed(true);
if (!matches.isEmpty())
tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl->type.size.mliter);
changed = true;
}
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:
newType = qPrintable(vString);
cyl.type.description = newType.c_str();
type = Command::EditCylinderType::TYPE;
break;
case WORKINGPRESS:
if (CHANGED()) {
case SIZE: {
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.size = string_to_volume(qPrintable(vString), cyl.type.workingpressure);
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::ML), cyl.type.size.mliter);
}
type = Command::EditCylinderType::TYPE;
break;
case WORKINGPRESS: {
TankInfoModel *tanks = TankInfoModel::instance();
QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl.type.description);
cyl.type.workingpressure = string_to_pressure(qPrintable(vString));
if (!matches.isEmpty())
tanks->setData(tanks->index(matches.first().row(), TankInfoModel::BAR), cyl.type.workingpressure.mbar / 1000.0);
}
type = Command::EditCylinderType::TYPE;
break;
case START:
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());
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]);
}
cylinder_renumber(&displayed_dive, &mapping[0]);
if (in_planner())
DivePlannerPointsModel::instance()->cylinderRenumber(&mapping[0]);
changed = true;
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,24 +671,71 @@ 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..
updateDive();
// And update the model (the actual change was already performed in the backend)..
beginResetModel();
endResetModel();
}
CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent)
// Save the cylinder in the given row so that we can revert if the user cancels a type-editing action.
void CylindersModel::initTempCyl(int row)
{
if (!d || tempRow == row)
return;
clearTempCyl();
const cylinder_t *cyl = get_cylinder(d, row);
if (!cyl)
return;
tempRow = row;
tempCyl = clone_cylinder(*cyl);
dataChanged(index(row, TYPE), index(row, USE));
}
void CylindersModel::clearTempCyl()
{
if (tempRow < 0)
return;
int oldRow = tempRow;
tempRow = -1;
free_cylinder(tempCyl);
dataChanged(index(oldRow, TYPE), index(oldRow, USE));
}
void CylindersModel::commitTempCyl(int row)
{
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;
}
CylindersModelFiltered::CylindersModelFiltered(QObject *parent) : QSortFilterProxyModel(parent),
source(false) // Currently, only the EquipmentTab uses the filtered model.
{
setSourceModel(&source);
}
void CylindersModelFiltered::updateDive()
void CylindersModelFiltered::updateDive(dive *d)
{
source.updateDive();
source.updateDive(d);
}
void CylindersModelFiltered::clear()
@ -632,21 +743,11 @@ void CylindersModelFiltered::clear()
source.clear();
}
void CylindersModelFiltered::add()
{
source.add();
}
CylindersModel *CylindersModelFiltered::model()
{
return &source;
}
void CylindersModelFiltered::remove(QModelIndex index)
{
source.remove(mapToSource(index));
}
bool CylindersModelFiltered::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
return prefs.display_unused_tanks || source.cylinderUsed(source_row);

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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);