Desktop: Fix Gas Editing for Manually Added Dives.

- show the correct gasmix in the profile;
- make gases available for gas switches in the profile after they have
  been added;
- persist gas changes;
- add air as a default gas when adding a dive.

This still has problems when undoing a gas switch - instead of
completely removing the gas switch it is just moved to the next point in the
profile.

Signed-off-by: Michael Keller <github@ike.ch>
This commit is contained in:
Michael Keller 2024-05-25 19:03:39 +12:00
parent 9243921cbb
commit f65afaf5d2
13 changed files with 121 additions and 54 deletions

View file

@ -100,6 +100,7 @@ enum class EditProfileType {
ADD,
REMOVE,
MOVE,
EDIT,
};
void replanDive(dive *d); // dive computer(s) and cylinder(s) of first argument will be consumed!
void editProfile(const dive *d, int dcNr, EditProfileType type, int count);

View file

@ -879,6 +879,7 @@ QString editProfileTypeToString(EditProfileType type, int count)
case EditProfileType::ADD: return Command::Base::tr("Add stop");
case EditProfileType::REMOVE: return Command::Base::tr("Remove %n stop(s)", "", count);
case EditProfileType::MOVE: return Command::Base::tr("Move %n stop(s)", "", count);
case EditProfileType::EDIT: return Command::Base::tr("Edit stop");
}
}
@ -904,7 +905,7 @@ EditProfile::EditProfile(const dive *source, int dcNr, EditProfileType type, int
copy_samples(sdc, &dc);
copy_events(sdc, &dc);
setText(editProfileTypeToString(type, count) + diveNumberOrDate(d));
setText(editProfileTypeToString(type, count) + " " + diveNumberOrDate(d));
}
EditProfile::~EditProfile()
@ -925,6 +926,7 @@ void EditProfile::undo()
std::swap(sdc->samples, dc.samples);
std::swap(sdc->alloc_samples, dc.alloc_samples);
std::swap(sdc->sample, dc.sample);
std::swap(sdc->events, dc.events);
std::swap(sdc->maxdepth, dc.maxdepth);
std::swap(d->maxdepth, maxdepth);
std::swap(d->meandepth, meandepth);
@ -1125,7 +1127,7 @@ AddCylinder::AddCylinder(bool currentDiveOnly) :
setText(Command::Base::tr("Add cylinder"));
else
setText(Command::Base::tr("Add cylinder (%n dive(s))", "", dives.size()));
cyl = create_new_cylinder(dives[0]);
cyl = create_new_manual_cylinder(dives[0]);
indexes.reserve(dives.size());
}

View file

@ -503,12 +503,38 @@ 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;
}
static bool show_cylinder(const struct dive *d, int i)
cylinder_t create_new_manual_cylinder(const struct dive *d)
{
cylinder_t cyl = create_new_cylinder(d);
cyl.manually_added = true;
return cyl;
}
void add_default_cylinder(struct dive *d)
{
// Only add if there are no cylinders yet
if (d->cylinders.nr > 0)
return;
cylinder_t cyl;
if (!empty_string(prefs.default_cylinder)) {
cyl = create_new_cylinder(d);
} else {
cyl = empty_cylinder;
// roughly an AL80
cyl.type.description = strdup(translate("gettextFromC", "unknown"));
cyl.type.size.mliter = 11100;
cyl.type.workingpressure.mbar = 207000;
}
add_cylinder(&d->cylinders, 0, cyl);
reset_cylinders(d, false);
}
static bool show_cylinder(const struct dive *d, int i)
{
if (is_cylinder_used(d, i))
return true;

View file

@ -93,7 +93,8 @@ 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. */
extern cylinder_t create_new_manual_cylinder(const struct dive *dive); /* dive is needed to fill out MOD, which depends on salinity. */
extern void add_default_cylinder(struct dive *dive);
extern int first_hidden_cylinder(const struct dive *d);
#ifdef DEBUG_CYL
extern void dump_cylinders(struct dive *dive, bool verbose);

View file

@ -711,6 +711,7 @@ void MainWindow::on_actionAddDive_triggered()
d.dc.meandepth.mm = M_OR_FT(13, 39); // this creates a resonable looking safety stop
make_manually_added_dive_dc(&d.dc);
fake_dc(&d.dc);
add_default_cylinder(&d);
fixup_dive(&d);
Command::addDive(&d, divelog.autogroup, true);

View file

@ -52,7 +52,7 @@ void EmptyView::resizeEvent(QResizeEvent *)
update();
}
ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placingCommand(false)
ProfileWidget::ProfileWidget() : d(nullptr), dc(0), placingCommand(false)
{
ui.setupUi(this);
@ -122,9 +122,13 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placi
connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &ProfileWidget::divesChanged);
connect(&diveListNotifier, &DiveListNotifier::settingsChanged, view.get(), &ProfileWidget2::settingsChanged);
connect(&diveListNotifier, &DiveListNotifier::cylinderAdded, this, &ProfileWidget::cylindersChanged);
connect(&diveListNotifier, &DiveListNotifier::cylinderRemoved, this, &ProfileWidget::cylindersChanged);
connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &ProfileWidget::cylindersChanged);
connect(view.get(), &ProfileWidget2::stopAdded, this, &ProfileWidget::stopAdded);
connect(view.get(), &ProfileWidget2::stopRemoved, this, &ProfileWidget::stopRemoved);
connect(view.get(), &ProfileWidget2::stopMoved, this, &ProfileWidget::stopMoved);
connect(view.get(), &ProfileWidget2::stopEdited, this, &ProfileWidget::stopEdited);
ui.profCalcAllTissues->setChecked(qPrefTechnicalDetails::calcalltissues());
ui.profCalcCeiling->setChecked(qPrefTechnicalDetails::calcceiling());
@ -192,6 +196,10 @@ void ProfileWidget::plotCurrentDive()
void ProfileWidget::plotDive(dive *dIn, int dcIn)
{
bool endEditMode = false;
if (editedDive && (dIn != d || dcIn != dc))
endEditMode = true;
d = dIn;
if (dcIn >= 0)
@ -202,7 +210,7 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn)
dc = std::min(dc, (int)number_of_computers(current_dive) - 1);
// Exit edit mode if the dive changed
if (editedDive && (originalDive != d || editedDc != dc))
if (endEditMode)
exitEditMode();
// If this is a manually added dive and we are not in the planner
@ -216,7 +224,7 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn)
setEnabledToolbar(d != nullptr);
if (editedDive) {
view->plotDive(editedDive.get(), editedDc);
view->plotDive(editedDive.get(), dc);
setDive(editedDive.get(), dc);
} else if (d) {
view->setProfileState(d, dc);
@ -256,22 +264,42 @@ void ProfileWidget::rotateDC(int dir)
void ProfileWidget::divesChanged(const QVector<dive *> &dives, DiveField field)
{
// If the current dive is not in list of changed dives, do nothing.
// Only if duration or depth changed, the profile needs to be replotted.
// Also, if we are currently placing a command, don't do anything.
// Note that we cannot use Command::placingCommand(), because placing
// a depth or time change on the maintab requires an update.
if (!d || !dives.contains(d) || !(field.duration || field.depth) || placingCommand)
return;
// If were editing the current dive and not currently
// If we're editing the current dive and not currently
// placing command, we have to update the edited dive.
if (editedDive) {
copy_dive(d, editedDive.get());
// TODO: Holy moly that function sends too many signals. Fix it!
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), editedDc);
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
}
plotCurrentDive();
// Only if duration or depth changed, the profile needs to be replotted.
if (field.duration || field.depth)
plotCurrentDive();
}
void ProfileWidget::cylindersChanged(struct dive *changed, int pos)
{
// If the current dive is not in list of changed dives, do nothing.
// Only if duration or depth changed, the profile needs to be replotted.
// Also, if we are currently placing a command, don't do anything.
// Note that we cannot use Command::placingCommand(), because placing
// a depth or time change on the maintab requires an update.
if (!d || changed != d || !editedDive)
return;
// If we're editing the current dive we have to update the
// cylinders of the edited dive.
if (editedDive) {
copy_cylinders(&d->cylinders, &editedDive.get()->cylinders);
// TODO: Holy moly that function sends too many signals. Fix it!
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
}
}
void ProfileWidget::setPlanState(const struct dive *d, int dcNr)
@ -297,22 +325,20 @@ void ProfileWidget::unsetProfTissues()
void ProfileWidget::editDive()
{
editedDive.reset(alloc_dive());
editedDc = dc;
copy_dive(d, editedDive.get()); // Work on a copy of the dive
originalDive = d;
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD);
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), editedDc);
view->setEditState(editedDive.get(), editedDc);
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::EDIT);
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
view->setEditState(editedDive.get(), dc);
}
void ProfileWidget::exitEditMode()
{
if (!editedDive)
return;
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING);
view->setProfileState(d, dc); // switch back to original dive before erasing the copy.
editedDive.reset();
originalDive = nullptr;
}
// Update depths of edited dive
@ -338,25 +364,36 @@ void ProfileWidget::stopAdded()
{
if (!editedDive)
return;
calcDepth(*editedDive, editedDc);
calcDepth(*editedDive, dc);
Setter s(placingCommand, true);
Command::editProfile(editedDive.get(), editedDc, Command::EditProfileType::ADD, 0);
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::ADD, 0);
}
void ProfileWidget::stopRemoved(int count)
{
if (!editedDive)
return;
calcDepth(*editedDive, editedDc);
calcDepth(*editedDive, dc);
Setter s(placingCommand, true);
Command::editProfile(editedDive.get(), editedDc, Command::EditProfileType::REMOVE, count);
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::REMOVE, count);
}
void ProfileWidget::stopMoved(int count)
{
if (!editedDive)
return;
calcDepth(*editedDive, editedDc);
calcDepth(*editedDive, dc);
Setter s(placingCommand, true);
Command::editProfile(editedDive.get(), editedDc, Command::EditProfileType::MOVE, count);
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::MOVE, count);
}
void ProfileWidget::stopEdited()
{
if (!editedDive)
return;
copy_events(get_dive_dc(editedDive.get(), dc), get_dive_dc(d, dc));
Setter s(placingCommand, true);
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::EDIT, 0);
}

View file

@ -34,11 +34,13 @@ public:
private
slots:
void divesChanged(const QVector<dive *> &dives, DiveField field);
void cylindersChanged(struct dive *changed, int pos);
void unsetProfHR();
void unsetProfTissues();
void stopAdded();
void stopRemoved(int count);
void stopMoved(int count);
void stopEdited();
private:
std::unique_ptr<EmptyView> emptyView;
std::vector<QAction *> toolbarActions;
@ -49,8 +51,6 @@ private:
void exitEditMode();
void rotateDC(int dir);
OwningDivePtr editedDive;
int editedDc;
dive *originalDive;
bool placingCommand;
};

View file

@ -62,10 +62,10 @@ void DiveHandler::selfRemove()
void DiveHandler::changeGas()
{
ProfileWidget2 *view = qobject_cast<ProfileWidget2 *>(scene()->views().first());
QAction *action = qobject_cast<QAction *>(sender());
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS);
plannerModel->gasChange(index.sibling(index.row() + 1, index.column()), action->data().toInt());
view->changeGas(parentIndex(), action->data().toInt());
}
void DiveHandler::mouseMoveEvent(QGraphicsSceneMouseEvent *event)

View file

@ -565,7 +565,7 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
for (int i = 0; i < d->cylinders.nr; i++) {
const cylinder_t *cylinder = get_cylinder(d, i);
QString label = printCylinderDescription(i, cylinder);
gasChange->addAction(label, [this, i, eventTime] { changeGas(i, eventTime); });
gasChange->addAction(label, [this, i, eventTime] { addGasSwitch(i, eventTime); });
}
} else if (d && d->cylinders.nr > 1) {
// if we have more than one gas, offer to switch to another one
@ -573,7 +573,7 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
for (int i = 0; i < d->cylinders.nr; i++) {
const cylinder_t *cylinder = get_cylinder(d, i);
QString label = printCylinderDescription(i, cylinder);
gasChange->addAction(label, [this, i, seconds] { changeGas(i, seconds); });
gasChange->addAction(label, [this, i, seconds] { addGasSwitch(i, seconds); });
}
}
m.addAction(tr("Add setpoint change"), [this, seconds]() { ProfileWidget2::addSetpointChange(seconds); });
@ -759,16 +759,25 @@ void ProfileWidget2::splitDive(int seconds)
Command::splitDives(mutable_dive(), duration_t{ seconds });
}
void ProfileWidget2::changeGas(int tank, int seconds)
void ProfileWidget2::addGasSwitch(int tank, int seconds)
{
if (!d || tank < 0 || tank >= d->cylinders.nr)
return;
Command::addGasSwitch(mutable_dive(), dc, seconds, tank);
}
#endif
#ifndef SUBSURFACE_MOBILE
void ProfileWidget2::changeGas(int index, int newCylinderId)
{
if ((currentState == PLAN || currentState == EDIT) && plannerModel) {
QModelIndex modelIndex = plannerModel->index(index, DivePlannerPointsModel::GAS);
plannerModel->gasChange(modelIndex.sibling(modelIndex.row() + 1, modelIndex.column()), newCylinderId);
if (currentState == EDIT)
emit stopEdited();
}
}
void ProfileWidget2::editName(DiveEventItem *item)
{
struct event *event = item->getEventMutable();

View file

@ -68,6 +68,7 @@ signals:
void stopAdded(); // only emitted in edit mode
void stopRemoved(int count); // only emitted in edit mode
void stopMoved(int count); // only emitted in edit mode
void stopEdited(); // only emitted in edit mode
public
slots: // Necessary to call from QAction's signals.
@ -111,7 +112,8 @@ private:
void replot();
void setZoom(int level);
void changeGas(int tank, int seconds);
void addGasSwitch(int tank, int seconds);
void changeGas(int index, int newCylinderId);
void setupSceneAndFlags();
void addItemsToScene();
void setupItemOnScene();

View file

@ -499,7 +499,7 @@ void CylindersModel::add()
if (!d)
return;
int row = d->cylinders.nr;
cylinder_t cyl = create_new_cylinder(d);
cylinder_t cyl = create_new_manual_cylinder(d);
beginInsertRows(QModelIndex(), row, row);
add_cylinder(&d->cylinders, row, cyl);
++numRows;

View file

@ -206,20 +206,8 @@ void DivePlannerPointsModel::setupCylinders()
return; // We have at least one cylinder
}
}
if (!empty_string(prefs.default_cylinder)) {
cylinder_t cyl = empty_cylinder;
fill_default_cylinder(d, &cyl);
cyl.start = cyl.type.workingpressure;
add_cylinder(&d->cylinders, 0, cyl);
} else {
cylinder_t cyl = empty_cylinder;
// roughly an AL80
cyl.type.description = copy_qstring(tr("unknown"));
cyl.type.size.mliter = 11100;
cyl.type.workingpressure.mbar = 207000;
add_cylinder(&d->cylinders, 0, cyl);
}
reset_cylinders(d, false);
add_default_cylinder(d);
cylinders.updateDive(d, dcNr);
}
@ -1288,7 +1276,7 @@ void DivePlannerPointsModel::computeVariationsDone(QString variations)
emit calculatedPlanNotes(QString(d->notes));
}
void DivePlannerPointsModel::createPlan(bool replanCopy)
void DivePlannerPointsModel::createPlan(bool saveAsNew)
{
// Ok, so, here the diveplan creates a dive
deco_state_cache cache;
@ -1350,7 +1338,7 @@ void DivePlannerPointsModel::createPlan(bool replanCopy)
#endif // !SUBSURFACE_TESTING
} else {
copy_events_until(current_dive, d, dcNr, preserved_until.seconds);
if (replanCopy) {
if (saveAsNew) {
// we were planning an old dive and save as a new dive
d->id = dive_getUniqID(); // Things will break horribly if we create dives with the same id.
#if !defined(SUBSURFACE_TESTING)

View file

@ -23,12 +23,12 @@ public:
GAS,
CCSETPOINT,
DIVEMODE,
COLUMNS
COLUMNS,
};
enum Mode {
NOTHING,
PLAN,
ADD
EDIT,
};
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;