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 <michael@andreen.dev>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Michael Andreen 2022-02-19 18:47:25 +01:00 committed by Dirk Hohndel
parent ec96cbaab5
commit 9b4263aa87
10 changed files with 130 additions and 3 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -96,6 +96,8 @@ signals:
void divesTimeChanged(timestamp_t delta, const QVector<dive *> &dives);
void divesImported(); // A general signal when multiple dives have been imported.
void diveComputerEdited(divecomputer *dc);
void cylindersReset(const QVector<dive *> &dives);
void cylinderAdded(dive *d, int pos);
void cylinderRemoved(dive *d, int pos);

View file

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

View file

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

View file

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

View file

@ -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 <string>
@ -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") <<tr("Bot. MOD") <<tr("MND") << tr("Use") << "" << "");
<< tr("Deco switch at") << tr("Bot. MOD") << tr("MND") << tr("Use") << ""
<< "" << tr("Sensors"));
connect(&diveListNotifier, &DiveListNotifier::cylindersReset, this, &CylindersModel::cylindersReset);
connect(&diveListNotifier, &DiveListNotifier::cylinderAdded, this, &CylindersModel::cylinderAdded);
@ -237,6 +239,24 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const
return static_cast<int>(cyl->type.workingpressure.mbar);
case SIZE_INT:
return static_cast<int>(cyl->type.size.mliter);
case SENSORS: {
std::vector<int16_t> 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<int> 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;
}

View file

@ -27,6 +27,7 @@ public:
USE,
WORKINGPRESS_INT,
SIZE_INT,
SENSORS,
COLUMNS
};