Convert the atmospheric pressure in the Information Tab to an editable field

The Information tab shows the atmospheric pressure. Make this value editable
and also ensure that changes to it are undo-able.

Signed-off-by: willemferguson <willemferguson@zoology.up.ac.za>
This commit is contained in:
willemferguson 2019-04-30 12:42:33 +02:00 committed by Dirk Hohndel
parent ca6aa38139
commit 1bdf00b2b4
19 changed files with 323 additions and 20 deletions

View file

@ -1317,9 +1317,10 @@ static struct event *find_previous_event(struct divecomputer *dc, struct event *
return previous;
}
static void fixup_surface_pressure(struct dive *dive)
pressure_t calculate_surface_pressure(const struct dive *dive)
{
struct divecomputer *dc;
const struct divecomputer *dc;
pressure_t res;
int sum = 0, nr = 0;
for_each_dc (dive, dc) {
@ -1328,8 +1329,24 @@ static void fixup_surface_pressure(struct dive *dive)
nr++;
}
}
if (nr)
dive->surface_pressure.mbar = (sum + nr / 2) / nr;
res.mbar = nr ? (sum + nr / 2) / nr : 0;
return res;
}
static void fixup_surface_pressure(struct dive *dive)
{
dive->surface_pressure = calculate_surface_pressure(dive);
}
/* if the surface pressure in the dive data is redundant to the calculated
* value (i.e., it was added by running fixup on the dive) return 0,
* otherwise return the air temperature given in the dive */
pressure_t un_fixup_surface_pressure(const struct dive *d)
{
pressure_t res = d->surface_pressure;
if (res.mbar && res.mbar == calculate_surface_pressure(d).mbar)
res.mbar = 0;
return res;
}
static void fixup_water_salinity(struct dive *dive)
@ -1823,7 +1840,8 @@ struct dive *fixup_dive(struct dive *dive)
fixup_dive_dc(dive, dc);
fixup_water_salinity(dive);
fixup_surface_pressure(dive);
if (!dive->surface_pressure.mbar)
fixup_surface_pressure(dive);
fixup_meandepth(dive);
fixup_duration(dive);
fixup_watertemp(dive);

View file

@ -533,6 +533,8 @@ extern bool dive_or_trip_less_than(struct dive_or_trip a, struct dive_or_trip b)
extern void sort_dive_table(struct dive_table *table);
extern void sort_trip_table(struct trip_table *table);
extern struct dive *fixup_dive(struct dive *dive);
extern pressure_t calculate_surface_pressure(const struct dive *dive);
extern pressure_t un_fixup_surface_pressure(const struct dive *d);
extern void fixup_dc_duration(struct divecomputer *dc);
extern int dive_getUniqID();
extern unsigned int dc_airtemp(const struct divecomputer *dc);

View file

@ -76,6 +76,13 @@ static weight_t get_weight(const char *line)
return w;
}
static pressure_t get_airpressure(const char *line)
{
pressure_t p;
p.mbar = lrint(ascii_strtod(line, NULL));
return p;
}
static pressure_t get_pressure(const char *line)
{
pressure_t p;
@ -245,6 +252,9 @@ static void parse_dive_airtemp(char *line, struct membuffer *str, void *_dive)
static void parse_dive_watertemp(char *line, struct membuffer *str, void *_dive)
{ UNUSED(str); struct dive *dive = _dive; dive->watertemp = get_temperature(line); }
static void parse_dive_airpressure(char *line, struct membuffer *str, void *_dive)
{ UNUSED(str); struct dive *dive = _dive; dive->surface_pressure = get_airpressure(line); }
static void parse_dive_duration(char *line, struct membuffer *str, void *_dive)
{ UNUSED(str); struct dive *dive = _dive; dive->duration = get_duration(line); }
@ -980,7 +990,7 @@ static void divecomputer_parser(char *line, struct membuffer *str, void *_dc)
struct keyword_action dive_action[] = {
#undef D
#define D(x) { #x, parse_dive_ ## x }
D(airtemp), D(buddy), D(cylinder), D(divemaster), D(divesiteid), D(duration),
D(airpressure), D(airtemp), D(buddy), D(cylinder), D(divemaster), D(divesiteid), D(duration),
D(gps), D(location), D(notes), D(notrip), D(rating), D(suit),
D(tags), D(visibility), D(watertemp), D(weightsystem)
};

View file

@ -1306,6 +1306,8 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str
return;
if (MATCH("visibility.dive", get_rating, &dive->visibility))
return;
if (MATCH_STATE("airpressure.dive", pressure, &dive->surface_pressure))
return;
if (state->cur_ws_index < MAX_WEIGHTSYSTEMS) {
if (MATCH("description.weightsystem", utf8_string, &dive->weightsystem[state->cur_ws_index].description))
return;

View file

@ -425,10 +425,13 @@ static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer
*/
static void create_dive_buffer(struct dive *dive, struct membuffer *b)
{
pressure_t surface_pressure = un_fixup_surface_pressure(dive);
if (dive->dc.duration.seconds > 0)
put_format(b, "duration %u:%02u min\n", FRACTION(dive->dc.duration.seconds, 60));
SAVE("rating", rating);
SAVE("visibility", visibility);
if (surface_pressure.mbar)
SAVE("airpressure", surface_pressure.mbar);
cond_put_format(dive->notrip, b, "notrip\n");
save_tags(b, dive->tag_list);
if (dive->dive_site)

View file

@ -473,6 +473,7 @@ static void save_picture(struct membuffer *b, struct picture *pic)
void save_one_dive_to_mb(struct membuffer *b, struct dive *dive, bool anonymize)
{
struct divecomputer *dc;
pressure_t surface_pressure = un_fixup_surface_pressure(dive);
put_string(b, "<dive");
if (dive->number)
@ -488,6 +489,8 @@ void save_one_dive_to_mb(struct membuffer *b, struct dive *dive, bool anonymize)
put_format(b, " divesiteid='%8x'", dive->dive_site->uuid);
}
show_date(b, dive->when);
if (surface_pressure.mbar)
put_pressure(b, surface_pressure, " airpressure='", " bar'");
if (dive->dc.duration.seconds > 0)
put_format(b, " duration='%u:%02u min'>\n",
FRACTION(dive->dc.duration.seconds, 60));

View file

@ -19,6 +19,7 @@ enum class DiveField {
DURATION,
AIR_TEMP,
WATER_TEMP,
ATM_PRESS,
DIVESITE,
DIVEMASTER,
BUDDY,

View file

@ -254,6 +254,17 @@ static inline int mbar_to_PSI(int mbar)
return to_PSI(p);
}
static inline int32_t altitude_to_pressure(int32_t altitude) // altitude in mm above sea level
{ // returns atmospheric pressure in mbar
return (int32_t) (1013.0 * exp(- altitude / 7800000.0));
}
static inline int32_t pressure_to_altitude(int32_t pressure) // pressure in mbar
{ // returns altitude in mm above sea level
return (int32_t) (log(1013.0 / pressure) * 7800000);
}
/*
* We keep our internal data in well-specified units, but
* the input and output may come in some random format. This

View file

@ -171,6 +171,11 @@ void editWaterTemp(int newValue, bool currentDiveOnly)
execute(new EditWaterTemp(newValue, currentDiveOnly));
}
void editAtmPress(int newValue, bool currentDiveOnly)
{
execute(new EditAtmPress(newValue, currentDiveOnly));
}
void editDepth(int newValue, bool currentDiveOnly)
{
execute(new EditDepth(newValue, currentDiveOnly));

View file

@ -61,6 +61,7 @@ void editRating(int newValue, bool currentDiveOnly);
void editVisibility(int newValue, bool currentDiveOnly);
void editAirTemp(int newValue, bool currentDiveOnly);
void editWaterTemp(int newValue, bool currentDiveOnly);
void editAtmPress(int newValue, bool currentDiveOnly);
void editDepth(int newValue, bool currentDiveOnly);
void editDuration(int newValue, bool currentDiveOnly);
void editDiveSite(struct dive_site *newValue, bool currentDiveOnly);

View file

@ -248,6 +248,27 @@ DiveField EditWaterTemp::fieldId() const
return DiveField::WATER_TEMP;
}
// ***** Atmospheric pressure *****
void EditAtmPress::set(struct dive *d, int value) const
{
d->surface_pressure.mbar = value > 0 ? (uint32_t)value : 0u;
}
int EditAtmPress::data(struct dive *d) const
{
return (int)d->surface_pressure.mbar;
}
QString EditAtmPress::fieldName() const
{
return tr("Atm. pressure");
}
DiveField EditAtmPress::fieldId() const
{
return DiveField::ATM_PRESS;
}
// ***** Duration *****
void EditDuration::set(struct dive *d, int value) const
{

View file

@ -102,6 +102,15 @@ public:
DiveField fieldId() const override;
};
class EditAtmPress : public EditBase<int> {
public:
using EditBase<int>::EditBase; // Use constructor of base class.
void set(struct dive *d, int value) const override;
int data(struct dive *d) const override;
QString fieldName() const override;
DiveField fieldId() const override;
};
class EditDuration : public EditBase<int> {
public:
using EditBase<int>::EditBase; // Use constructor of base class.

View file

@ -2,15 +2,24 @@
#include "TabDiveInformation.h"
#include "ui_TabDiveInformation.h"
#include "../tagwidget.h"
#include "core/units.h"
#include "core/dive.h"
#include "desktop-widgets/command.h"
#include <core/qthelper.h>
#include <core/statistics.h>
#include <core/display.h>
#define COMBO_CHANGED 0
#define TEXT_EDITED 1
TabDiveInformation::TabDiveInformation(QWidget *parent) : TabBase(parent), ui(new Ui::TabDiveInformation())
{
ui->setupUi(this);
connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &TabDiveInformation::divesChanged);
QStringList atmPressTypes { "mbar", get_depth_unit() ,"use dc"};
ui->atmPressType->insertItems(0, atmPressTypes);
pressTypeIndex = 0;
}
TabDiveInformation::~TabDiveInformation()
@ -32,7 +41,7 @@ void TabDiveInformation::clear()
ui->averageDepthText->clear();
ui->waterTemperatureText->clear();
ui->airTemperatureText->clear();
ui->airPressureText->clear();
ui->atmPressVal->clear();
ui->salinityText->clear();
}
@ -74,6 +83,17 @@ void TabDiveInformation::updateProfile()
" ", current_dive->dc.divemode == FREEDIVE));
ui->sacText->setText( mean[0] ? SACs : QString());
if (current_dive->surface_pressure.mbar == 0) {
ui->atmPressVal->clear(); // If no atm pressure for dive then clear text box
}
else {
ui->atmPressVal->setEnabled(true);
QString pressStr;
pressStr.sprintf("%d",current_dive->surface_pressure.mbar);
ui->atmPressVal->setText(pressStr); // else display atm pressure
}
}
// Update fields that depend on start of dive
@ -99,15 +119,15 @@ void TabDiveInformation::updateData()
ui->waterTemperatureText->setText(get_temperature_string(current_dive->watertemp, true));
ui->airTemperatureText->setText(get_temperature_string(current_dive->airtemp, true));
if (current_dive->surface_pressure.mbar) /* this is ALWAYS displayed in mbar */
ui->airPressureText->setText(QString("%1mbar").arg(current_dive->surface_pressure.mbar));
else
ui->airPressureText->clear();
if (current_dive->salinity)
ui->salinityText->setText(QString("%1g/").arg(current_dive->salinity / 10.0));
else
ui->salinityText->clear();
ui->atmPressType->setEditable(true);
ui->atmPressType->setItemText(1, get_depth_unit()); // Check for changes in depth unit (imperial/metric)
ui->atmPressType->setEditable(false);
ui->atmPressType->setCurrentIndex(0); // Set the atmospheric pressure combo box to mbar
}
// This function gets called if a field gets updated by an undo command.
@ -130,6 +150,9 @@ void TabDiveInformation::divesChanged(dive_trip *trip, const QVector<dive *> &di
case DiveField::WATER_TEMP:
ui->waterTemperatureText->setText(get_temperature_string(current_dive->watertemp, true));
break;
case DiveField::ATM_PRESS:
ui->atmPressVal->setText(ui->atmPressVal->text().sprintf("%d",current_dive->surface_pressure.mbar));
break;
case DiveField::DATETIME:
updateWhen();
break;
@ -137,3 +160,47 @@ void TabDiveInformation::divesChanged(dive_trip *trip, const QVector<dive *> &di
break;
}
}
void TabDiveInformation::on_atmPressType_currentIndexChanged(int index) { updateTextBox(COMBO_CHANGED); }
void TabDiveInformation::on_atmPressVal_editingFinished() { updateTextBox(TEXT_EDITED); }
void TabDiveInformation::updateTextBox(int event) // Either the text box has been edited or the pressure type has changed.
{ // Either way this gets a numeric value and puts it on the text box atmPressVal,
pressure_t atmpress = { 0 }; // then stores it in dive->surface_pressure.The undo stack for the text box content is
double altitudeVal; // maintained even though two independent events trigger saving the text box contents.
if (current_dive) {
switch (ui->atmPressType->currentIndex()) {
case 0: // If an atm pressure has been specified in mbar:
if (event == TEXT_EDITED) // this is only triggered by on_atmPressVal_editingFinished()
atmpress.mbar = ui->atmPressVal->text().toInt(); // use the specified mbar pressure
break;
case 1: // If an altitude has been specified:
if (event == TEXT_EDITED) { // this is only triggered by on_atmPressVal_editingFinished()
altitudeVal = (ui->atmPressVal->text().toFloat()); // get altitude from text box
if (prefs.units.length == units::FEET) // if altitude in feet
altitudeVal = feet_to_mm(altitudeVal); // imperial: convert altitude from feet to mm
else
altitudeVal = altitudeVal * 1000; // metric: convert altitude from meters to mm
atmpress.mbar = altitude_to_pressure((int32_t) altitudeVal); // convert altitude (mm) to pressure (mbar)
ui->atmPressVal->setText(ui->atmPressVal->text().sprintf("%d",atmpress.mbar));
ui->atmPressType->setCurrentIndex(0); // reset combobox to mbar
} else { // i.e. event == COMBO_CHANGED, that is, "m" or "ft" was selected from combobox
ui->atmPressVal->clear(); // Clear the text box so that altitude can be typed
}
break;
case 2: // i.e. event = COMBO_CHANGED, that is, the option "Use dc" was selected from combobox
atmpress = calculate_surface_pressure(current_dive); // re-calculate air pressure from dc data
ui->atmPressVal->setText(QString::number(atmpress.mbar)); // display it in text box
ui->atmPressType->setCurrentIndex(0); // reset combobox to mbar
break;
default:
atmpress.mbar = 1013; // This line should never execute
break;
}
if (atmpress.mbar)
Command::editAtmPress(atmpress.mbar, false); // and save the pressure for undo
}
}

View file

@ -18,10 +18,14 @@ public:
void clear() override;
private slots:
void divesChanged(dive_trip *trip, const QVector<dive *> &dives, DiveField field);
void on_atmPressVal_editingFinished();
void on_atmPressType_currentIndexChanged(int index);
private:
Ui::TabDiveInformation *ui;
void updateProfile();
void updateWhen();
int pressTypeIndex;
void updateTextBox(int event);
};
#endif

View file

@ -224,25 +224,37 @@
</layout>
</widget>
</item>
<item row="2" column="2">
<item row="2" column="2" colspan="1">
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string>Air pressure</string>
<string>Atm. pressure</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter</set>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="diveInfoAirPressureLayout">
<item>
<widget class="QLabel" name="airPressureText">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
<widget class="QLineEdit" name="atmPressVal">
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="atmPressType">
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="2">
<widget class="QGroupBox" name="groupBox_9">
<property name="title">

50
dives/TestAtmPress.xml Normal file
View file

@ -0,0 +1,50 @@
<divelog program='subsurface' version='3'>
<settings>
<autogroup state='1' />
</settings>
<divesites>
<site uuid='4fdcf19b' name='Loc1'>
</site>
<site uuid='b15ad032' name='Loc2'>
</site>
</divesites>
<dives>
<trip date='2019-04-09' time='09:15:16' location='Scottburgh'>
<dive number='524' tripflag='NOTRIP' divesiteid='b15ad032' date='2019-04-09' time='09:15:16' airpressure='1.012 bar' duration='42:08 min'>
<cylinder size='10.0 l' workpressure='232.0 bar' description='10 232 bar' o2='30.0%' depth='42.96 m' />
<cylinder size='10.0 l' workpressure='232.0 bar' description='10 232 bar' o2='30.0%' depth='42.96 m' />
<weightsystem weight='3.0 kg' description='belt' />
<divecomputer model='Uwatec Galileo Trimix' deviceid='462769ce' diveid='14106bf6'>
<depth max='30.068 m' mean='21.394 m' />
<temperature air='24.6 C' water='23.2 C' />
<water salinity='1025 g/l' />
<sample time='0:00 min' depth='0.0 m' temp='24.4 C' rbt='99:00 min' />
<sample time='0:04 min' depth='2.439 m' temp='24.8 C' rbt='99:00 min' />
<sample time='0:08 min' depth='3.395 m' temp='24.4 C' rbt='99:00 min' />
<sample time='0:12 min' depth='3.902 m' temp='24.8 C' pressure='208.5 bar' rbt='99:00 min' />
<sample time='45:16 min' depth='4.488 m' pressure='208.25 bar' rbt='98:00 min' />
</divecomputer>
</dive>
<dive number='525' divesiteid='4fdcf19b' date='2019-04-10' time='08:14:01' airpressure='0.991 bar' duration='41:24 min'>
<cylinder size='10.0 l' workpressure='232.0 bar' description='10 232 bar' o2='31.0%' depth='41.25 m' />
<cylinder description='unknown' o2='31.0%' depth='41.25 m' />
<weightsystem weight='3.0 kg' description='belt' />
<divecomputer model='Uwatec Galileo Trimix' deviceid='462769ce' diveid='04c0cf94'>
<depth max='26.166 m' mean='19.561 m' />
<temperature air='24.8 C' water='23.6 C' />
<water salinity='1025 g/l' />
<sample time='0:00 min' depth='0.0 m' temp='26.8 C' rbt='99:00 min' />
<sample time='0:04 min' depth='3.61 m' rbt='99:00 min' />
<sample time='0:08 min' depth='4.741 m' temp='25.6 C' pressure='217.25 bar' rbt='99:00 min' />
<sample time='0:12 min' depth='6.478 m' pressure='216.75 bar' rbt='97:00 min' />
<sample time='0:16 min' depth='7.785 m' rbt='94:00 min' />
<sample time='44:04 min' depth='0.0 m' rbt='99:00 min' />
<sample time='44:08 min' depth='0.02 m' rbt='99:00 min' />
<sample time='44:12 min' depth='0.098 m' rbt='99:00 min' />
<sample time='44:16 min' depth='0.059 m' rbt='99:00 min' />
<sample time='44:20 min' depth='0.059 m' rbt='99:00 min' />
</divecomputer>
</dive>
</trip>
</dives>
</divelog>

View file

@ -92,6 +92,7 @@ TEST(TestUnitConversion testunitconversion.cpp)
TEST(TestProfile testprofile.cpp)
TEST(TestGpsCoords testgpscoords.cpp)
TEST(TestParse testparse.cpp)
TEST(TestAirPressure testAirPressure.cpp)
if (BTSUPPORT)
TEST(TestHelper testhelper.cpp)
endif()
@ -128,6 +129,7 @@ add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}
TestParse
TestGitStorage
TestPlan
TestAirPressure
TestDiveSiteDuplication
TestRenumber
TestPicture

63
tests/testAirPressure.cpp Normal file
View file

@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-2.0
#include "testAirPressure.h"
#include "core/divesite.h"
#include "core/divelist.h"
#include "core/file.h"
#include "core/dive.h"
#include <QString>
#include <core/qthelper.h>
void TestAirPressure::initTestCase()
{
/* we need to manually tell that the resource exists, because we are using it as library. */
Q_INIT_RESOURCE(subsurface);
}
void TestAirPressure::get_dives()
{
struct dive *dive;
verbose = 1;
QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/TestAtmPress.xml", &dive_table, &trip_table, &dive_site_table), 0);
dive = get_dive(0);
dive->selected = true;
QVERIFY(dive != NULL);
}
void TestAirPressure::testReadAirPressure()
{
struct dive *dive;
dive = get_dive(0);
QVERIFY(dive != NULL);
dive->selected = true;
QCOMPARE(1012, dive->surface_pressure.mbar);
dive = get_dive(1);
QVERIFY(dive != NULL);
dive->selected = true;
QCOMPARE(991, dive->surface_pressure.mbar);
}
void TestAirPressure::testConvertAltitudetoAirPressure()
{
QCOMPARE(891,altitude_to_pressure(1000000)); // 1000 m altitude in mm
QCOMPARE(1013,altitude_to_pressure(0)); // sea level
}
void TestAirPressure::testWriteReadBackAirPressure()
{
struct dive *dive;
int32_t ap = 1111;
dive = get_dive(0);
QVERIFY(dive != NULL);
dive->selected = true;
dive->surface_pressure.mbar = ap;
QCOMPARE(save_dives("./testout.ssrf"), 0);
clear_dive_file_data();
QCOMPARE(parse_file("./testout.ssrf", &dive_table, &trip_table, &dive_site_table), 0);
dive = get_dive(0);
QVERIFY(dive != NULL);
dive->selected = true;
QCOMPARE(ap, dive->surface_pressure.mbar);
}
QTEST_GUILESS_MAIN(TestAirPressure)

19
tests/testAirPressure.h Normal file
View file

@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef TESTPICTURE_H
#define TESTPICTURE_H
#include <QtTest>
class TestAirPressure : public QObject {
Q_OBJECT
private slots:
void initTestCase();
void get_dives();
void testReadAirPressure();
void testWriteReadBackAirPressure();
void testConvertAltitudetoAirPressure();
};
#endif