From 9b4263aa87bafef7e1f8ef3b827fbd106dccae6b Mon Sep 17 00:00:00 2001 From: Michael Andreen Date: Sat, 19 Feb 2022 18:47:25 +0100 Subject: [PATCH] Allow editing sensors through equipment tab Add a column to the equipment table that shows if a sensor is attached to a tank, or which sensors would be available to attach to a tank that currently doesn't have a pressure sensor associated with it. Changing the sensor assignement can be undone. This column is hidden by default as this is a somewhat unusual activity. Signed-off-by: Michael Andreen Signed-off-by: Dirk Hohndel --- commands/command.cpp | 5 ++ commands/command.h | 1 + commands/command_edit.cpp | 38 +++++++++++++ commands/command_edit.h | 16 ++++++ core/subsurface-qt/divelistnotifier.h | 2 + desktop-widgets/diveplanner.cpp | 1 + .../tab-widgets/TabDiveEquipment.cpp | 12 +++- profile-widget/profilewidget2.cpp | 1 + qt-models/cylindermodel.cpp | 56 ++++++++++++++++++- qt-models/cylindermodel.h | 1 + 10 files changed, 130 insertions(+), 3 deletions(-) diff --git a/commands/command.cpp b/commands/command.cpp index 1772f9063..2d1cee60c 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -314,6 +314,11 @@ int editCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentD return execute_edit(new EditCylinder(index, cyl, type, currentDiveOnly)); } +void editSensors(int toCylinder, const int fromCylinder) +{ + execute(new EditSensors(toCylinder, fromCylinder)); +} + // Trip editing related commands void editTripLocation(dive_trip *trip, const QString &s) { diff --git a/commands/command.h b/commands/command.h index d5e22b135..ffd371aec 100644 --- a/commands/command.h +++ b/commands/command.h @@ -111,6 +111,7 @@ enum class EditCylinderType { GASMIX }; int editCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentDiveOnly); +void editSensors(int toCylinder, const int fromCylinder); #ifdef SUBSURFACE_MOBILE // Edits a dive and creates a divesite (if createDs != NULL) or edits a divesite (if changeDs != NULL). // Takes ownership of newDive and createDs! diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index fce031d76..6adfdf945 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -4,6 +4,7 @@ #include "core/divelist.h" #include "core/fulltext.h" #include "core/qthelper.h" // for copy_qstring +#include "core/sample.h" #include "core/selection.h" #include "core/subsurface-string.h" #include "core/tag.h" @@ -1360,6 +1361,43 @@ void EditCylinder::undo() redo(); } +EditSensors::EditSensors(int toCylinderIn, int fromCylinderIn) + : dc(current_dc), d(current_dive), toCylinder(toCylinderIn), fromCylinder(fromCylinderIn) +{ + if (!d || !dc) + return; + + setText(Command::Base::tr("Edit sensors")); + +} + +void EditSensors::mapSensors(int toCyl, int fromCyl) +{ + for (int i = 0; i < dc->samples; ++i) { + for (int s = 0; s < MAX_SENSORS; ++s) { + if (dc->sample[i].pressure[s].mbar && dc->sample[i].sensor[s] == fromCyl) + dc->sample[i].sensor[s] = toCyl; + } + } + emit diveListNotifier.diveComputerEdited(dc); + invalidate_dive_cache(d); // Ensure that dive is written in git_save() +} + +void EditSensors::undo() +{ + mapSensors(fromCylinder, toCylinder); +} + +void EditSensors::redo() +{ + mapSensors(toCylinder, fromCylinder); +} + +bool EditSensors::workToBeDone() +{ + return d && dc; +} + #ifdef SUBSURFACE_MOBILE EditDive::EditDive(dive *oldDiveIn, dive *newDiveIn, dive_site *createDs, dive_site *editDs, location_t dsLocationIn) diff --git a/commands/command_edit.h b/commands/command_edit.h index d2a2cb7db..32f053754 100644 --- a/commands/command_edit.h +++ b/commands/command_edit.h @@ -440,6 +440,22 @@ private: void redo() override; }; +class EditSensors : public Base +{ +public: + EditSensors(int cylIndex, int fromCylinder); + +private: + struct divecomputer *dc; + struct dive *d; + int toCylinder; + int fromCylinder; + void mapSensors(int toCyl, int fromCyl); + void undo() override; + void redo() override; + bool workToBeDone() 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. diff --git a/core/subsurface-qt/divelistnotifier.h b/core/subsurface-qt/divelistnotifier.h index f8c814440..f39ea156b 100644 --- a/core/subsurface-qt/divelistnotifier.h +++ b/core/subsurface-qt/divelistnotifier.h @@ -96,6 +96,8 @@ signals: void divesTimeChanged(timestamp_t delta, const QVector &dives); void divesImported(); // A general signal when multiple dives have been imported. + void diveComputerEdited(divecomputer *dc); + void cylindersReset(const QVector &dives); void cylinderAdded(dive *d, int pos); void cylinderRemoved(dive *d, int pos); diff --git a/desktop-widgets/diveplanner.cpp b/desktop-widgets/diveplanner.cpp index e5d55f0d2..9cdb3754d 100644 --- a/desktop-widgets/diveplanner.cpp +++ b/desktop-widgets/diveplanner.cpp @@ -49,6 +49,7 @@ DivePlannerWidget::DivePlannerWidget(QWidget *parent) : QWidget(parent, QFlag(0) view->setColumnHidden(CylindersModel::DEPTH, false); view->setColumnHidden(CylindersModel::WORKINGPRESS_INT, true); view->setColumnHidden(CylindersModel::SIZE_INT, true); + view->setColumnHidden(CylindersModel::SENSORS, true); view->setItemDelegateForColumn(CylindersModel::TYPE, new TankInfoDelegate(this)); connect(ui.cylinderTableWidget, &TableView::addButtonClicked, plannerModel, &DivePlannerPointsModel::addCylinder_clicked); connect(ui.tableWidget, &TableView::addButtonClicked, plannerModel, &DivePlannerPointsModel::addDefaultStop); diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp index 6c912ae02..84b159c8f 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp @@ -20,6 +20,15 @@ static bool ignoreHiddenFlag(int i) i == CylindersModel::WORKINGPRESS_INT || i == CylindersModel::SIZE_INT; } +static bool hiddenByDefault(int i) +{ + switch (i) { + case CylindersModel::SENSORS: + return true; + } + return false; +} + TabDiveEquipment::TabDiveEquipment(QWidget *parent) : TabBase(parent), cylindersModel(new CylindersModel(false, true, this)), weightModel(new WeightModel(this)) @@ -81,7 +90,8 @@ TabDiveEquipment::TabDiveEquipment(QWidget *parent) : TabBase(parent), for (int i = 0; i < CylindersModel::COLUMNS; i++) { if (ignoreHiddenFlag(i)) continue; - bool checked = s.value(QString("column%1_hidden").arg(i)).toBool(); + auto setting = s.value(QString("column%1_hidden").arg(i)); + bool checked = setting.isValid() ? setting.toBool() : hiddenByDefault(i) ; QAction *action = new QAction(cylindersModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(), ui.cylinders->view()); action->setCheckable(true); action->setData(i); diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 3f2cbdeee..43c245c8a 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -77,6 +77,7 @@ ProfileWidget2::ProfileWidget2(DivePlannerPointsModel *plannerModelIn, double dp connect(&diveListNotifier, &DiveListNotifier::pictureOffsetChanged, this, &ProfileWidget2::pictureOffsetChanged); connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &ProfileWidget2::divesChanged); connect(&diveListNotifier, &DiveListNotifier::deviceEdited, this, &ProfileWidget2::replot); + connect(&diveListNotifier, &DiveListNotifier::diveComputerEdited, this, &ProfileWidget2::replot); #endif // SUBSURFACE_MOBILE #if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE) diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index a8083e971..580dd2dd4 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -7,6 +7,7 @@ #include "core/color.h" #include "qt-models/diveplannermodel.h" #include "core/gettextfromc.h" +#include "core/sample.h" #include "core/subsurface-qt/divelistnotifier.h" #include "core/subsurface-string.h" #include @@ -19,9 +20,10 @@ CylindersModel::CylindersModel(bool planner, bool hideUnused, QObject *parent) : tempRow(-1), tempCyl(empty_cylinder) { - // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH, MOD, MND, USE, IS_USED, WORKINGPRESS_INT, SIZE_INT}; + // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH, MOD, MND, USE, WORKINGPRESS_INT, SIZE_INT, SENSORS}; setHeaderDataStrings(QStringList() << "" << tr("Type") << tr("Size") << tr("Work press.") << tr("Start press.") << tr("End press.") << tr("O₂%") << tr("He%") - << tr("Deco switch at") <(cyl->type.workingpressure.mbar); case SIZE_INT: return static_cast(cyl->type.size.mliter); + case SENSORS: { + std::vector sensors; + for (int i = 0; i < current_dc->samples; ++i) { + auto &sample = current_dc->sample[i]; + for (auto s = 0; s < MAX_SENSORS; ++s) { + if (sample.pressure[s].mbar) { + if (sample.sensor[s] == index.row()) + return tr("Sensor attached, can't move another sensor here."); + else if (std::find(sensors.begin(), sensors.end(), sample.sensor[s]) == sensors.end()) + sensors.push_back(sample.sensor[s]); + } + } + } + QStringList sensorStrings; + for (auto s : sensors) + sensorStrings << QString::number(s); + return tr("Select one of these cylinders: ") + sensorStrings.join(","); + } } break; case Qt::DecorationRole: @@ -272,6 +292,8 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const return tr("Calculated using Bottom pO₂ preference. Setting MOD adjusts O₂%, set to '*' for best O₂% for max. depth."); case MND: return tr("Calculated using Best Mix END preference. Setting MND adjusts He%, set to '*' for best He% for max. depth."); + case SENSORS: + return tr("Index of cylinder that you want to move sensor data from."); } break; } @@ -453,6 +475,24 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in } type = Command::EditCylinderType::TYPE; break; + case SENSORS: { + std::vector sensors; + for (auto &sensor : vString.split(",")) { + bool ok = false; + int s = sensor.toInt(&ok); + if (ok && s < MAX_SENSORS) + sensors.push_back(s); + } + + bool ok = false; + int s = vString.toInt(&ok); + if (ok) { + Command::editSensors(index.row(), s); + // We don't use the edit cylinder command and editing sensors is not relevant for planner + return true; + } + return false; + } } if (inPlanner) { @@ -512,6 +552,18 @@ Qt::ItemFlags CylindersModel::flags(const QModelIndex &index) const { if (index.column() == REMOVE || index.column() == USE) return Qt::ItemIsEnabled; + if (index.column() == SENSORS) { + for (int i = 0; i < current_dc->samples; ++i) { + auto &sample = current_dc->sample[i]; + for (auto s = 0; s < MAX_SENSORS; ++s) { + if (sample.pressure[s].mbar) { + if (sample.sensor[s] == index.row()) + // Sensor attached, not editable. + return QAbstractItemModel::flags(index); + } + } + } + } return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index 0da08900b..b5a350b4e 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -27,6 +27,7 @@ public: USE, WORKINGPRESS_INT, SIZE_INT, + SENSORS, COLUMNS };