Compare commits

...

7 commits

Author SHA1 Message Date
Dirk Hohndel
32f92d9497
Merge 5d38fd3fe8 into 91d8bfef55 2024-11-19 08:48:36 +13:00
Michael Keller
91d8bfef55 Import: Add Parsing for Divesoft Freedom+ Format Files.
Switch the Divesoft log file importer to use the parser in
libdivecomputer. This adds support for the newer Divesoft Freedom+ log
file format.
Also refactor the OSTCTools log file importer to share common
functionality.

Signed-off-by: Michael Keller <github@ike.ch>
2024-11-19 08:47:00 +13:00
Michael Keller
7f42acfdfb Import: Add Parsing for Divesoft Freedom+ Format Files.
Switch the Divesoft log file importer to use the parser in
libdivecomputer. This adds support for the newer Divesoft Freedom+ log
file format.
Also refactor the OSTCTools log file importer to share common
functionality.

Signed-off-by: Michael Keller <github@ike.ch>
2024-11-19 08:47:00 +13:00
Robert C. Helling
7dc92e170f Round MOD to lower depths
When computing the suggested switch depth for a gas,
we should take the next stop depth above the MOD, i.e.
round down. Otherwise we can produce MOD violation warnings.

We need, however, a bit of fudge as otherwise we do not
suggest to switch to o2 at 6m.

TestPlan uses the MOD to determine the depth to switch to
Tx21/35. This happens to be 65.378m. Therefore, switching
at 66m violates the MOD, the switch should be at 63m.
This then affects the gas usage.

Signed-off-by: Robert C. Helling <helling@atdotde.de>
2024-11-19 08:46:13 +13:00
Michael Keller
a898173ce7 CICD: Update the MXE Version Used to Build the Windows Version.
This fixes the missing 'Print' functionality in Windows.

Signed-off-by: Michael Keller <github@ike.ch>
2024-11-19 08:44:55 +13:00
Dirk Hohndel
5d38fd3fe8 Make googlemaps compile with Qt5 and Qt6
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-08-27 22:34:57 -07:00
Egbertdepauw
1742d6eabb qt6: Make subsurface buildable with Qt6 on Macos
Building on macos with the Qt6 framework.

made changes to several files to make it possible to build subsurface
desktop with the Qt 6.5.x and higher framework.

Tested versions: 6.5.2, 6.6.3, 6.7.2

code builds and googlemaps works, made some adjustments to get panning
and zooming with mouse or trackpad working.

See issue #3577 "Build Fails, macOS" for build details.

Signed-off-by: Egbertdepauw <egbert@despaankamer.nl>
2024-08-27 22:34:57 -07:00
18 changed files with 260 additions and 632 deletions

View file

@ -12,8 +12,9 @@ jobs:
windows-mxe: windows-mxe:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
VERSION: ${{ '3.3.0' }} # 'official' images should have a dot-zero version VERSION: ${{ '3.4.0' }} # 'official' images should have a dot-zero version
mxe_sha: '974808c2ecb02866764d236fe533ae57ba342e7a' # Fix this here as QTWebKit (needed for printing) is broken in newer versions
mxe_sha: 'd6377b2f2334694dbb040294fd0d848327e63328'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -82,6 +82,7 @@ SOURCES += subsurface-mobile-main.cpp \
core/save-git.cpp \ core/save-git.cpp \
core/datatrak.cpp \ core/datatrak.cpp \
core/ostctools.cpp \ core/ostctools.cpp \
core/divesoft.cpp \
core/planner.cpp \ core/planner.cpp \
core/save-xml.cpp \ core/save-xml.cpp \
core/cochran.cpp \ core/cochran.cpp \

View file

@ -77,6 +77,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
divesitetable.h divesitetable.h
divesitehelpers.cpp divesitehelpers.cpp
divesitehelpers.h divesitehelpers.h
divesoft.cpp
downloadfromdcthread.cpp downloadfromdcthread.cpp
downloadfromdcthread.h downloadfromdcthread.h
event.cpp event.cpp

View file

@ -2457,7 +2457,9 @@ int dive::mbar_to_depth(int mbar) const
depth_t dive::gas_mod(struct gasmix mix, pressure_t po2_limit, int roundto) const depth_t dive::gas_mod(struct gasmix mix, pressure_t po2_limit, int roundto) const
{ {
double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix)); double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix));
return depth_t { .mm = int_cast<int>(depth / roundto) * roundto }; // Rounding should be towards lower=safer depths but we give a bit
// of fudge to all to switch to o2 at 6m. So from .9 we round up.
return depth_t { .mm = (int)(depth / roundto + 0.1) * roundto };
} }
/* Maximum narcotic depth rounded to multiples of roundto mm */ /* Maximum narcotic depth rounded to multiples of roundto mm */

48
core/divesoft.cpp Normal file
View file

@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "errorhelper.h"
#include "subsurface-string.h"
#include "gettext.h"
#include "dive.h"
#include "divelist.h"
#include "divelog.h"
#include "extradata.h"
#include "format.h"
#include "libdivecomputer.h"
// As supplied by Divesoft
static const char divesoft_liberty_serial_prefix[] = "7026";
static const char divesoft_freedom_serial_prefix[] = "7044";
static const char divesoft_freedom_plus_serial_prefix[] = "7273";
// From libdivecomputer
static const int divesoft_liberty_model = 10;
static const int divesoft_freedom_model = 19;
int divesoft_import(const std::unique_ptr<std::vector<unsigned char>> &buffer, struct divelog *log)
{
int model = 0;
if (strncmp((char *)(buffer->data() + 52), divesoft_liberty_serial_prefix, 4) == 0)
model = divesoft_liberty_model;
else if (strncmp((char *)(buffer->data() + 52), divesoft_freedom_serial_prefix, 4) == 0 || strncmp((char *)(buffer->data() + 52), divesoft_freedom_plus_serial_prefix, 4) == 0)
model = divesoft_freedom_model;
device_data_t devdata;
int ret = prepare_device_descriptor(model, DC_FAMILY_DIVESOFT_FREEDOM, devdata);
if (ret == 0)
return report_error("%s", translate("gettextFromC", "Unknown DC"));
auto d = std::make_unique<dive>();
d->dcs[0].model = devdata.vendor + " " + devdata.model + " (Imported from file)";
// Parse the dive data
dc_status_t rc = libdc_buffer_parser(d.get(), &devdata, buffer->data(), buffer->size());
if (rc != DC_STATUS_SUCCESS)
return report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), d->number);
log->dives.record_dive(std::move(d));
return 1;
}

View file

@ -267,6 +267,43 @@ bool remote_repo_uptodate(const char *filename, struct git_info *info)
return false; return false;
} }
static std::unique_ptr<std::vector<unsigned char>> read_into_buffer(const char *file)
{
const char *failed_to_read_msg = translate("gettextFromC", "Failed to read '%s'");
struct stat file_status;
if (stat(file, &file_status) < 0) {
report_error(failed_to_read_msg, file);
return NULL;
}
FILE *archive;
if ((archive = subsurface_fopen(file, "rb")) == NULL) {
report_error(failed_to_read_msg, file);
return NULL;
}
// Read dive's raw data
auto buffer = std::make_unique<std::vector<unsigned char>>(file_status.st_size, 0);
int i = 0, c;
while ((c = getc(archive)) != EOF) {
(*buffer)[i] = c;
i++;
}
if (ferror(archive)) {
report_error(failed_to_read_msg, file);
fclose(archive);
return NULL;
}
fclose(archive);
return buffer;
}
int parse_file(const char *filename, struct divelog *log) int parse_file(const char *filename, struct divelog *log)
{ {
struct git_info info; struct git_info info;
@ -304,8 +341,13 @@ int parse_file(const char *filename, struct divelog *log)
} }
/* Divesoft Freedom */ /* Divesoft Freedom */
if (fmt && (!strcasecmp(fmt + 1, "DLF"))) if (fmt && (!strcasecmp(fmt + 1, "DLF"))) {
return parse_dlf_buffer((unsigned char *)mem.data(), mem.size(), log); auto buffer = read_into_buffer(filename);
if (buffer == NULL)
return -1;
return divesoft_import(buffer, log);
}
/* DataTrak/Wlog */ /* DataTrak/Wlog */
if (fmt && !strcasecmp(fmt + 1, "LOG")) { if (fmt && !strcasecmp(fmt + 1, "LOG")) {
@ -321,8 +363,11 @@ int parse_file(const char *filename, struct divelog *log)
/* OSTCtools */ /* OSTCtools */
if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) { if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) {
ostctools_import(filename, log); auto buffer = read_into_buffer(filename);
return 0; if (buffer == NULL)
return -1;
return ostctools_import(buffer, log);
} }
return parse_file_buffer(filename, mem, log); return parse_file_buffer(filename, mem, log);

View file

@ -8,11 +8,13 @@
#include <stdio.h> #include <stdio.h>
#include <vector> #include <vector>
#include <utility> #include <utility>
#include <memory>
struct divelog; struct divelog;
struct zip; struct zip;
extern void ostctools_import(const char *file, struct divelog *log); extern int ostctools_import(const std::unique_ptr<std::vector<unsigned char>> &buffer, struct divelog *log);
extern int divesoft_import(const std::unique_ptr<std::vector<unsigned char>> &buffer, struct divelog *log);
extern int parse_file(const char *filename, struct divelog *log); extern int parse_file(const char *filename, struct divelog *log);
extern int try_to_open_zip(const char *filename, struct divelog *log); extern int try_to_open_zip(const char *filename, struct divelog *log);

View file

@ -1534,6 +1534,26 @@ std::string do_libdivecomputer_import(device_data_t *data)
return err; return err;
} }
/*
* Fills a device_data_t structure with known dc data and a descriptor.
*/
int prepare_device_descriptor(int data_model, dc_family_t dc_fam, device_data_t &dev_data)
{
dev_data.device = NULL;
dev_data.context = NULL;
dc_descriptor_t *data_descriptor = get_descriptor(dc_fam, data_model);
if (data_descriptor) {
dev_data.descriptor = data_descriptor;
dev_data.vendor = dc_descriptor_get_vendor(data_descriptor);
dev_data.model = dc_descriptor_get_product(data_descriptor);
} else {
return 0;
}
return 1;
}
/* /*
* Parse data buffers instead of dc devices downloaded data. * Parse data buffers instead of dc devices downloaded data.
* Intended to be used to parse profile data from binary files during import tasks. * Intended to be used to parse profile data from binary files during import tasks.
@ -1543,7 +1563,7 @@ std::string do_libdivecomputer_import(device_data_t *data)
* Note that dc_descriptor_t in data *must* have been filled using dc_descriptor_iterator() * Note that dc_descriptor_t in data *must* have been filled using dc_descriptor_iterator()
* calls. * calls.
*/ */
dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size) dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, const unsigned char *buffer, int size)
{ {
dc_status_t rc; dc_status_t rc;
dc_parser_t *parser = NULL; dc_parser_t *parser = NULL;
@ -1556,6 +1576,7 @@ dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned
case DC_FAMILY_HW_OSTC: case DC_FAMILY_HW_OSTC:
case DC_FAMILY_HW_FROG: case DC_FAMILY_HW_FROG:
case DC_FAMILY_HW_OSTC3: case DC_FAMILY_HW_OSTC3:
case DC_FAMILY_DIVESOFT_FREEDOM:
rc = dc_parser_new2(&parser, data->context, data->descriptor, buffer, size); rc = dc_parser_new2(&parser, data->context, data->descriptor, buffer, size);
break; break;
default: default:

View file

@ -54,7 +54,8 @@ struct device_data_t {
const char *errmsg (dc_status_t rc); const char *errmsg (dc_status_t rc);
std::string do_libdivecomputer_import(device_data_t *data); std::string do_libdivecomputer_import(device_data_t *data);
dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size); int prepare_device_descriptor(int data_model, dc_family_t dc_fam, device_data_t &dev_data);
dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, const unsigned char *buffer, int size);
void logfunc(dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata); void logfunc(dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata);
dc_descriptor_t *get_descriptor(dc_family_t type, unsigned int model); dc_descriptor_t *get_descriptor(dc_family_t type, unsigned int model);

View file

@ -14,101 +14,48 @@
#include "format.h" #include "format.h"
#include "libdivecomputer.h" #include "libdivecomputer.h"
/*
* Fills a device_data_t structure with known dc data and a descriptor.
*/
static int ostc_prepare_data(int data_model, dc_family_t dc_fam, device_data_t &dev_data)
{
dc_descriptor_t *data_descriptor;
dev_data.device = NULL;
dev_data.context = NULL;
data_descriptor = get_descriptor(dc_fam, data_model);
if (data_descriptor) {
dev_data.descriptor = data_descriptor;
dev_data.vendor = dc_descriptor_get_vendor(data_descriptor);
dev_data.model = dc_descriptor_get_product(data_descriptor);
} else {
return 0;
}
return 1;
}
/* /*
* OSTCTools stores the raw dive data in heavily padded files, one dive * OSTCTools stores the raw dive data in heavily padded files, one dive
* each file. So it's not necessary to iterate once and again on a parsing * each file. So it's not necessary to iterate once and again on a parsing
* function. Actually there's only one kind of archive for every DC model. * function. Actually there's only one kind of archive for every DC model.
*/ */
void ostctools_import(const char *file, struct divelog *log) int ostctools_import(const std::unique_ptr<std::vector<unsigned char>> &buffer, struct divelog *log)
{ {
FILE *archive; if (buffer->size() < 456)
device_data_t devdata; return report_error("%s", translate("gettextFromC", "Invalid OSTCTools file"));
dc_family_t dc_fam;
std::vector<unsigned char> buffer(65536, 0);
unsigned char uc_tmp[2];
auto ostcdive = std::make_unique<dive>();
dc_status_t rc = DC_STATUS_SUCCESS;
int model, ret, i = 0, c;
unsigned int serial;
const char *failed_to_read_msg = translate("gettextFromC", "Failed to read '%s'");
// Open the archive
if ((archive = subsurface_fopen(file, "rb")) == NULL) {
report_error(failed_to_read_msg, file);
return;
}
// Read dive number from the log // Read dive number from the log
if (fseek(archive, 258, 0) == -1) { auto ostcdive = std::make_unique<dive>();
report_error(failed_to_read_msg, file); ostcdive->number = (*buffer)[258] + ((*buffer)[259] << 8);
fclose(archive);
return;
}
if (fread(uc_tmp, 1, 2, archive) != 2) {
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
ostcdive->number = uc_tmp[0] + (uc_tmp[1] << 8);
// Read device's serial number // Read device's serial number
if (fseek(archive, 265, 0) == -1) { unsigned int serial = (*buffer)[265] + ((*buffer)[266] << 8);
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
if (fread(uc_tmp, 1, 2, archive) != 2) {
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
serial = uc_tmp[0] + (uc_tmp[1] << 8);
// Read dive's raw data, header + profile // Trim the buffer to the actual dive data
if (fseek(archive, 456, 0) == -1) { buffer->erase(buffer->begin(), buffer->begin() + 456);
report_error(failed_to_read_msg, file); unsigned int i = 0;
fclose(archive); bool end_marker = false;
return; for (auto c: *buffer) {
}
while ((c = getc(archive)) != EOF) {
buffer[i] = c;
if (buffer[i] == 0xFD && buffer[i - 1] == 0xFD)
break;
i++; i++;
if (c == 0xFD) {
if (end_marker)
break;
else
end_marker = true;
} else {
end_marker = false;
}
} }
if (ferror(archive)) { if (end_marker)
report_error(failed_to_read_msg, file); buffer->erase(buffer->begin() + i, buffer->end());
fclose(archive);
return;
}
fclose(archive);
// Try to determine the dc family based on the header type // Try to determine the dc family based on the header type
if (buffer[2] == 0x20 || buffer[2] == 0x21) { dc_family_t dc_fam;
if ((*buffer)[2] == 0x20 || (*buffer)[2] == 0x21) {
dc_fam = DC_FAMILY_HW_OSTC; dc_fam = DC_FAMILY_HW_OSTC;
} else { } else {
switch (buffer[8]) { switch ((*buffer)[8]) {
case 0x22: case 0x22:
dc_fam = DC_FAMILY_HW_FROG; dc_fam = DC_FAMILY_HW_FROG;
break; break;
@ -117,12 +64,12 @@ void ostctools_import(const char *file, struct divelog *log)
dc_fam = DC_FAMILY_HW_OSTC3; dc_fam = DC_FAMILY_HW_OSTC3;
break; break;
default: default:
report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); return report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
return;
} }
} }
// Try to determine the model based on serial number // Try to determine the model based on serial number
int model;
switch (dc_fam) { switch (dc_fam) {
case DC_FAMILY_HW_OSTC: case DC_FAMILY_HW_OSTC:
if (serial > 7000) if (serial > 7000)
@ -145,17 +92,16 @@ void ostctools_import(const char *file, struct divelog *log)
} }
// Prepare data to pass to libdivecomputer. // Prepare data to pass to libdivecomputer.
ret = ostc_prepare_data(model, dc_fam, devdata); device_data_t devdata;
if (ret == 0) { int ret = prepare_device_descriptor(model, dc_fam, devdata);
report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); if (ret == 0)
return; return report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
}
ostcdive->dcs[0].model = devdata.vendor + " " + devdata.model + " (Imported from OSTCTools)"; ostcdive->dcs[0].model = devdata.vendor + " " + devdata.model + " (Imported from OSTCTools)";
// Parse the dive data // Parse the dive data
rc = libdc_buffer_parser(ostcdive.get(), &devdata, buffer.data(), i + 1); dc_status_t rc = libdc_buffer_parser(ostcdive.get(), &devdata, buffer->data(), buffer->size());
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), ostcdive->number); return report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), ostcdive->number);
// Serial number is not part of the header nor the profile, so libdc won't // Serial number is not part of the header nor the profile, so libdc won't
// catch it. If Serial is part of the extra_data, and set to zero, replace it. // catch it. If Serial is part of the extra_data, and set to zero, replace it.
@ -169,4 +115,6 @@ void ostctools_import(const char *file, struct divelog *log)
add_extra_data(&ostcdive->dcs[0], "Serial", ostcdive->dcs[0].serial); add_extra_data(&ostcdive->dcs[0], "Serial", ostcdive->dcs[0].serial);
log->dives.record_dive(std::move(ostcdive)); log->dives.record_dive(std::move(ostcdive));
return 1;
} }

View file

@ -1770,499 +1770,6 @@ int parse_xml_buffer(const char *url, const char *buffer, int, struct divelog *l
return ret; return ret;
} }
/*
* Parse a unsigned 32-bit integer in little-endian mode,
* that is seconds since Jan 1, 2000.
*/
static timestamp_t parse_dlf_timestamp(unsigned char *buffer)
{
timestamp_t offset;
offset = buffer[3];
offset = (offset << 8) + buffer[2];
offset = (offset << 8) + buffer[1];
offset = (offset << 8) + buffer[0];
// Jan 1, 2000 is 946684800 seconds after Jan 1, 1970, which is
// the Unix epoch date that "timestamp_t" uses.
return offset + 946684800;
}
int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divelog *log)
{
using namespace std::string_literals;
unsigned char *ptr = buffer;
unsigned char event;
bool found;
unsigned int time = 0;
char serial[6];
struct battery_status {
uint16_t volt1;
uint8_t percent1;
uint16_t volt2;
uint8_t percent2;
};
struct battery_status battery_start = {0, 0, 0, 0};
struct battery_status battery_end = {0, 0, 0, 0};
uint16_t o2_sensor_calibration_values[4] = {0};
cylinder_t *cyl;
struct parser_state state;
state.log = log;
// Check for the correct file magic
if (ptr[0] != 'D' || ptr[1] != 'i' || ptr[2] != 'v' || ptr[3] != 'E')
return -1;
dive_start(&state);
divecomputer_start(&state);
state.cur_dc->model = "DLF import";
// (ptr[7] << 8) + ptr[6] Is "Serial"
snprintf(serial, sizeof(serial), "%d", (ptr[7] << 8) + ptr[6]);
state.cur_dc->serial = serial;
state.cur_dc->when = parse_dlf_timestamp(ptr + 8);
state.cur_dive->when = state.cur_dc->when;
state.cur_dc->duration.seconds = ((ptr[14] & 0xFE) << 16) + (ptr[13] << 8) + ptr[12];
// ptr[14] >> 1 is scrubber used in %
// 3 bit dive type
switch((ptr[15] & 0x38) >> 3) {
case 0: // unknown
case 1:
state.cur_dc->divemode = OC;
break;
case 2:
state.cur_dc->divemode = CCR;
break;
case 3:
state.cur_dc->divemode = CCR; // mCCR
break;
case 4:
state.cur_dc->divemode = FREEDIVE;
break;
case 5:
state.cur_dc->divemode = OC; // Gauge
break;
case 6:
state.cur_dc->divemode = PSCR; // ASCR
break;
case 7:
state.cur_dc->divemode = PSCR;
break;
}
state.cur_dc->maxdepth.mm = ((ptr[21] << 8) + ptr[20]) * 10;
state.cur_dc->surface_pressure.mbar = ((ptr[25] << 8) + ptr[24]) / 10;
// Declare initial mix as first cylinder
cyl = state.cur_dive->get_or_create_cylinder(0);
cyl->gasmix.o2.permille = ptr[26] * 10;
cyl->gasmix.he.permille = ptr[27] * 10;
/* Done with parsing what we know about the dive header */
ptr += 32;
// We're going to interpret ppO2 saved as a sensor value in these modes.
if (state.cur_dc->divemode == CCR || state.cur_dc->divemode == PSCR)
state.cur_dc->no_o2sensors = 1;
for (; ptr < buffer + size; ptr += 16) {
time = ((ptr[0] >> 4) & 0x0f) +
((ptr[1] << 4) & 0xff0) +
((ptr[2] << 12) & 0x1f000);
event = ptr[0] & 0x0f;
switch (event) {
case 0:
/* Regular sample */
sample_start(&state);
state.cur_sample->time.seconds = time;
state.cur_sample->depth.mm = ((ptr[5] << 8) + ptr[4]) * 10;
// Crazy precision on these stored values...
// Only store value if we're in CCR/PSCR mode,
// because we rather calculate ppo2 our selfs.
if (state.cur_dc->divemode == CCR || state.cur_dc->divemode == PSCR)
state.cur_sample->o2sensor[0].mbar = ((ptr[7] << 8) + ptr[6]) / 10;
// In some test files, ndl / tts / temp is bogus if this bits are 1
// flag bits in ptr[11] & 0xF0 is probably involved to,
if ((ptr[2] >> 5) != 1) {
// NDL in minutes, 10 bit
state.cur_sample->ndl.seconds = (((ptr[9] & 0x03) << 8) + ptr[8]) * 60;
// TTS in minutes, 10 bit
state.cur_sample->tts.seconds = (((ptr[10] & 0x0F) << 6) + (ptr[9] >> 2)) * 60;
// Temperature in 1/10 C, 10 bit signed
state.cur_sample->temperature.mkelvin = ((ptr[11] & 0x20) ? -1 : 1) * (((ptr[11] & 0x1F) << 4) + (ptr[10] >> 4)) * 100 + ZERO_C_IN_MKELVIN;
}
state.cur_sample->stopdepth.mm = ((ptr[13] << 8) + ptr[12]) * 10;
if (state.cur_sample->stopdepth.mm)
state.cur_sample->in_deco = true;
//ptr[14] is helium content, always zero?
//ptr[15] is setpoint, what the computer thinks you should aim for?
sample_end(&state);
break;
case 1: /* dive event */
case 2: /* automatic parameter change */
case 3: /* diver error */
case 4: /* internal error */
case 5: /* device activity log */
//Event 18 is a button press. Lets ingore that event.
if (ptr[4] == 18)
continue;
event_start(&state);
state.cur_event.time.seconds = time;
switch (ptr[4]) {
case 1:
state.cur_event.name = "Setpoint Manual"s;
state.cur_event.value = ptr[6];
sample_start(&state);
state.cur_sample->setpoint.mbar = ptr[6] * 10;
sample_end(&state);
break;
case 2:
state.cur_event.name = "Setpoint Auto"s;
state.cur_event.value = ptr[6];
sample_start(&state);
state.cur_sample->setpoint.mbar = ptr[6] * 10;
sample_end(&state);
switch (ptr[7]) {
case 0:
state.cur_event.name += " Manual"s;
break;
case 1:
state.cur_event.name += " Auto Start"s;
break;
case 2:
state.cur_event.name += " Auto Hypox"s;
break;
case 3:
state.cur_event.name += " Auto Timeout"s;
break;
case 4:
state.cur_event.name += " Auto Ascent"s;
break;
case 5:
state.cur_event.name += " Auto Stall"s;
break;
case 6:
state.cur_event.name += " Auto SP Low"s;
break;
default:
break;
}
break;
case 3:
// obsolete
state.cur_event.name = "OC"s;
break;
case 4:
// obsolete
state.cur_event.name = "CCR"s;
break;
case 5:
state.cur_event.name = "gaschange"s;
state.cur_event.type = SAMPLE_EVENT_GASCHANGE2;
state.cur_event.value = ptr[7] << 8 ^ ptr[6];
for (const auto [i, cyl]: enumerated_range(state.cur_dive->cylinders)) {
if (cyl.gasmix.o2.permille == ptr[6] * 10 && cyl.gasmix.he.permille == ptr[7] * 10) {
found = true;
state.cur_event.gas.index = i;
break;
}
}
if (!found) {
cyl = cylinder_start(&state);
cyl->gasmix.o2.permille = ptr[6] * 10;
cyl->gasmix.he.permille = ptr[7] * 10;
cylinder_end(&state);
state.cur_event.gas.index = static_cast<int>(state.cur_dive->cylinders.size()) - 1;
}
break;
case 6:
state.cur_event.name = "Start"s;
break;
case 7:
state.cur_event.name = "Too Fast"s;
break;
case 8:
state.cur_event.name = "Above Ceiling"s;
break;
case 9:
state.cur_event.name = "Toxic"s;
break;
case 10:
state.cur_event.name = "Hypox"s;
break;
case 11:
state.cur_event.name = "Critical"s;
break;
case 12:
state.cur_event.name = "Sensor Disabled"s;
break;
case 13:
state.cur_event.name = "Sensor Enabled"s;
break;
case 14:
state.cur_event.name = "O2 Backup"s;
break;
case 15:
state.cur_event.name = "Peer Down"s;
break;
case 16:
state.cur_event.name = "HS Down"s;
break;
case 17:
state.cur_event.name = "Inconsistent"s;
break;
case 18:
// key pressed - It should never get in here
// as we ingored it at the parent 'case 5'.
break;
case 19:
// obsolete
state.cur_event.name = "SCR"s;
break;
case 20:
state.cur_event.name = "Above Stop"s;
break;
case 21:
state.cur_event.name = "Safety Miss"s;
break;
case 22:
state.cur_event.name = "Fatal"s;
break;
case 23:
state.cur_event.name = "gaschange"s;
state.cur_event.type = SAMPLE_EVENT_GASCHANGE2;
state.cur_event.value = ptr[7] << 8 ^ ptr[6];
event_end(&state);
break;
case 24:
state.cur_event.name = "gaschange"s;
state.cur_event.type = SAMPLE_EVENT_GASCHANGE2;
state.cur_event.value = ptr[7] << 8 ^ ptr[6];
event_end(&state);
// This is both a mode change and a gas change event
// so we encode it as two separate events.
event_start(&state);
state.cur_event.name = "Change Mode"s;
switch (ptr[8]) {
case 1:
state.cur_event.name += ": OC"s;
break;
case 2:
state.cur_event.name += ": CCR"s;
break;
case 3:
state.cur_event.name += ": mCCR"s;
break;
case 4:
state.cur_event.name += ": Free"s;
break;
case 5:
state.cur_event.name += ": Gauge"s;
break;
case 6:
state.cur_event.name += ": ASCR"s;
break;
case 7:
state.cur_event.name += ": PSCR"s;
break;
default:
break;
}
event_end(&state);
break;
case 25:
// uint16_t solenoid_bitmap = (ptr[7] << 8) + (ptr[6] << 0);
// uint32_t time = (ptr[11] << 24) + (ptr[10] << 16) + (ptr[9] << 8) + (ptr[8] << 0);
state.cur_event.name = format_string_std("CCR O2 solenoid %s", ptr[12] ? "opened": "closed");
break;
case 26:
state.cur_event.name = "User mark"s;
break;
case 27:
state.cur_event.name = format_string_std("%sGF Switch (%d/%d)", ptr[6] ? "Bailout, ": "", ptr[7], ptr[8]);
break;
case 28:
state.cur_event.name = "Peer Up"s;
break;
case 29:
state.cur_event.name = "HS Up"s;
break;
case 30:
state.cur_event.name = format_string_std("CNS %d%%", ptr[6]);
break;
default:
// No values above 30 had any description
break;
}
event_end(&state);
break;
case 6:
/* device configuration */
switch (((ptr[3] & 0x7f) << 3) + ((ptr[2] & 0xe0) >> 5)) {
// Buffer to print extra string into
// Local variables to temporary decode into
struct tm tm;
const char *device;
const char *deep_stops;
case 0: // TEST_CCR_FULL_1
utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm);
add_extra_data(state.cur_dc, "TEST_CCR_FULL_1",
format_string_std("START=%04u-%02u-%02u %02u:%02u:%02u,TEST=%02X%02X%02X%02X,RESULT=%02X%02X%02X%02X",
tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ptr[7], ptr[6], ptr[5], ptr[4], ptr[11], ptr[10], ptr[9], ptr[8]));
break;
case 1: // TEST_CCR_PARTIAL_1
utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm);
add_extra_data(state.cur_dc, "TEST_CCR_PARTIAL_1",
format_string_std("START=%04u-%02u-%02u %02u:%02u:%02u,TEST=%02X%02X%02X%02X,RESULT=%02X%02X%02X%02X",
tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ptr[7], ptr[6], ptr[5], ptr[4], ptr[11], ptr[10], ptr[9], ptr[8]));
break;
case 2: // CFG_OXYGEN_CALIBRATION
utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm);
o2_sensor_calibration_values[0] = (ptr[5] << 8) + ptr[4];
o2_sensor_calibration_values[1] = (ptr[7] << 8) + ptr[6];
o2_sensor_calibration_values[2] = (ptr[9] << 8) + ptr[8];
o2_sensor_calibration_values[3] = (ptr[11] << 8) + ptr[10];
add_extra_data(state.cur_dc, "CFG_OXYGEN_CALIBRATION",
format_string_std("%04u,%04u,%04u,%04u,TIME=%04u-%02u-%02u %02u:%02u:%02u",
o2_sensor_calibration_values[0], o2_sensor_calibration_values[1], o2_sensor_calibration_values[2], o2_sensor_calibration_values[3], tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec));
break;
case 3: // CFG_SERIAL
add_extra_data(state.cur_dc, "CFG_SERIAL",
format_string_std("PRODUCT=%c%c%c%c,SERIAL=%c%c%c%c%c%c%c%c",
ptr[4], ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]));
break;
case 4: // CFG_CONFIG_DECO
switch ((ptr[5] & 0xC0) >> 6) {
case 0:
deep_stops = "none";
break;
case 1:
deep_stops = "Pyle";
break;
case 2:
deep_stops = "Sladek";
break;
default:
deep_stops = "unknown";
break;
}
add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 1",
format_string_std("%s,%s,%s,safety stop required=%s,last_stop=%s,deco_algorithm=%s,stop_rounding=%u,deep_stops=%s",
(ptr[4] & 0x80) ? "imperial" : "metric", (ptr[4] & 0x40) ? "sea" : "fresh", (ptr[4] & 0x30) ? "stops" : "ceiling", (ptr[4] & 0x10) ? "yes" : "no", (ptr[4] & 0x08) ? "6m" : "3m", (ptr[4] & 0x04) ? "VPM" : "Buhlmann+GF", (ptr[4] & 0x03) ? (ptr[4] & 0x03) * 30 : 1, deep_stops));
add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 2",
format_string_std("deep_stop_len=%u min,gas_switch_len=%u min,gf_low=%u,gf_high=%u,gf_low_bailout=%u,gf_high_bailout=%u,ppO2_low=%4.2f,ppO2_high=%4.2f",
(ptr[5] & 0x38) >> 3, ptr[5] & 0x07, ptr[6], ptr[7], ptr[8], ptr[9], ptr[10] / 100.0f, ptr[11] / 100.0f));
add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 3",
format_string_std("alarm_global=%u,alarm_cns=%u,alarm_ppO2=%u,alarm_ceiling=%u,alarm_stop_miss=%u,alarm_decentrate=%u,alarm_ascentrate=%u",
(ptr[12] & 0x80) >> 7, (ptr[12] & 0x40) >> 6, (ptr[12] & 0x20) >> 5, (ptr[12] & 0x10) >> 4, (ptr[12] & 0x08) >> 3, (ptr[12] & 0x04) >> 2, (ptr[12] & 0x02) >> 1));
break;
case 5: // CFG_VERSION
switch (ptr[4]) {
case 0:
device = "FREEDOM";
break;
case 1:
device = "LIBERTY_CU";
break;
case 2:
device = "LIBERTY_HS";
break;
default:
device = "UNKNOWN";
break;
}
add_extra_data(state.cur_dc, "CFG_VERSION",
format_string_std("DEVICE=%s,HW=%d.%d,FW=%d.%d.%d.%d,FLAGS=%04X",
device, ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], (ptr[15] << 24) + (ptr[14] << 16) + (ptr[13] << 8) + (ptr[12]), (ptr[11] << 8) + ptr[10]));
break;
}
break;
case 7:
/* measure record */
switch (ptr[2] >> 5) {
case 1:
/* Record starting battery level */
if (!battery_start.volt1 && !battery_start.volt2) {
battery_start.volt1 = (ptr[5] << 8) + ptr[4];
battery_start.percent1 = ptr[6];
battery_start.volt2 = (ptr[9] << 8) + ptr[8];
battery_start.percent2 = ptr[10];
}
/* Measure Battery, recording the last reading only */
battery_end.volt1 = (ptr[5] << 8) + ptr[4];
battery_end.percent1 = ptr[6];
battery_end.volt2 = (ptr[9] << 8) + ptr[8];
battery_end.percent2 = ptr[10];
break;
case 2:
/* Measure He */
//report_info("%ds he2 cells(0.01 mV): %d %d", time, (ptr[5] << 8) + ptr[4], (ptr[9] << 8) + ptr[8]);
break;
case 3:
/* Measure Oxygen */
//report_info("%d s: o2 cells(0.01 mV): %d %d %d %d", time, (ptr[5] << 8) + ptr[4], (ptr[7] << 8) + ptr[6], (ptr[9] << 8) + ptr[8], (ptr[11] << 8) + ptr[10]);
// [Pa/mV] coeficient O2
// 100 Pa == 1 mbar
sample_start(&state);
state.cur_sample->time.seconds = time;
state.cur_sample->o2sensor[0].mbar = ( ((ptr[5] << 8) + ptr[4]) * o2_sensor_calibration_values[0]) / 10000;
state.cur_sample->o2sensor[1].mbar = ( ((ptr[7] << 8) + ptr[6]) * o2_sensor_calibration_values[1]) / 10000;
state.cur_sample->o2sensor[2].mbar = ( ((ptr[9] << 8) + ptr[8]) * o2_sensor_calibration_values[2]) / 10000;
state.cur_sample->o2sensor[3].mbar = ( ((ptr[11] << 8) + ptr[10]) * o2_sensor_calibration_values[3]) / 10000;
sample_end(&state);
break;
case 4:
/* Measure GPS */
state.cur_location.lat.udeg = (int)((ptr[7] << 24) + (ptr[6] << 16) + (ptr[5] << 8) + (ptr[4] << 0));
state.cur_location.lon.udeg = (int)((ptr[11] << 24) + (ptr[10] << 16) + (ptr[9] << 8) + (ptr[8] << 0));
state.log->sites.create("DLF imported"s, state.cur_location)->add_dive(state.cur_dive.get());
break;
default:
break;
}
break;
case 8:
/* Deco event */
break;
default:
/* Unknown... */
break;
}
}
/* Recording the starting battery status to extra data */
if (battery_start.volt1) {
std::string str = format_string_std("%dmV (%d%%)", battery_start.volt1, battery_start.percent1);
add_extra_data(state.cur_dc, "Battery 1 (start)", str.c_str());
str = format_string_std("%dmV (%d%%)", battery_start.volt2, battery_start.percent2);
add_extra_data(state.cur_dc, "Battery 2 (start)", str.c_str());
}
/* Recording the ending battery status to extra data */
if (battery_end.volt1) {
std::string str = format_string_std("%dmV (%d%%)", battery_end.volt1, battery_end.percent1);
add_extra_data(state.cur_dc, "Battery 1 (end)", str.c_str());
str = format_string_std("%dmV (%d%%)", battery_end.volt2, battery_end.percent2);
add_extra_data(state.cur_dc, "Battery 2 (end)", str.c_str());
}
divecomputer_end(&state);
dive_end(&state);
return 0;
}
void parse_xml_init() void parse_xml_init()
{ {
LIBXML_TEST_VERSION LIBXML_TEST_VERSION

View file

@ -150,7 +150,6 @@ int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *buf, i
int parse_shearwater_cloud_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log); int parse_shearwater_cloud_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log);
int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log); int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log);
int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log); int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log);
int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divelog *log);
std::string trimspace(const char *buffer); std::string trimspace(const char *buffer);
#endif #endif

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
import QtQuick 2.5 import QtQuick
import QtLocation 5.3 import QtLocation
import QtPositioning 5.3 import QtPositioning
import org.subsurfacedivelog.mobile 1.0 import org.subsurfacedivelog.mobile 1.0
Item { Item {
@ -15,7 +15,7 @@ Item {
id: mapHelper id: mapHelper
map: map map: map
editMode: false editMode: false
onSelectedDivesChanged: rootItem.selectedDivesChanged(list) onSelectedDivesChanged: (list) => { rootItem.selectedDivesChanged(list) }
onEditModeChanged: editMessage.isVisible = editMode === true ? 1 : 0 onEditModeChanged: editMessage.isVisible = editMode === true ? 1 : 0
onCoordinatesChanged: {} onCoordinatesChanged: {}
Component.onCompleted: { Component.onCompleted: {
@ -29,7 +29,6 @@ Item {
id: map id: map
anchors.fill: parent anchors.fill: parent
zoomLevel: defaultZoomIn zoomLevel: defaultZoomIn
property var mapType property var mapType
readonly property var defaultCenter: QtPositioning.coordinate(0, 0) readonly property var defaultCenter: QtPositioning.coordinate(0, 0)
readonly property real defaultZoomIn: 12.0 readonly property real defaultZoomIn: 12.0
@ -41,12 +40,46 @@ Item {
property real newZoomOut: 1.0 property real newZoomOut: 1.0
property var clickCoord: QtPositioning.coordinate(0, 0) property var clickCoord: QtPositioning.coordinate(0, 0)
property bool isReady: false property bool isReady: false
Component.onCompleted: isReady = true Component.onCompleted: isReady = true
onZoomLevelChanged: { onZoomLevelChanged: {
if (isReady) if (isReady)
mapHelper.calculateSmallCircleRadius(map.center) mapHelper.calculateSmallCircleRadius(map.center)
} }
property geoCoordinate startCentroid
startCentroid: newCenter
PinchHandler {
id: pinch
target: null
onActiveChanged: if (active) {
map.startCentroid = map.toCoordinate(pinch.centroid.position, false)
}
onScaleChanged: (delta) => {
map.zoomLevel += Math.log2(delta)
map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position)
}
onRotationChanged: (delta) => {
map.bearing -= delta
map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position)
}
grabPermissions: PointerHandler.TakeOverForbidden
}
WheelHandler {
id: wheel
// workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
// Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
// and we don't yet distinguish mice and trackpads on Wayland either
acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland"
? PointerDevice.Mouse | PointerDevice.TouchPad
: PointerDevice.Mouse
rotationScale: 1/120
property: "zoomLevel"
}
DragHandler {
id: drag
target: null
onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y)
}
MapItemView { MapItemView {
id: mapItemView id: mapItemView
@ -67,7 +100,9 @@ Item {
} }
MouseArea { MouseArea {
drag.target: (mapHelper.editMode && model.isSelected) ? mapItem : undefined drag.target: (mapHelper.editMode && model.isSelected) ? mapItem : undefined
anchors.fill: parent anchors.fill: parent
hoverEnabled: true
onClicked: { onClicked: {
if (!mapHelper.editMode && model.divesite) if (!mapHelper.editMode && model.divesite)
mapHelper.selectedLocationChanged(model.divesite) mapHelper.selectedLocationChanged(model.divesite)
@ -122,8 +157,8 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onPressed: { map.stopZoomAnimations(); mouse.accepted = false } onPressed: (mouse) => { map.stopZoomAnimations(); mouse.accepted = false }
onWheel: { map.stopZoomAnimations(); wheel.accepted = false } onWheel: (wheel) => { map.stopZoomAnimations(); wheel.accepted = false }
onDoubleClicked: map.doubleClickHandler(map.toCoordinate(Qt.point(mouseX, mouseY))) onDoubleClicked: map.doubleClickHandler(map.toCoordinate(Qt.point(mouseX, mouseY)))
} }

View file

@ -4,20 +4,20 @@
#include <QDebug> #include <QDebug>
#include <QVector> #include <QVector>
#include "qmlmapwidgethelper.h"
#include "core/divefilter.h" #include "core/divefilter.h"
#include "core/divelist.h" #include "core/divelist.h"
#include "core/divelog.h" #include "core/divelog.h"
#include "core/divesite.h" #include "core/divesite.h"
#include "core/qthelper.h" #include "core/qthelper.h"
#include "core/range.h" #include "core/range.h"
#include "qt-models/maplocationmodel.h" #include "qmlmapwidgethelper.h"
#include "qt-models/divelocationmodel.h" #include "qt-models/divelocationmodel.h"
#include "qt-models/maplocationmodel.h"
#ifndef SUBSURFACE_MOBILE #ifndef SUBSURFACE_MOBILE
#include "desktop-widgets/mapwidget.h" #include "desktop-widgets/mapwidget.h"
#endif #endif
#define SMALL_CIRCLE_RADIUS_PX 26.0 #define SMALL_CIRCLE_RADIUS_PX 26.0
MapWidgetHelper::MapWidgetHelper(QObject *parent) : QObject(parent) MapWidgetHelper::MapWidgetHelper(QObject *parent) : QObject(parent)
{ {
@ -44,7 +44,7 @@ void MapWidgetHelper::centerOnDiveSite(struct dive_site *ds)
} else { } else {
// dive site with GPS // dive site with GPS
m_mapLocationModel->setSelected(ds); m_mapLocationModel->setSelected(ds);
QGeoCoordinate dsCoord (ds->location.lat.udeg * 0.000001, ds->location.lon.udeg * 0.000001); QGeoCoordinate dsCoord(ds->location.lat.udeg * 0.000001, ds->location.lon.udeg * 0.000001);
QMetaObject::invokeMethod(m_map, "centerOnCoordinate", Q_ARG(QVariant, QVariant::fromValue(dsCoord))); QMetaObject::invokeMethod(m_map, "centerOnCoordinate", Q_ARG(QVariant, QVariant::fromValue(dsCoord)));
} }
} }
@ -69,7 +69,7 @@ void MapWidgetHelper::centerOnSelectedDiveSite()
// find the most top-left and bottom-right dive sites on the map coordinate system. // find the most top-left and bottom-right dive sites on the map coordinate system.
qreal minLat = 0.0, minLon = 0.0, maxLat = 0.0, maxLon = 0.0; qreal minLat = 0.0, minLon = 0.0, maxLat = 0.0, maxLon = 0.0;
int count = 0; int count = 0;
for(struct dive_site *dss: selDS) { for (struct dive_site *dss : selDS) {
if (!has_location(&dss->location)) if (!has_location(&dss->location))
continue; continue;
qreal lat = dss->location.lat.udeg * 0.000001; qreal lat = dss->location.lat.udeg * 0.000001;
@ -92,7 +92,7 @@ void MapWidgetHelper::centerOnSelectedDiveSite()
// Pass coordinates to QML, either as a point or as a rectangle. // Pass coordinates to QML, either as a point or as a rectangle.
// If we didn't find any coordinates, do nothing. // If we didn't find any coordinates, do nothing.
if (count == 1) { if (count == 1) {
QGeoCoordinate dsCoord (selDS[0]->location.lat.udeg * 0.000001, selDS[0]->location.lon.udeg * 0.000001); QGeoCoordinate dsCoord(selDS[0]->location.lat.udeg * 0.000001, selDS[0]->location.lon.udeg * 0.000001);
QMetaObject::invokeMethod(m_map, "centerOnCoordinate", Q_ARG(QVariant, QVariant::fromValue(dsCoord))); QMetaObject::invokeMethod(m_map, "centerOnCoordinate", Q_ARG(QVariant, QVariant::fromValue(dsCoord)));
} else if (count > 1) { } else if (count > 1) {
QGeoCoordinate coordTopLeft(minLat, minLon); QGeoCoordinate coordTopLeft(minLat, minLon);
@ -134,7 +134,7 @@ void MapWidgetHelper::selectedLocationChanged(struct dive_site *ds_in)
return; return;
QGeoCoordinate locationCoord = location->coordinate; QGeoCoordinate locationCoord = location->coordinate;
for (auto [idx, dive]: enumerated_range(divelog.dives)) { for (auto [idx, dive] : enumerated_range(divelog.dives)) {
struct dive_site *ds = dive->dive_site; struct dive_site *ds = dive->dive_site;
if (!ds || !ds->has_gps_location()) if (!ds || !ds->has_gps_location())
continue; continue;
@ -151,9 +151,9 @@ void MapWidgetHelper::selectedLocationChanged(struct dive_site *ds_in)
} }
int last; // get latest dive chronologically int last; // get latest dive chronologically
if (!selectedDiveIds.isEmpty()) { if (!selectedDiveIds.isEmpty()) {
last = selectedDiveIds.last(); last = selectedDiveIds.last();
selectedDiveIds.clear(); selectedDiveIds.clear();
selectedDiveIds.append(last); selectedDiveIds.append(last);
} }
#endif #endif
emit selectedDivesChanged(selectedDiveIds); emit selectedDivesChanged(selectedDiveIds);
@ -162,7 +162,7 @@ void MapWidgetHelper::selectedLocationChanged(struct dive_site *ds_in)
void MapWidgetHelper::selectVisibleLocations() void MapWidgetHelper::selectVisibleLocations()
{ {
QList<int> selectedDiveIds; QList<int> selectedDiveIds;
for (auto [idx, dive]: enumerated_range(divelog.dives)) { for (auto [idx, dive] : enumerated_range(divelog.dives)) {
struct dive_site *ds = dive->dive_site; struct dive_site *ds = dive->dive_site;
if (!ds || ds->has_gps_location()) if (!ds || ds->has_gps_location())
continue; continue;
@ -171,7 +171,7 @@ void MapWidgetHelper::selectVisibleLocations()
QGeoCoordinate dsCoord(latitude, longitude); QGeoCoordinate dsCoord(latitude, longitude);
QPointF point; QPointF point;
QMetaObject::invokeMethod(m_map, "fromCoordinate", Q_RETURN_ARG(QPointF, point), QMetaObject::invokeMethod(m_map, "fromCoordinate", Q_RETURN_ARG(QPointF, point),
Q_ARG(QGeoCoordinate, dsCoord)); Q_ARG(QGeoCoordinate, dsCoord));
if (!qIsNaN(point.x())) if (!qIsNaN(point.x()))
#ifndef SUBSURFACE_MOBILE // indices on desktop #ifndef SUBSURFACE_MOBILE // indices on desktop
selectedDiveIds.append(idx); selectedDiveIds.append(idx);
@ -181,9 +181,9 @@ void MapWidgetHelper::selectVisibleLocations()
} }
int last; // get latest dive chronologically int last; // get latest dive chronologically
if (!selectedDiveIds.isEmpty()) { if (!selectedDiveIds.isEmpty()) {
last = selectedDiveIds.last(); last = selectedDiveIds.last();
selectedDiveIds.clear(); selectedDiveIds.clear();
selectedDiveIds.append(last); selectedDiveIds.append(last);
} }
#endif #endif
emit selectedDivesChanged(selectedDiveIds); emit selectedDivesChanged(selectedDiveIds);
@ -205,11 +205,11 @@ void MapWidgetHelper::calculateSmallCircleRadius(QGeoCoordinate coord)
{ {
QPointF point; QPointF point;
QMetaObject::invokeMethod(m_map, "fromCoordinate", Q_RETURN_ARG(QPointF, point), QMetaObject::invokeMethod(m_map, "fromCoordinate", Q_RETURN_ARG(QPointF, point),
Q_ARG(QGeoCoordinate, coord)); Q_ARG(QGeoCoordinate, coord));
QPointF point2(point.x() + SMALL_CIRCLE_RADIUS_PX, point.y()); QPointF point2(point.x() + SMALL_CIRCLE_RADIUS_PX, point.y());
QGeoCoordinate coord2; QGeoCoordinate coord2;
QMetaObject::invokeMethod(m_map, "toCoordinate", Q_RETURN_ARG(QGeoCoordinate, coord2), QMetaObject::invokeMethod(m_map, "toCoordinate", Q_RETURN_ARG(QGeoCoordinate, coord2),
Q_ARG(QPointF, point2)); Q_ARG(QPointF, point2));
m_smallCircleRadius = coord2.distanceTo(coord); m_smallCircleRadius = coord2.distanceTo(coord);
} }
@ -251,8 +251,8 @@ QString MapWidgetHelper::pluginObject()
{ {
QString lang = getUiLanguage().replace('_', '-'); QString lang = getUiLanguage().replace('_', '-');
QString cacheFolder = QString::fromStdString(system_default_directory() + "/googlemaps").replace("\\", "/"); QString cacheFolder = QString::fromStdString(system_default_directory() + "/googlemaps").replace("\\", "/");
return QStringLiteral("import QtQuick 2.0;" return QStringLiteral("import QtQuick;"
"import QtLocation 5.3;" "import QtLocation;"
"Plugin {" "Plugin {"
" id: mapPlugin;" " id: mapPlugin;"
" name: 'googlemaps';" " name: 'googlemaps';"
@ -263,5 +263,6 @@ QString MapWidgetHelper::pluginObject()
" console.warn('MapWidget.qml: cannot find a plugin named: ' + name);" " console.warn('MapWidget.qml: cannot find a plugin named: ' + name);"
" }" " }"
" }" " }"
"}").arg(lang, cacheFolder); "}")
.arg(lang, cacheFolder);
} }

View file

@ -571,7 +571,11 @@ if [ "$QUICK" != "1" ] && [ "$BUILD_DESKTOP$BUILD_MOBILE" != "" ] && ( [[ $QT_VE
# build the googlemaps map plugin # build the googlemaps map plugin
cd "$SRC" cd "$SRC"
./${SRC_DIR}/scripts/get-dep-lib.sh single . googlemaps if [ "$BUILD_WITH_QT6" = "1" ] ; then
./${SRC_DIR}/scripts/get-dep-lib.sh -qt6 single . googlemaps
else
./${SRC_DIR}/scripts/get-dep-lib.sh single . googlemaps
fi
pushd googlemaps pushd googlemaps
mkdir -p build mkdir -p build
mkdir -p J10build mkdir -p J10build

View file

@ -1,7 +1,10 @@
# Build the image using the --build-arg option, e.g.: # Build the image using the --build-arg option, e.g.:
# docker build -t boret/myimage:0.1 --build-arg=mxe_sha=123ABC456 . # docker build -t boret/myimage:0.1 --build-arg=mxe_sha=123ABC456 .
FROM ubuntu:24.04 as base # We need to stick with 22.04 for now because the latest MXE version
# (db430dc676e6f5d77604af150b8acc1403af4fd7) does not build a working
# version of QtWebKit, which breaks printing in Subsurface.
FROM ubuntu:22.04 as base
# update and set up the packages we need for the build # update and set up the packages we need for the build
RUN apt-get update && \ RUN apt-get update && \
@ -54,8 +57,9 @@ RUN apt-get install -y \
xz-utils \ xz-utils \
scons scons
# very often master is broken, so we pass in a known good SHA # Default to 'master' if no build argument is passed in
ARG mxe_sha=d6377b2f2334694dbb040294fd0d848327e63328 ARG mxe_sha=master
# Very often master is broken, so we pass in a known good SHA
ENV _ver=${mxe_sha} ENV _ver=${mxe_sha}
WORKDIR /win WORKDIR /win

View file

@ -79,13 +79,17 @@ curl_download_library() {
fi fi
} }
QT6="0"
# deal with all the command line arguments # deal with all the command line arguments
if [ "$1" == "-qt6" ] ; then
shift
QT6="1"
fi
if [ $# -ne 2 ] && [ $# -ne 3 ] ; then if [ $# -ne 2 ] && [ $# -ne 3 ] ; then
echo "wrong number of parameters, format:" echo "wrong number of parameters, format:"
echo "get-dep-lib.sh <platform> <install dir>" echo "get-dep-lib.sh [ -qt6 ] <platform> <install dir>"
echo "get-dep-lib.sh single <install dir> <lib>" echo "get-dep-lib.sh [ -qt6 ] single <install dir> <lib>"
echo "get-dep-lib.sh singleAndroid <install dir> <lib>" echo "get-dep-lib.sh [ -qt6 ] singleAndroid <install dir> <lib>"
echo "where" echo "where"
echo "<platform> is one of scripts, ios or android" echo "<platform> is one of scripts, ios or android"
echo "(the name of the directory where build.sh resides)" echo "(the name of the directory where build.sh resides)"
@ -169,7 +173,11 @@ for package in "${PACKAGES[@]}" ; do
git_checkout_library breeze-icons $CURRENT_BREEZE_ICONS https://github.com/kde/breeze-icons.git git_checkout_library breeze-icons $CURRENT_BREEZE_ICONS https://github.com/kde/breeze-icons.git
;; ;;
googlemaps) googlemaps)
git_checkout_library googlemaps master https://github.com/Subsurface/googlemaps.git if [ "$QT6" = "1" ] ; then
git_checkout_library googlemaps master https://github.com/vladest/googlemaps.git
else
git_checkout_library googlemaps master https://github.com/Subsurface/googlemaps.git
fi
;; ;;
hidapi) hidapi)
git_checkout_library hidapi master https://github.com/libusb/hidapi.git git_checkout_library hidapi master https://github.com/libusb/hidapi.git

View file

@ -865,17 +865,17 @@ void TestPlan::testVpmbMetricRepeat()
// check minimum gas result // check minimum gas result
dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; });
QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 80l); QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 85l);
// print first ceiling // print first ceiling
printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001);
QVERIFY(dive.dcs[0].events.size() >= 3); QVERIFY(dive.dcs[0].events.size() >= 3);
// check first gas change to 21/35 at 66m // check first gas change to 21/35 at 63m
struct event *ev = &dive.dcs[0].events[0]; struct event *ev = &dive.dcs[0].events[0];
QVERIFY(ev != NULL); QVERIFY(ev != NULL);
QCOMPARE(ev->gas.index, 1); QCOMPARE(ev->gas.index, 1);
QCOMPARE(ev->gas.mix.o2.permille, 210); QCOMPARE(ev->gas.mix.o2.permille, 210);
QCOMPARE(ev->gas.mix.he.permille, 350); QCOMPARE(ev->gas.mix.he.permille, 350);
QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 66000); QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 63000);
// check second gas change to EAN50 at 21m // check second gas change to EAN50 at 21m
ev = &dive.dcs[0].events[1]; ev = &dive.dcs[0].events[1];
QCOMPARE(ev->gas.index, 2); QCOMPARE(ev->gas.index, 2);