2017-04-27 18:24:53 +00:00
|
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2013-06-17 22:58:26 +00:00
|
|
|
|
#include "qthelper.h"
|
2020-05-01 11:43:52 +00:00
|
|
|
|
#include "dive.h"
|
2018-08-20 09:48:49 +00:00
|
|
|
|
#include "core/settings/qPrefLanguage.h"
|
|
|
|
|
#include "core/settings/qPrefUpdateManager.h"
|
cleanup: invert control-flow when resetting the core structures
To reset the core data structures, the mobile and desktop UIs
were calling into the dive-list models, which then reset the
core data structures, themselves and the unrelated
locationinformation model. The UI code then reset various other
things, such as the TankInformation model or the map. . This was
unsatisfying from a control-flow perspective, as the models should
display the core data, not act on it. Moreover, this meant lots
of intricate intermodule-dependencies.
Thus, straighten up the control flow: give the C core the
possibility to send a "all data reset" event. And do that
in those functions that reset the core data structures.
Let each module react to this event by itself. This removes
inter-module dependencies. For example, the MainWindow now
doesn't have to reset the TankInfoModel or the MapWidget.
Then, to reset the core data structures, let the UI code
simply directly call the respective core functions.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2020-05-04 22:12:36 +00:00
|
|
|
|
#include "core/subsurface-qt/divelistnotifier.h"
|
2018-05-11 15:25:41 +00:00
|
|
|
|
#include "subsurface-string.h"
|
2014-07-16 16:54:39 +00:00
|
|
|
|
#include "gettextfromc.h"
|
2014-06-10 05:45:44 +00:00
|
|
|
|
#include "statistics.h"
|
2015-02-15 04:01:33 +00:00
|
|
|
|
#include "membuffer.h"
|
2015-02-23 17:09:48 +00:00
|
|
|
|
#include "subsurfacesysinfo.h"
|
|
|
|
|
#include "version.h"
|
2019-08-05 17:41:15 +00:00
|
|
|
|
#include "errorhelper.h"
|
2019-08-05 18:43:06 +00:00
|
|
|
|
#include "planner.h"
|
2020-05-01 12:07:59 +00:00
|
|
|
|
#include "subsurface-time.h"
|
2015-02-23 17:09:48 +00:00
|
|
|
|
#include "gettextfromc.h"
|
2019-05-10 17:51:43 +00:00
|
|
|
|
#include "applicationstate.h"
|
2018-04-29 21:10:32 +00:00
|
|
|
|
#include "metadata.h"
|
2018-03-10 13:15:50 +00:00
|
|
|
|
#include "exif.h"
|
|
|
|
|
#include "file.h"
|
2020-04-10 07:42:14 +00:00
|
|
|
|
#include "picture.h"
|
2020-09-29 22:01:24 +00:00
|
|
|
|
#include "selection.h"
|
2019-05-30 16:29:36 +00:00
|
|
|
|
#include "tag.h"
|
2019-05-31 14:09:14 +00:00
|
|
|
|
#include "trip.h"
|
2018-03-10 13:15:50 +00:00
|
|
|
|
#include "imagedownloader.h"
|
2020-10-17 18:15:23 +00:00
|
|
|
|
#include "xmlparams.h"
|
2014-07-13 21:36:35 +00:00
|
|
|
|
#include <QFile>
|
2013-10-05 16:48:26 +00:00
|
|
|
|
#include <QRegExp>
|
2013-12-25 00:26:00 +00:00
|
|
|
|
#include <QDir>
|
2013-10-05 16:48:26 +00:00
|
|
|
|
#include <QDebug>
|
2015-02-08 19:35:50 +00:00
|
|
|
|
#include <QStandardPaths>
|
2015-02-15 04:01:33 +00:00
|
|
|
|
#include <QJsonDocument>
|
2015-06-12 13:24:48 +00:00
|
|
|
|
#include <QNetworkProxy>
|
2015-02-23 17:09:48 +00:00
|
|
|
|
#include <QDateTime>
|
2015-02-26 13:39:42 +00:00
|
|
|
|
#include <QImageReader>
|
|
|
|
|
#include <QtConcurrent>
|
2015-07-26 05:08:25 +00:00
|
|
|
|
#include <QFont>
|
|
|
|
|
#include <QApplication>
|
2015-10-27 11:08:57 +00:00
|
|
|
|
#include <QTextDocument>
|
2018-04-29 21:10:32 +00:00
|
|
|
|
#include <QProgressDialog> // TODO: remove with convertThumbnails()
|
2018-02-21 21:35:50 +00:00
|
|
|
|
#include <cstdarg>
|
|
|
|
|
#include <cstdint>
|
2015-02-15 04:01:33 +00:00
|
|
|
|
|
2014-04-17 15:21:39 +00:00
|
|
|
|
#include <libxslt/documents.h>
|
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
|
const char *existing_filename;
|
|
|
|
|
static QLocale loc;
|
|
|
|
|
|
2019-04-01 21:15:23 +00:00
|
|
|
|
static inline QString degreeSigns()
|
|
|
|
|
{
|
2020-03-05 16:57:02 +00:00
|
|
|
|
return QStringLiteral("dD\u00b0");
|
2019-04-01 21:15:23 +00:00
|
|
|
|
}
|
2013-06-17 22:58:26 +00:00
|
|
|
|
|
2013-10-04 18:39:33 +00:00
|
|
|
|
QString weight_string(int weight_in_grams)
|
|
|
|
|
{
|
|
|
|
|
QString str;
|
|
|
|
|
if (get_units()->weight == units::KG) {
|
2017-08-07 11:07:36 +00:00
|
|
|
|
double kg = (double) weight_in_grams / 1000.0;
|
2018-02-19 20:55:18 +00:00
|
|
|
|
str = QString("%L1").arg(kg, 0, 'f', kg >= 20.0 ? 0 : 1);
|
2013-10-04 18:39:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
double lbs = grams_to_lbs(weight_in_grams);
|
2018-02-19 20:55:18 +00:00
|
|
|
|
str = QString("%L1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1);
|
2013-10-04 18:39:33 +00:00
|
|
|
|
}
|
2018-02-17 20:21:16 +00:00
|
|
|
|
return str;
|
2013-10-04 18:39:33 +00:00
|
|
|
|
}
|
2013-10-05 16:48:26 +00:00
|
|
|
|
|
2015-07-14 18:35:04 +00:00
|
|
|
|
QString distance_string(int distanceInMeters)
|
|
|
|
|
{
|
|
|
|
|
QString str;
|
|
|
|
|
if(get_units()->length == units::METERS) {
|
|
|
|
|
if (distanceInMeters >= 1000)
|
2018-06-17 18:42:19 +00:00
|
|
|
|
str = gettextFromC::tr("%1km").arg(distanceInMeters / 1000);
|
2015-07-14 18:35:04 +00:00
|
|
|
|
else
|
2018-06-17 18:42:19 +00:00
|
|
|
|
str = gettextFromC::tr("%1m").arg(distanceInMeters);
|
2015-07-14 18:35:04 +00:00
|
|
|
|
} else {
|
|
|
|
|
double miles = m_to_mile(distanceInMeters);
|
|
|
|
|
if (miles >= 1.0)
|
2018-06-17 18:42:19 +00:00
|
|
|
|
str = gettextFromC::tr("%1mi").arg((int)miles);
|
2015-07-14 18:35:04 +00:00
|
|
|
|
else
|
2018-06-17 18:42:19 +00:00
|
|
|
|
str = gettextFromC::tr("%1yd").arg((int)(miles * 1760));
|
2015-07-14 18:35:04 +00:00
|
|
|
|
}
|
|
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-25 08:05:47 +00:00
|
|
|
|
QString printGPSCoords(const location_t *location)
|
2014-05-12 16:19:57 +00:00
|
|
|
|
{
|
2019-03-17 20:55:11 +00:00
|
|
|
|
int lat = location->lat.udeg;
|
|
|
|
|
int lon = location->lon.udeg;
|
2014-05-12 16:19:57 +00:00
|
|
|
|
unsigned int latdeg, londeg;
|
|
|
|
|
unsigned int latmin, lonmin;
|
|
|
|
|
double latsec, lonsec;
|
|
|
|
|
QString lath, lonh, result;
|
|
|
|
|
|
2019-03-17 20:55:11 +00:00
|
|
|
|
if (!has_location(location))
|
2019-03-25 08:05:47 +00:00
|
|
|
|
return QString();
|
2014-05-12 16:19:57 +00:00
|
|
|
|
|
2015-05-20 10:25:46 +00:00
|
|
|
|
if (prefs.coordinates_traditional) {
|
2018-06-17 18:42:19 +00:00
|
|
|
|
lath = lat >= 0 ? gettextFromC::tr("N") : gettextFromC::tr("S");
|
|
|
|
|
lonh = lon >= 0 ? gettextFromC::tr("E") : gettextFromC::tr("W");
|
2015-05-20 10:25:46 +00:00
|
|
|
|
lat = abs(lat);
|
|
|
|
|
lon = abs(lon);
|
|
|
|
|
latdeg = lat / 1000000U;
|
|
|
|
|
londeg = lon / 1000000U;
|
|
|
|
|
latmin = (lat % 1000000U) * 60U;
|
|
|
|
|
lonmin = (lon % 1000000U) * 60U;
|
|
|
|
|
latsec = (latmin % 1000000) * 60;
|
|
|
|
|
lonsec = (lonmin % 1000000) * 60;
|
2019-05-11 20:45:49 +00:00
|
|
|
|
result.sprintf("%u°%02d\'%06.3f\"%s %u°%02d\'%06.3f\"%s",
|
|
|
|
|
latdeg, latmin / 1000000, latsec / 1000000, qPrintable(lath),
|
|
|
|
|
londeg, lonmin / 1000000, lonsec / 1000000, qPrintable(lonh));
|
2015-05-20 10:25:46 +00:00
|
|
|
|
} else {
|
|
|
|
|
result.sprintf("%f %f", (double) lat / 1000000.0, (double) lon / 1000000.0);
|
|
|
|
|
}
|
2019-03-25 08:05:47 +00:00
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" char *printGPSCoordsC(const location_t *location)
|
|
|
|
|
{
|
|
|
|
|
return copy_qstring(printGPSCoords(location));
|
2014-05-12 16:19:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-23 12:38:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* Try to parse in a generic manner a coordinate.
|
|
|
|
|
*/
|
2020-02-03 22:24:18 +00:00
|
|
|
|
static bool parseCoord(const QString &txt, int &pos, const QString &positives,
|
|
|
|
|
const QString &negatives, const QString &others,
|
|
|
|
|
double &value)
|
2015-02-04 08:30:24 +00:00
|
|
|
|
{
|
|
|
|
|
bool numberDefined = false, degreesDefined = false,
|
|
|
|
|
minutesDefined = false, secondsDefined = false;
|
|
|
|
|
double number = 0.0;
|
|
|
|
|
int posBeforeNumber = pos;
|
|
|
|
|
int sign = 0;
|
|
|
|
|
value = 0.0;
|
|
|
|
|
while (pos < txt.size()) {
|
|
|
|
|
if (txt[pos].isDigit()) {
|
|
|
|
|
if (numberDefined)
|
|
|
|
|
return false;
|
|
|
|
|
QRegExp numberRe("(\\d+(?:[\\.,]\\d+)?).*");
|
|
|
|
|
if (!numberRe.exactMatch(txt.mid(pos)))
|
|
|
|
|
return false;
|
|
|
|
|
number = numberRe.cap(1).toDouble();
|
|
|
|
|
numberDefined = true;
|
|
|
|
|
posBeforeNumber = pos;
|
|
|
|
|
pos += numberRe.cap(1).size() - 1;
|
2015-02-23 12:38:41 +00:00
|
|
|
|
} else if (positives.indexOf(txt[pos]) >= 0) {
|
2015-02-04 08:30:24 +00:00
|
|
|
|
if (sign != 0)
|
|
|
|
|
return false;
|
|
|
|
|
sign = 1;
|
|
|
|
|
if (degreesDefined || numberDefined) {
|
|
|
|
|
//sign after the degrees =>
|
|
|
|
|
//at the end of the coordinate
|
|
|
|
|
++pos;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-02-23 12:38:41 +00:00
|
|
|
|
} else if (negatives.indexOf(txt[pos]) >= 0) {
|
2015-02-04 08:30:24 +00:00
|
|
|
|
if (sign != 0) {
|
|
|
|
|
if (others.indexOf(txt[pos]) >= 0)
|
|
|
|
|
//special case for the '-' sign => next coordinate
|
|
|
|
|
break;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
sign = -1;
|
|
|
|
|
if (degreesDefined || numberDefined) {
|
|
|
|
|
//sign after the degrees =>
|
|
|
|
|
//at the end of the coordinate
|
|
|
|
|
++pos;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-02-23 12:38:41 +00:00
|
|
|
|
} else if (others.indexOf(txt[pos]) >= 0) {
|
2015-02-04 08:30:24 +00:00
|
|
|
|
//we are at the next coordinate.
|
|
|
|
|
break;
|
2019-04-01 21:15:23 +00:00
|
|
|
|
} else if (degreeSigns().indexOf(txt[pos]) >= 0 ||
|
2015-02-23 12:38:41 +00:00
|
|
|
|
(txt[pos].isSpace() && !degreesDefined && numberDefined)) {
|
2015-02-04 08:30:24 +00:00
|
|
|
|
if (!numberDefined)
|
|
|
|
|
return false;
|
|
|
|
|
if (degreesDefined) {
|
|
|
|
|
//next coordinate => need to put back the number
|
|
|
|
|
pos = posBeforeNumber;
|
|
|
|
|
numberDefined = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
value += number;
|
|
|
|
|
numberDefined = false;
|
|
|
|
|
degreesDefined = true;
|
2015-02-23 12:38:41 +00:00
|
|
|
|
} else if (txt[pos] == '\'' || (txt[pos].isSpace() && !minutesDefined && numberDefined)) {
|
2015-02-04 08:30:24 +00:00
|
|
|
|
if (!numberDefined || minutesDefined)
|
|
|
|
|
return false;
|
|
|
|
|
value += number / 60.0;
|
|
|
|
|
numberDefined = false;
|
|
|
|
|
minutesDefined = true;
|
2015-02-23 12:38:41 +00:00
|
|
|
|
} else if (txt[pos] == '"' || (txt[pos].isSpace() && !secondsDefined && numberDefined)) {
|
2015-02-04 08:30:24 +00:00
|
|
|
|
if (!numberDefined || secondsDefined)
|
|
|
|
|
return false;
|
|
|
|
|
value += number / 3600.0;
|
|
|
|
|
numberDefined = false;
|
|
|
|
|
secondsDefined = true;
|
2015-10-07 16:19:58 +00:00
|
|
|
|
} else if ((numberDefined || minutesDefined || secondsDefined) &&
|
|
|
|
|
(txt[pos] == ',' || txt[pos] == ';')) {
|
|
|
|
|
// next coordinate coming up
|
|
|
|
|
// eat the ',' and any subsequent white space
|
2017-01-14 17:09:58 +00:00
|
|
|
|
while (++pos < txt.size() && txt[pos].isSpace())
|
2015-10-07 16:19:58 +00:00
|
|
|
|
/* nothing */ ;
|
|
|
|
|
break;
|
2015-02-04 08:30:24 +00:00
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
++pos;
|
|
|
|
|
}
|
2020-02-04 08:27:35 +00:00
|
|
|
|
if (!degreesDefined && numberDefined)
|
2015-02-04 08:30:24 +00:00
|
|
|
|
value = number; //just a single number => degrees
|
2020-02-04 08:27:35 +00:00
|
|
|
|
else if (!minutesDefined && numberDefined)
|
2015-02-23 12:38:41 +00:00
|
|
|
|
value += number / 60.0;
|
2020-02-04 08:27:35 +00:00
|
|
|
|
else if (!secondsDefined && numberDefined)
|
2015-02-23 12:38:41 +00:00
|
|
|
|
value += number / 3600.0;
|
2020-02-04 08:27:35 +00:00
|
|
|
|
else if (numberDefined)
|
2015-02-04 08:30:24 +00:00
|
|
|
|
return false;
|
|
|
|
|
if (sign == -1) value *= -1.0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-23 12:38:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* Parse special coordinate formats that cannot be handled by parseCoord.
|
|
|
|
|
*/
|
2020-02-03 22:24:18 +00:00
|
|
|
|
static bool parseSpecialCoords(const QString &txt, double &latitude, double &longitude) {
|
2015-05-24 20:59:00 +00:00
|
|
|
|
QRegExp xmlFormat("(-?\\d+(?:\\.\\d+)?),?\\s+(-?\\d+(?:\\.\\d+)?)");
|
2015-02-23 12:38:41 +00:00
|
|
|
|
if (xmlFormat.exactMatch(txt)) {
|
|
|
|
|
latitude = xmlFormat.cap(1).toDouble();
|
|
|
|
|
longitude = xmlFormat.cap(2).toDouble();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
bool parseGpsText(const QString &gps_text, double *latitude, double *longitude)
|
2013-10-05 16:48:26 +00:00
|
|
|
|
{
|
2018-06-17 18:42:19 +00:00
|
|
|
|
static const QString POS_LAT = QString("+N") + gettextFromC::tr("N");
|
|
|
|
|
static const QString NEG_LAT = QString("-S") + gettextFromC::tr("S");
|
|
|
|
|
static const QString POS_LON = QString("+E") + gettextFromC::tr("E");
|
|
|
|
|
static const QString NEG_LON = QString("-W") + gettextFromC::tr("W");
|
2015-02-23 12:38:41 +00:00
|
|
|
|
|
2020-02-04 09:37:00 +00:00
|
|
|
|
// Remove the useless spaces (but keep the ones separating numbers)
|
|
|
|
|
// and normalize different ways of writing separators.
|
2015-02-23 12:38:41 +00:00
|
|
|
|
static const QRegExp SPACE_CLEANER("\\s*([" + POS_LAT + NEG_LAT + POS_LON +
|
2019-04-01 21:15:23 +00:00
|
|
|
|
NEG_LON + degreeSigns() + "'\"\\s])\\s*");
|
2020-02-04 09:37:00 +00:00
|
|
|
|
const QString normalized = gps_text.trimmed().toUpper().
|
|
|
|
|
replace(SPACE_CLEANER, "\\1").
|
|
|
|
|
replace(QStringLiteral("′"), "'").
|
|
|
|
|
replace(QStringLiteral("’"), "'").
|
|
|
|
|
replace(QStringLiteral("''"), "\"").
|
|
|
|
|
replace(QStringLiteral("″"), "\"");
|
2015-02-23 12:38:41 +00:00
|
|
|
|
|
|
|
|
|
if (normalized.isEmpty()) {
|
2013-10-05 16:48:26 +00:00
|
|
|
|
*latitude = 0.0;
|
|
|
|
|
*longitude = 0.0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2015-02-23 12:38:41 +00:00
|
|
|
|
if (parseSpecialCoords(normalized, *latitude, *longitude))
|
|
|
|
|
return true;
|
2015-02-04 08:30:24 +00:00
|
|
|
|
int pos = 0;
|
2015-02-23 12:38:41 +00:00
|
|
|
|
return parseCoord(normalized, pos, POS_LAT, NEG_LAT, POS_LON + NEG_LON, *latitude) &&
|
|
|
|
|
parseCoord(normalized, pos, POS_LON, NEG_LON, "", *longitude) &&
|
|
|
|
|
pos == normalized.size();
|
2013-10-05 16:48:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-13 06:24:12 +00:00
|
|
|
|
#if 0 // we'll need something like this for the dive site management, eventually
|
2014-06-03 01:13:50 +00:00
|
|
|
|
bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out)
|
2013-10-05 16:48:26 +00:00
|
|
|
|
{
|
2018-10-20 18:12:15 +00:00
|
|
|
|
location_t location;
|
2014-06-03 01:13:50 +00:00
|
|
|
|
bool ignore;
|
|
|
|
|
bool *parsed = parsed_out ?: &ignore;
|
2014-07-30 16:46:21 +00:00
|
|
|
|
*parsed = true;
|
2013-10-05 16:48:26 +00:00
|
|
|
|
|
|
|
|
|
/* if we have a master and the dive's gps address is different from it,
|
|
|
|
|
* don't change the dive */
|
2018-10-20 18:12:15 +00:00
|
|
|
|
if (master && !same_location(&master->location, &dive->location))
|
2013-10-05 16:48:26 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2018-10-20 18:12:15 +00:00
|
|
|
|
if (!(*parsed = parseGpsText(gps_text, location)))
|
2013-10-05 16:48:26 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/* if dive gps didn't change, nothing changed */
|
2018-10-20 18:12:15 +00:00
|
|
|
|
if (same_location(&dive->location, location))
|
2013-10-05 16:48:26 +00:00
|
|
|
|
return false;
|
|
|
|
|
/* ok, update the dive and mark things changed */
|
2018-10-20 18:12:15 +00:00
|
|
|
|
dive->location = location;
|
2013-10-05 16:48:26 +00:00
|
|
|
|
return true;
|
2013-11-26 17:44:18 +00:00
|
|
|
|
}
|
2015-02-13 06:24:12 +00:00
|
|
|
|
#endif
|
2013-10-05 16:48:26 +00:00
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
QList<int> getDivesInTrip(dive_trip_t *trip)
|
2013-11-26 17:44:18 +00:00
|
|
|
|
{
|
|
|
|
|
QList<int> ret;
|
2014-05-11 01:12:40 +00:00
|
|
|
|
int i;
|
|
|
|
|
struct dive *d;
|
|
|
|
|
for_each_dive (i, d) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
if (d->divetrip == trip) {
|
2013-11-26 17:44:18 +00:00
|
|
|
|
ret.push_back(get_divenr(d));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
2013-10-05 16:48:26 +00:00
|
|
|
|
}
|
2014-01-07 01:30:01 +00:00
|
|
|
|
|
2014-04-17 15:21:39 +00:00
|
|
|
|
static xmlDocPtr get_stylesheet_doc(const xmlChar *uri, xmlDictPtr, int, void *, xsltLoadType)
|
|
|
|
|
{
|
|
|
|
|
QFile f(QLatin1String(":/xslt/") + (const char *)uri);
|
2015-03-15 01:07:20 +00:00
|
|
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
|
|
|
if (verbose > 0) {
|
2016-12-27 12:31:30 +00:00
|
|
|
|
qDebug() << "cannot open stylesheet" << QLatin1String(":/xslt/") + (const char *)uri << f.errorString();
|
2015-03-15 01:07:20 +00:00
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-04-17 15:21:39 +00:00
|
|
|
|
/* Load and parse the data */
|
|
|
|
|
QByteArray source = f.readAll();
|
|
|
|
|
|
|
|
|
|
xmlDocPtr doc = xmlParseMemory(source, source.size());
|
|
|
|
|
return doc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" xsltStylesheetPtr get_stylesheet(const char *name)
|
|
|
|
|
{
|
|
|
|
|
// this needs to be done only once, but doesn't hurt to run every time
|
|
|
|
|
xsltSetLoaderFunc(get_stylesheet_doc);
|
|
|
|
|
|
|
|
|
|
// get main document:
|
|
|
|
|
xmlDocPtr doc = get_stylesheet_doc((const xmlChar *)name, NULL, 0, NULL, XSLT_LOAD_START);
|
|
|
|
|
if (!doc)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
// xsltSetGenericErrorFunc(stderr, NULL);
|
|
|
|
|
xsltStylesheetPtr xslt = xsltParseStylesheetDoc(doc);
|
|
|
|
|
if (!xslt) {
|
|
|
|
|
xmlFreeDoc(doc);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return xslt;
|
|
|
|
|
}
|
2014-06-02 21:28:02 +00:00
|
|
|
|
|
2015-09-23 12:08:35 +00:00
|
|
|
|
extern "C" char *move_away(const char *old_path)
|
|
|
|
|
{
|
|
|
|
|
if (verbose > 1)
|
|
|
|
|
qDebug() << "move away" << old_path;
|
2015-09-26 16:34:38 +00:00
|
|
|
|
QDir oldDir(old_path);
|
|
|
|
|
QDir newDir;
|
2015-09-23 12:08:35 +00:00
|
|
|
|
QString newPath;
|
|
|
|
|
int i = 0;
|
|
|
|
|
do {
|
|
|
|
|
newPath = QString(old_path) + QString(".%1").arg(++i);
|
2015-09-26 16:34:38 +00:00
|
|
|
|
newDir.setPath(newPath);
|
|
|
|
|
} while(newDir.exists());
|
2015-09-23 12:08:35 +00:00
|
|
|
|
if (verbose > 1)
|
|
|
|
|
qDebug() << "renaming to" << newPath;
|
2015-09-26 16:34:38 +00:00
|
|
|
|
if (!oldDir.rename(old_path, newPath)) {
|
|
|
|
|
if (verbose)
|
|
|
|
|
qDebug() << "rename of" << old_path << "to" << newPath << "failed";
|
2015-09-26 16:35:21 +00:00
|
|
|
|
// this next one we only try on Windows... if we are on a different platform
|
|
|
|
|
// we simply give up and return an empty string
|
|
|
|
|
#ifdef WIN32
|
|
|
|
|
if (subsurface_dir_rename(old_path, qPrintable(newPath)) == 0)
|
|
|
|
|
#endif
|
|
|
|
|
return strdup("");
|
2015-09-23 12:08:35 +00:00
|
|
|
|
}
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(newPath);
|
2015-09-23 12:08:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-08-17 17:42:47 +00:00
|
|
|
|
extern "C" char *get_file_name(const char *fileName)
|
2014-07-13 21:36:35 +00:00
|
|
|
|
{
|
2014-08-17 17:42:47 +00:00
|
|
|
|
QFileInfo fileInfo(fileName);
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(fileInfo.fileName());
|
2014-07-13 21:36:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-21 18:55:40 +00:00
|
|
|
|
extern "C" void copy_image_and_overwrite(const char *cfileName, const char *path, const char *cnewName)
|
2014-07-13 21:36:35 +00:00
|
|
|
|
{
|
2015-06-21 18:55:40 +00:00
|
|
|
|
QString fileName(cfileName);
|
|
|
|
|
QString newName(path);
|
|
|
|
|
newName += cnewName;
|
2014-07-13 21:36:35 +00:00
|
|
|
|
QFile file(newName);
|
|
|
|
|
if (file.exists())
|
|
|
|
|
file.remove();
|
2015-06-21 14:43:35 +00:00
|
|
|
|
if (!QFile::copy(fileName, newName))
|
|
|
|
|
qDebug() << "copy of" << fileName << "to" << newName << "failed";
|
2014-07-13 21:36:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-06-10 05:45:44 +00:00
|
|
|
|
static bool lessThan(const QPair<QString, int> &a, const QPair<QString, int> &b)
|
|
|
|
|
{
|
|
|
|
|
return a.second < b.second;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-03 21:05:44 +00:00
|
|
|
|
QVector<QPair<QString, int>> selectedDivesGasUsed()
|
2014-06-10 05:45:44 +00:00
|
|
|
|
{
|
2020-09-29 22:01:24 +00:00
|
|
|
|
int j;
|
2014-06-10 05:45:44 +00:00
|
|
|
|
QMap<QString, int> gasUsed;
|
2020-09-29 22:01:24 +00:00
|
|
|
|
for (dive *d: getDiveSelection()) {
|
2019-06-27 05:42:09 +00:00
|
|
|
|
volume_t *diveGases = get_gas_used(d);
|
2019-08-04 16:44:57 +00:00
|
|
|
|
for (j = 0; j < d->cylinders.nr; j++) {
|
2014-06-10 05:45:44 +00:00
|
|
|
|
if (diveGases[j].mliter) {
|
2019-08-04 20:13:49 +00:00
|
|
|
|
QString gasName = gasname(get_cylinder(d, j)->gasmix);
|
2014-06-10 05:45:44 +00:00
|
|
|
|
gasUsed[gasName] += diveGases[j].mliter;
|
|
|
|
|
}
|
2019-06-27 05:42:09 +00:00
|
|
|
|
}
|
|
|
|
|
free(diveGases);
|
2014-06-10 05:45:44 +00:00
|
|
|
|
}
|
2019-04-03 21:05:44 +00:00
|
|
|
|
QVector<QPair<QString, int>> gasUsedOrdered;
|
|
|
|
|
gasUsedOrdered.reserve(gasUsed.size());
|
|
|
|
|
for (auto it = gasUsed.cbegin(); it != gasUsed.cend(); ++it)
|
|
|
|
|
gasUsedOrdered.append(qMakePair(it.key(), it.value()));
|
2019-04-03 18:21:53 +00:00
|
|
|
|
std::sort(gasUsedOrdered.begin(), gasUsedOrdered.end(), lessThan);
|
2019-04-03 21:05:44 +00:00
|
|
|
|
|
|
|
|
|
return gasUsedOrdered;
|
2014-06-10 05:45:44 +00:00
|
|
|
|
}
|
2015-02-15 04:01:33 +00:00
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
|
QString getUserAgent()
|
|
|
|
|
{
|
|
|
|
|
QString arch;
|
|
|
|
|
// fill in the system data - use ':' as separator
|
|
|
|
|
// replace all other ':' with ' ' so that this is easy to parse
|
2015-11-18 22:44:07 +00:00
|
|
|
|
#ifdef SUBSURFACE_MOBILE
|
2016-03-05 20:53:38 +00:00
|
|
|
|
QString userAgent = QString("Subsurface-mobile:%1(%2):").arg(subsurface_mobile_version()).arg(subsurface_canonical_version());
|
2015-11-18 22:44:07 +00:00
|
|
|
|
#else
|
2015-12-20 15:59:50 +00:00
|
|
|
|
QString userAgent = QString("Subsurface:%1:").arg(subsurface_canonical_version());
|
2015-11-18 22:44:07 +00:00
|
|
|
|
#endif
|
2015-02-23 17:09:48 +00:00
|
|
|
|
userAgent.append(SubsurfaceSysInfo::prettyOsName().replace(':', ' ') + ":");
|
|
|
|
|
arch = SubsurfaceSysInfo::buildCpuArchitecture().replace(':', ' ');
|
|
|
|
|
userAgent.append(arch);
|
|
|
|
|
if (arch == "i386")
|
|
|
|
|
userAgent.append("/" + SubsurfaceSysInfo::currentCpuArchitecture());
|
2020-03-22 15:43:21 +00:00
|
|
|
|
userAgent.append(":" + getUiLanguage());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
return userAgent;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-20 15:59:50 +00:00
|
|
|
|
extern "C" const char *subsurface_user_agent()
|
|
|
|
|
{
|
|
|
|
|
static QString uA = getUserAgent();
|
|
|
|
|
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(uA);
|
2015-12-20 15:59:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-22 15:43:21 +00:00
|
|
|
|
QString getUiLanguage()
|
|
|
|
|
{
|
|
|
|
|
return prefs.locale.lang_locale;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-28 01:13:33 +00:00
|
|
|
|
/* TOOD: Move this to SettingsObjectWrapper, and also fix this complexity.
|
|
|
|
|
* gezus.
|
|
|
|
|
*/
|
2020-03-22 15:43:21 +00:00
|
|
|
|
void initUiLanguage()
|
2015-02-23 17:09:48 +00:00
|
|
|
|
{
|
2015-10-29 23:57:43 +00:00
|
|
|
|
QString shortDateFormat;
|
|
|
|
|
QString dateFormat;
|
|
|
|
|
QString timeFormat;
|
2015-02-23 17:09:48 +00:00
|
|
|
|
|
2018-08-20 09:48:49 +00:00
|
|
|
|
// set loc as system language or selected language
|
|
|
|
|
if (!qPrefLanguage::use_system_language()) {
|
|
|
|
|
loc = QLocale(qPrefLanguage::lang_locale());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
} else {
|
|
|
|
|
loc = QLocale(QLocale().uiLanguages().first());
|
|
|
|
|
}
|
2018-08-20 09:48:49 +00:00
|
|
|
|
|
2016-01-06 07:41:30 +00:00
|
|
|
|
QStringList languages = loc.uiLanguages();
|
|
|
|
|
QString uiLang;
|
|
|
|
|
if (languages[0].contains('-'))
|
|
|
|
|
uiLang = languages[0];
|
|
|
|
|
else if (languages.count() > 1 && languages[1].contains('-'))
|
|
|
|
|
uiLang = languages[1];
|
|
|
|
|
else if (languages.count() > 2 && languages[2].contains('-'))
|
|
|
|
|
uiLang = languages[2];
|
|
|
|
|
else
|
|
|
|
|
uiLang = languages[0];
|
2018-08-20 09:48:49 +00:00
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
|
// there's a stupid Qt bug on MacOS where uiLanguages doesn't give us the country info
|
|
|
|
|
if (!uiLang.contains('-') && uiLang != loc.bcp47Name()) {
|
|
|
|
|
QLocale loc2(loc.bcp47Name());
|
|
|
|
|
loc = loc2;
|
2016-01-06 07:41:30 +00:00
|
|
|
|
QStringList languages = loc2.uiLanguages();
|
|
|
|
|
if (languages[0].contains('-'))
|
|
|
|
|
uiLang = languages[0];
|
|
|
|
|
else if (languages.count() > 1 && languages[1].contains('-'))
|
|
|
|
|
uiLang = languages[1];
|
|
|
|
|
else if (languages.count() > 2 && languages[2].contains('-'))
|
|
|
|
|
uiLang = languages[2];
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-22 15:45:34 +00:00
|
|
|
|
free((void*)prefs.locale.lang_locale);
|
2020-03-22 15:25:43 +00:00
|
|
|
|
prefs.locale.lang_locale = copy_qstring(uiLang);
|
|
|
|
|
|
2020-05-22 07:02:12 +00:00
|
|
|
|
if (!prefs.date_format_override || empty_string(prefs.date_format)) {
|
2015-11-02 19:32:46 +00:00
|
|
|
|
// derive our standard date format from what the locale gives us
|
|
|
|
|
// the long format uses long weekday and month names, so replace those with the short ones
|
|
|
|
|
// for time we don't want the time zone designator and don't want leading zeroes on the hours
|
|
|
|
|
dateFormat = loc.dateFormat(QLocale::LongFormat);
|
|
|
|
|
dateFormat.replace("dddd,", "ddd").replace("dddd", "ddd").replace("MMMM", "MMM");
|
|
|
|
|
// special hack for Swedish as our switching from long weekday names to short weekday names
|
|
|
|
|
// messes things up there
|
|
|
|
|
dateFormat.replace("'en' 'den' d:'e'", " d");
|
2020-05-22 07:02:12 +00:00
|
|
|
|
free((void *)prefs.date_format);
|
|
|
|
|
prefs.date_format = copy_qstring(dateFormat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!prefs.date_format_override || empty_string(prefs.date_format_short)) {
|
|
|
|
|
// derive our standard date format from what the locale gives us
|
|
|
|
|
shortDateFormat = loc.dateFormat(QLocale::ShortFormat);
|
|
|
|
|
free((void *)prefs.date_format_short);
|
|
|
|
|
prefs.date_format_short = copy_qstring(shortDateFormat);
|
2015-11-02 19:32:46 +00:00
|
|
|
|
}
|
2020-05-22 07:02:12 +00:00
|
|
|
|
|
2018-01-07 10:12:48 +00:00
|
|
|
|
if (!prefs.time_format_override || empty_string(prefs.time_format)) {
|
2015-11-02 19:32:46 +00:00
|
|
|
|
timeFormat = loc.timeFormat();
|
|
|
|
|
timeFormat.replace("(t)", "").replace(" t", "").replace("t", "").replace("hh", "h").replace("HH", "H").replace("'kl'.", "");
|
|
|
|
|
timeFormat.replace(".ss", "").replace(":ss", "").replace("ss", "");
|
2017-11-18 18:57:50 +00:00
|
|
|
|
free((void *)prefs.time_format);
|
2018-02-28 22:37:09 +00:00
|
|
|
|
prefs.time_format = copy_qstring(timeFormat);
|
2015-11-02 19:32:46 +00:00
|
|
|
|
}
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QLocale getLocale()
|
|
|
|
|
{
|
|
|
|
|
return loc;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-10 21:22:13 +00:00
|
|
|
|
void set_filename(const char *filename)
|
2015-02-23 17:09:48 +00:00
|
|
|
|
{
|
|
|
|
|
free((void *)existing_filename);
|
2017-12-10 21:28:35 +00:00
|
|
|
|
existing_filename = copy_string(filename);
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_depth_string(int mm, bool showunit, bool showdecimal)
|
|
|
|
|
{
|
|
|
|
|
if (prefs.units.length == units::METERS) {
|
|
|
|
|
double meters = mm / 1000.0;
|
2018-06-17 18:42:19 +00:00
|
|
|
|
return QString("%L1%2").arg(meters, 0, 'f', (showdecimal && meters < 20.0) ? 1 : 0).arg(showunit ? gettextFromC::tr("m") : QString());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
} else {
|
|
|
|
|
double feet = mm_to_feet(mm);
|
2018-06-17 18:42:19 +00:00
|
|
|
|
return QString("%L1%2").arg(feet, 0, 'f', 0).arg(showunit ? gettextFromC::tr("ft") : QString());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_depth_string(depth_t depth, bool showunit, bool showdecimal)
|
|
|
|
|
{
|
|
|
|
|
return get_depth_string(depth.mm, showunit, showdecimal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_depth_unit()
|
|
|
|
|
{
|
|
|
|
|
if (prefs.units.length == units::METERS)
|
2018-06-17 18:42:19 +00:00
|
|
|
|
return gettextFromC::tr("m");
|
2015-02-23 17:09:48 +00:00
|
|
|
|
else
|
2018-06-17 18:42:19 +00:00
|
|
|
|
return gettextFromC::tr("ft");
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_weight_string(weight_t weight, bool showunit)
|
|
|
|
|
{
|
|
|
|
|
QString str = weight_string(weight.grams);
|
|
|
|
|
if (get_units()->weight == units::KG) {
|
2018-06-17 18:42:19 +00:00
|
|
|
|
str = QString("%1%2").arg(str, showunit ? gettextFromC::tr("kg") : QString());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
} else {
|
2018-06-17 18:42:19 +00:00
|
|
|
|
str = QString("%1%2").arg(str, showunit ? gettextFromC::tr("lbs") : QString());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
2018-02-17 20:21:16 +00:00
|
|
|
|
return str;
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_weight_unit()
|
|
|
|
|
{
|
|
|
|
|
if (prefs.units.weight == units::KG)
|
2018-06-17 18:42:19 +00:00
|
|
|
|
return gettextFromC::tr("kg");
|
2015-02-23 17:09:48 +00:00
|
|
|
|
else
|
2018-06-17 18:42:19 +00:00
|
|
|
|
return gettextFromC::tr("lbs");
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_temperature_string(temperature_t temp, bool showunit)
|
|
|
|
|
{
|
|
|
|
|
if (temp.mkelvin == 0) {
|
|
|
|
|
return ""; //temperature not defined
|
|
|
|
|
} else if (prefs.units.temperature == units::CELSIUS) {
|
|
|
|
|
double celsius = mkelvin_to_C(temp.mkelvin);
|
2019-05-11 20:45:49 +00:00
|
|
|
|
return QString("%L1%2%3").arg(celsius, 0, 'f', 1).arg(showunit ? "°" : "").arg(showunit ? gettextFromC::tr("C") : QString());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
} else {
|
|
|
|
|
double fahrenheit = mkelvin_to_F(temp.mkelvin);
|
2019-05-11 20:45:49 +00:00
|
|
|
|
return QString("%L1%2%3").arg(fahrenheit, 0, 'f', 1).arg(showunit ? "°" : "").arg(showunit ? gettextFromC::tr("F") : QString());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_temp_unit()
|
|
|
|
|
{
|
|
|
|
|
if (prefs.units.temperature == units::CELSIUS)
|
2019-05-11 20:45:49 +00:00
|
|
|
|
return QString("°C");
|
2015-02-23 17:09:48 +00:00
|
|
|
|
else
|
2019-05-11 20:45:49 +00:00
|
|
|
|
return QString("°F");
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-19 20:55:18 +00:00
|
|
|
|
QString get_volume_string(int mliter, bool showunit)
|
2015-02-23 17:09:48 +00:00
|
|
|
|
{
|
|
|
|
|
const char *unit;
|
|
|
|
|
int decimals;
|
2018-02-19 20:55:18 +00:00
|
|
|
|
double value = get_volume_units(mliter, &decimals, &unit);
|
|
|
|
|
return QString("%L1%2").arg(value, 0, 'f', decimals).arg(showunit ? unit : "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_volume_string(volume_t volume, bool showunit)
|
|
|
|
|
{
|
|
|
|
|
return get_volume_string(volume.mliter, showunit);
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_volume_unit()
|
|
|
|
|
{
|
|
|
|
|
const char *unit;
|
|
|
|
|
(void) get_volume_units(0, NULL, &unit);
|
|
|
|
|
return QString(unit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_pressure_string(pressure_t pressure, bool showunit)
|
|
|
|
|
{
|
|
|
|
|
if (prefs.units.pressure == units::BAR) {
|
|
|
|
|
double bar = pressure.mbar / 1000.0;
|
2018-07-12 08:41:32 +00:00
|
|
|
|
return QString("%L1%2").arg(bar, 0, 'f', 0).arg(showunit ? gettextFromC::tr("bar") : QString());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
} else {
|
|
|
|
|
double psi = mbar_to_PSI(pressure.mbar);
|
2018-06-17 18:42:19 +00:00
|
|
|
|
return QString("%L1%2").arg(psi, 0, 'f', 0).arg(showunit ? gettextFromC::tr("psi") : QString());
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 13:54:58 +00:00
|
|
|
|
QString get_salinity_string(int salinity)
|
|
|
|
|
{
|
2020-07-11 19:00:05 +00:00
|
|
|
|
return QStringLiteral("%L1%2").arg(salinity / 10.0).arg(gettextFromC::tr("g/ℓ"));
|
2020-05-04 13:54:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-21 19:44:35 +00:00
|
|
|
|
// the water types need to match the watertypes enum
|
|
|
|
|
static const char *waterTypes[] = {
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "Fresh"),
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "Brackish"),
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "EN13319"),
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "Salt"),
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "Use DC")
|
|
|
|
|
};
|
|
|
|
|
|
2020-05-04 13:54:58 +00:00
|
|
|
|
QString get_water_type_string(int salinity)
|
|
|
|
|
{
|
|
|
|
|
if (salinity < 10050)
|
2020-09-21 19:44:35 +00:00
|
|
|
|
return gettextFromC::tr(waterTypes[FRESHWATER]);
|
2020-05-04 13:54:58 +00:00
|
|
|
|
else if (salinity < 10190)
|
2020-09-21 19:44:35 +00:00
|
|
|
|
return gettextFromC::tr(waterTypes[BRACKISHWATER]);
|
2020-05-04 13:54:58 +00:00
|
|
|
|
else if (salinity < 10210)
|
2020-09-21 19:44:35 +00:00
|
|
|
|
return gettextFromC::tr(waterTypes[EN13319WATER]);
|
2020-05-04 13:54:58 +00:00
|
|
|
|
else
|
2020-09-21 19:44:35 +00:00
|
|
|
|
return gettextFromC::tr(waterTypes[SALTWATER]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList getWaterTypesAsString()
|
|
|
|
|
{
|
|
|
|
|
QStringList res;
|
|
|
|
|
res.reserve(std::end(waterTypes) - std::begin(waterTypes)); // Waiting for C++17's std::size()
|
|
|
|
|
for (const char *t: waterTypes)
|
|
|
|
|
res.push_back(gettextFromC::tr(t));
|
|
|
|
|
return res;
|
2020-05-04 13:54:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
|
QString getSubsurfaceDataPath(QString folderToFind)
|
|
|
|
|
{
|
|
|
|
|
QString execdir;
|
|
|
|
|
QDir folder;
|
|
|
|
|
|
|
|
|
|
// first check if we are running in the build dir, so the path that we
|
|
|
|
|
// are looking for is just a subdirectory of the execution path;
|
|
|
|
|
// this also works on Windows as there we install the dirs
|
|
|
|
|
// under the application path
|
|
|
|
|
execdir = QCoreApplication::applicationDirPath();
|
|
|
|
|
folder = QDir(execdir.append(QDir::separator()).append(folderToFind));
|
|
|
|
|
if (folder.exists())
|
|
|
|
|
return folder.absolutePath();
|
|
|
|
|
|
|
|
|
|
// next check for the Linux typical $(prefix)/share/subsurface
|
|
|
|
|
execdir = QCoreApplication::applicationDirPath();
|
|
|
|
|
if (execdir.contains("bin")) {
|
|
|
|
|
folder = QDir(execdir.replace("bin", "share/subsurface/").append(folderToFind));
|
|
|
|
|
if (folder.exists())
|
|
|
|
|
return folder.absolutePath();
|
|
|
|
|
}
|
|
|
|
|
// then look for the usual locations on a Mac
|
|
|
|
|
execdir = QCoreApplication::applicationDirPath();
|
|
|
|
|
folder = QDir(execdir.append("/../Resources/share/").append(folderToFind));
|
|
|
|
|
if (folder.exists())
|
|
|
|
|
return folder.absolutePath();
|
|
|
|
|
execdir = QCoreApplication::applicationDirPath();
|
|
|
|
|
folder = QDir(execdir.append("/../Resources/").append(folderToFind));
|
|
|
|
|
if (folder.exists())
|
|
|
|
|
return folder.absolutePath();
|
2019-04-03 17:34:52 +00:00
|
|
|
|
return QString();
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 21:25:14 +00:00
|
|
|
|
static const char *printing_templates = "printing_templates";
|
|
|
|
|
|
|
|
|
|
QString getPrintingTemplatePathUser()
|
|
|
|
|
{
|
|
|
|
|
static QString path = QString();
|
|
|
|
|
if (path.isEmpty())
|
|
|
|
|
path = QString(system_default_directory()) + QDir::separator() + QString(printing_templates);
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString getPrintingTemplatePathBundle()
|
|
|
|
|
{
|
|
|
|
|
static QString path = QString();
|
|
|
|
|
if (path.isEmpty())
|
|
|
|
|
path = getSubsurfaceDataPath(printing_templates);
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-11 19:00:05 +00:00
|
|
|
|
int gettimezoneoffset()
|
2015-02-23 17:09:48 +00:00
|
|
|
|
{
|
|
|
|
|
QDateTime dt1, dt2;
|
2020-07-11 19:00:05 +00:00
|
|
|
|
dt1 = QDateTime::currentDateTime();
|
2015-02-23 17:09:48 +00:00
|
|
|
|
dt2 = dt1.toUTC();
|
|
|
|
|
dt1.setTimeSpec(Qt::UTC);
|
|
|
|
|
return dt2.secsTo(dt1);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-22 16:02:15 +00:00
|
|
|
|
QDateTime timestampToDateTime(timestamp_t when)
|
|
|
|
|
{
|
|
|
|
|
// Subsurface always uses "local time" as in "whatever was the local time at the location"
|
|
|
|
|
// so all time stamps have no time zone information and are in UTC
|
|
|
|
|
return QDateTime::fromMSecsSinceEpoch(1000 * when, Qt::UTC);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-22 16:53:25 +00:00
|
|
|
|
timestamp_t dateTimeToTimestamp(const QDateTime &t)
|
|
|
|
|
{
|
|
|
|
|
return t.toSecsSinceEpoch();
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 19:07:32 +00:00
|
|
|
|
QString render_seconds_to_string(int seconds)
|
|
|
|
|
{
|
|
|
|
|
if (seconds % 60 == 0)
|
|
|
|
|
return QDateTime::fromTime_t(seconds).toUTC().toString("h:mm");
|
|
|
|
|
else
|
|
|
|
|
return QDateTime::fromTime_t(seconds).toUTC().toString("h:mm:ss");
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-26 04:23:03 +00:00
|
|
|
|
int parseDurationToSeconds(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
int secs;
|
|
|
|
|
QString numOnly = text;
|
|
|
|
|
QString hours, minutes, seconds;
|
|
|
|
|
numOnly.replace(",", ".").remove(QRegExp("[^-0-9.:]"));
|
|
|
|
|
if (numOnly.isEmpty())
|
|
|
|
|
return 0;
|
|
|
|
|
if (numOnly.contains(':')) {
|
|
|
|
|
hours = numOnly.left(numOnly.indexOf(':'));
|
|
|
|
|
minutes = numOnly.right(numOnly.length() - hours.length() - 1);
|
|
|
|
|
if (minutes.contains(':')) {
|
|
|
|
|
numOnly = minutes;
|
|
|
|
|
minutes = numOnly.left(numOnly.indexOf(':'));
|
|
|
|
|
seconds = numOnly.right(numOnly.length() - minutes.length() - 1);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
hours = "0";
|
|
|
|
|
minutes = numOnly;
|
|
|
|
|
}
|
2017-03-23 01:13:49 +00:00
|
|
|
|
secs = lrint(hours.toDouble() * 3600 + minutes.toDouble() * 60 + seconds.toDouble());
|
2017-02-26 04:23:03 +00:00
|
|
|
|
return secs;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-01 08:32:30 +00:00
|
|
|
|
int parseLengthToMm(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
int mm;
|
|
|
|
|
QString numOnly = text;
|
|
|
|
|
numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]"));
|
|
|
|
|
if (numOnly.isEmpty())
|
|
|
|
|
return 0;
|
|
|
|
|
double number = numOnly.toDouble();
|
2018-07-03 14:52:20 +00:00
|
|
|
|
if (text.contains(gettextFromC::tr("m"), Qt::CaseInsensitive)) {
|
2017-03-23 01:13:49 +00:00
|
|
|
|
mm = lrint(number * 1000);
|
2018-07-03 14:52:20 +00:00
|
|
|
|
} else if (text.contains(gettextFromC::tr("ft"), Qt::CaseInsensitive)) {
|
2016-02-15 15:48:57 +00:00
|
|
|
|
mm = feet_to_mm(number);
|
|
|
|
|
} else {
|
|
|
|
|
switch (prefs.units.length) {
|
|
|
|
|
case units::FEET:
|
|
|
|
|
mm = feet_to_mm(number);
|
|
|
|
|
break;
|
|
|
|
|
case units::METERS:
|
2017-03-23 01:13:49 +00:00
|
|
|
|
mm = lrint(number * 1000);
|
2016-02-15 15:48:57 +00:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
mm = 0;
|
|
|
|
|
}
|
2016-01-01 08:32:30 +00:00
|
|
|
|
}
|
|
|
|
|
return mm;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
|
int parseTemperatureToMkelvin(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
int mkelvin;
|
|
|
|
|
QString numOnly = text;
|
|
|
|
|
numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]"));
|
|
|
|
|
if (numOnly.isEmpty())
|
|
|
|
|
return 0;
|
|
|
|
|
double number = numOnly.toDouble();
|
2018-07-03 14:52:20 +00:00
|
|
|
|
if (text.contains(gettextFromC::tr("C"), Qt::CaseInsensitive)) {
|
2015-02-23 17:09:48 +00:00
|
|
|
|
mkelvin = C_to_mkelvin(number);
|
2018-07-03 14:52:20 +00:00
|
|
|
|
} else if (text.contains(gettextFromC::tr("F"), Qt::CaseInsensitive)) {
|
2015-02-23 17:09:48 +00:00
|
|
|
|
mkelvin = F_to_mkelvin(number);
|
2016-02-15 15:48:57 +00:00
|
|
|
|
} else {
|
|
|
|
|
switch (prefs.units.temperature) {
|
|
|
|
|
case units::CELSIUS:
|
|
|
|
|
mkelvin = C_to_mkelvin(number);
|
|
|
|
|
break;
|
|
|
|
|
case units::FAHRENHEIT:
|
|
|
|
|
mkelvin = F_to_mkelvin(number);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
mkelvin = 0;
|
|
|
|
|
}
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
return mkelvin;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-06 06:51:46 +00:00
|
|
|
|
int parseWeightToGrams(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
int grams;
|
|
|
|
|
QString numOnly = text;
|
|
|
|
|
numOnly.replace(",", ".").remove(QRegExp("[^0-9.]"));
|
|
|
|
|
if (numOnly.isEmpty())
|
|
|
|
|
return 0;
|
|
|
|
|
double number = numOnly.toDouble();
|
2018-07-03 14:52:20 +00:00
|
|
|
|
if (text.contains(gettextFromC::tr("kg"), Qt::CaseInsensitive)) {
|
2017-03-08 06:41:41 +00:00
|
|
|
|
grams = lrint(number * 1000);
|
2018-07-03 14:52:20 +00:00
|
|
|
|
} else if (text.contains(gettextFromC::tr("lbs"), Qt::CaseInsensitive)) {
|
2016-02-06 06:51:46 +00:00
|
|
|
|
grams = lbs_to_grams(number);
|
2016-02-15 15:48:57 +00:00
|
|
|
|
} else {
|
2016-02-12 20:12:23 +00:00
|
|
|
|
switch (prefs.units.weight) {
|
|
|
|
|
case units::KG:
|
2017-03-08 06:41:41 +00:00
|
|
|
|
grams = lrint(number * 1000);
|
2016-02-12 20:12:23 +00:00
|
|
|
|
break;
|
|
|
|
|
case units::LBS:
|
|
|
|
|
grams = lbs_to_grams(number);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
grams = 0;
|
|
|
|
|
}
|
2016-02-06 06:51:46 +00:00
|
|
|
|
}
|
|
|
|
|
return grams;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 18:52:02 +00:00
|
|
|
|
int parsePressureToMbar(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
int mbar;
|
|
|
|
|
QString numOnly = text;
|
2020-08-15 20:46:16 +00:00
|
|
|
|
// different locales use different symbols as group separator or decimal separator
|
|
|
|
|
// (I think it's usually '.' and ',' - but maybe there are others?)
|
|
|
|
|
// let's use Qt's help to get the parsing right
|
|
|
|
|
QString validNumberCharacters("0-9");
|
|
|
|
|
validNumberCharacters += loc.decimalPoint();
|
|
|
|
|
validNumberCharacters += loc.groupSeparator();
|
|
|
|
|
numOnly.remove(QRegExp(QString("[^%1]").arg(validNumberCharacters)));
|
2016-02-09 18:52:02 +00:00
|
|
|
|
if (numOnly.isEmpty())
|
|
|
|
|
return 0;
|
2020-08-15 20:46:16 +00:00
|
|
|
|
double number = loc.toDouble(numOnly);
|
2018-07-03 14:52:20 +00:00
|
|
|
|
if (text.contains(gettextFromC::tr("bar"), Qt::CaseInsensitive)) {
|
2017-03-08 06:41:41 +00:00
|
|
|
|
mbar = lrint(number * 1000);
|
2018-07-03 14:52:20 +00:00
|
|
|
|
} else if (text.contains(gettextFromC::tr("psi"), Qt::CaseInsensitive)) {
|
2016-02-09 18:52:02 +00:00
|
|
|
|
mbar = psi_to_mbar(number);
|
2016-02-15 15:48:57 +00:00
|
|
|
|
} else {
|
2016-02-12 20:12:23 +00:00
|
|
|
|
switch (prefs.units.pressure) {
|
|
|
|
|
case units::BAR:
|
2017-03-08 06:41:41 +00:00
|
|
|
|
mbar = lrint(number * 1000);
|
2016-02-12 20:12:23 +00:00
|
|
|
|
break;
|
|
|
|
|
case units::PSI:
|
|
|
|
|
mbar = psi_to_mbar(number);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
mbar = 0;
|
|
|
|
|
}
|
2016-02-09 18:52:02 +00:00
|
|
|
|
}
|
|
|
|
|
return mbar;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-13 17:34:30 +00:00
|
|
|
|
int parseGasMixO2(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
QString gasString = text;
|
|
|
|
|
int o2, number;
|
2018-07-03 14:52:20 +00:00
|
|
|
|
if (gasString.contains(gettextFromC::tr("AIR"), Qt::CaseInsensitive)) {
|
2016-02-13 17:34:30 +00:00
|
|
|
|
o2 = O2_IN_AIR;
|
2018-07-03 14:52:20 +00:00
|
|
|
|
} else if (gasString.contains(gettextFromC::tr("EAN"), Qt::CaseInsensitive)) {
|
2016-02-13 17:34:30 +00:00
|
|
|
|
gasString.remove(QRegExp("[^0-9]"));
|
|
|
|
|
number = gasString.toInt();
|
|
|
|
|
o2 = number * 10;
|
2016-02-15 15:48:57 +00:00
|
|
|
|
} else if (gasString.contains("/")) {
|
2016-02-13 17:34:30 +00:00
|
|
|
|
QStringList gasSplit = gasString.split("/");
|
|
|
|
|
number = gasSplit[0].toInt();
|
|
|
|
|
o2 = number * 10;
|
2016-02-15 15:48:57 +00:00
|
|
|
|
} else {
|
2016-02-13 17:34:30 +00:00
|
|
|
|
number = gasString.toInt();
|
|
|
|
|
o2 = number * 10;
|
|
|
|
|
}
|
|
|
|
|
return o2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int parseGasMixHE(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
QString gasString = text;
|
|
|
|
|
int he, number;
|
|
|
|
|
if (gasString.contains("/")) {
|
|
|
|
|
QStringList gasSplit = gasString.split("/");
|
|
|
|
|
number = gasSplit[1].toInt();
|
|
|
|
|
he = number * 10;
|
2016-02-15 15:48:57 +00:00
|
|
|
|
} else {
|
2016-02-13 17:34:30 +00:00
|
|
|
|
he = 0;
|
|
|
|
|
}
|
|
|
|
|
return he;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-11 20:43:36 +00:00
|
|
|
|
QString get_dive_duration_string(timestamp_t when, QString hoursText, QString minutesText, QString secondsText, QString separator, bool isFreeDive)
|
2015-02-23 17:09:48 +00:00
|
|
|
|
{
|
2017-05-07 00:43:32 +00:00
|
|
|
|
int hrs, mins, fullmins, secs;
|
2017-05-11 20:43:36 +00:00
|
|
|
|
mins = (when + 30) / 60;
|
2017-05-07 00:43:32 +00:00
|
|
|
|
fullmins = when / 60;
|
|
|
|
|
secs = when - 60 * fullmins;
|
2015-02-23 17:09:48 +00:00
|
|
|
|
hrs = mins / 60;
|
|
|
|
|
|
|
|
|
|
QString displayTime;
|
2017-05-07 00:43:32 +00:00
|
|
|
|
if (prefs.units.duration_units == units::ALWAYS_HOURS || (prefs.units.duration_units == units::MIXED && hrs)) {
|
|
|
|
|
mins -= hrs * 60;
|
2017-05-11 20:43:36 +00:00
|
|
|
|
displayTime = QString("%1%2%3%4%5").arg(hrs).arg(separator == ":" ? "" : hoursText).arg(separator)
|
|
|
|
|
.arg(mins, 2, 10, QChar('0')).arg(separator == ":" ? hoursText : minutesText);
|
|
|
|
|
} else if (isFreeDive && ( prefs.units.duration_units == units::MINUTES_ONLY || minutesText != "" )) {
|
|
|
|
|
// Freedive <1h and we display no hours but only minutes for other dives
|
|
|
|
|
// --> display a short (5min 35sec) freedives e.g. as "5:35"
|
|
|
|
|
// Freedive <1h and we display a unit for minutes
|
|
|
|
|
// --> display a short (5min 35sec) freedives e.g. as "5:35min"
|
|
|
|
|
if (separator == ":") displayTime = QString("%1%2%3%4").arg(fullmins).arg(separator)
|
|
|
|
|
.arg(secs, 2, 10, QChar('0')).arg(minutesText);
|
|
|
|
|
else displayTime = QString("%1%2%3%4%5").arg(fullmins).arg(minutesText).arg(separator)
|
|
|
|
|
.arg(secs).arg(secondsText);
|
2017-05-07 00:43:32 +00:00
|
|
|
|
} else if (isFreeDive) {
|
2017-05-11 20:43:36 +00:00
|
|
|
|
// Mixed display (hh:mm / mm only) and freedive < 1h and we have no unit for minutes
|
2017-11-09 14:43:38 +00:00
|
|
|
|
// --> Prefix duration with "0:" --> "0:05:35"
|
2017-05-11 20:43:36 +00:00
|
|
|
|
if (separator == ":") displayTime = QString("%1%2%3%4%5%6").arg(hrs).arg(separator)
|
|
|
|
|
.arg(fullmins, 2, 10, QChar('0')).arg(separator)
|
|
|
|
|
.arg(secs, 2, 10, QChar('0')).arg(hoursText);
|
|
|
|
|
// Separator != ":" and no units for minutes --> unlikely case - remove?
|
|
|
|
|
else displayTime = QString("%1%2%3%4%5%6%7%8").arg(hrs).arg(hoursText).arg(separator)
|
|
|
|
|
.arg(fullmins).arg(minutesText).arg(separator)
|
|
|
|
|
.arg(secs).arg(secondsText);
|
2017-05-07 00:43:32 +00:00
|
|
|
|
} else {
|
2017-05-11 20:43:36 +00:00
|
|
|
|
displayTime = QString("%1%2").arg(mins).arg(minutesText);
|
2017-05-07 00:43:32 +00:00
|
|
|
|
}
|
2015-02-23 17:09:48 +00:00
|
|
|
|
return displayTime;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-11 20:43:36 +00:00
|
|
|
|
QString get_dive_surfint_string(timestamp_t when, QString daysText, QString hoursText, QString minutesText, QString separator, int maxdays)
|
|
|
|
|
{
|
|
|
|
|
int days, hrs, mins;
|
|
|
|
|
days = when / 3600 / 24;
|
|
|
|
|
hrs = (when - days * 3600 * 24) / 3600;
|
|
|
|
|
mins = (when + 30 - days * 3600 * 24 - hrs * 3600) / 60;
|
|
|
|
|
|
|
|
|
|
QString displayInt;
|
2018-06-17 18:42:19 +00:00
|
|
|
|
if (maxdays && days > maxdays) displayInt = gettextFromC::tr("more than %1 days").arg(maxdays);
|
2017-05-11 20:43:36 +00:00
|
|
|
|
else if (days) displayInt = QString("%1%2%3%4%5%6%7%8").arg(days).arg(daysText).arg(separator)
|
|
|
|
|
.arg(hrs).arg(hoursText).arg(separator)
|
|
|
|
|
.arg(mins).arg(minutesText);
|
|
|
|
|
else displayInt = QString("%1%2%3%4%5").arg(hrs).arg(hoursText).arg(separator)
|
|
|
|
|
.arg(mins).arg(minutesText);
|
|
|
|
|
return displayInt;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
|
QString get_dive_date_string(timestamp_t when)
|
|
|
|
|
{
|
|
|
|
|
QDateTime ts;
|
|
|
|
|
ts.setMSecsSinceEpoch(when * 1000L);
|
2015-10-29 23:57:43 +00:00
|
|
|
|
return loc.toString(ts.toUTC(), QString(prefs.date_format) + " " + prefs.time_format);
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-17 01:40:46 +00:00
|
|
|
|
// Get local seconds since Epoch from ISO formatted UTC date time + offset string
|
|
|
|
|
extern "C" time_t get_dive_datetime_from_isostring(char *when) {
|
|
|
|
|
QDateTime divetime = QDateTime::fromString(when, Qt::ISODate);
|
|
|
|
|
return (time_t)(divetime.toSecsSinceEpoch());
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
|
QString get_short_dive_date_string(timestamp_t when)
|
|
|
|
|
{
|
|
|
|
|
QDateTime ts;
|
|
|
|
|
ts.setMSecsSinceEpoch(when * 1000L);
|
2015-10-29 23:57:43 +00:00
|
|
|
|
return loc.toString(ts.toUTC(), QString(prefs.date_format_short) + " " + prefs.time_format);
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 21:12:23 +00:00
|
|
|
|
char *get_dive_date_c_string(timestamp_t when)
|
2015-02-23 17:09:48 +00:00
|
|
|
|
{
|
|
|
|
|
QString text = get_dive_date_string(when);
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(text);
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 22:29:26 +00:00
|
|
|
|
static QString get_dive_only_date_string(timestamp_t when)
|
|
|
|
|
{
|
|
|
|
|
QDateTime ts;
|
|
|
|
|
ts.setMSecsSinceEpoch(when * 1000L);
|
|
|
|
|
return loc.toString(ts.toUTC(), QString(prefs.date_format));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_first_dive_date_string()
|
|
|
|
|
{
|
|
|
|
|
return dive_table.nr > 0 ? get_dive_only_date_string(dive_table.dives[0]->when) : gettextFromC::tr("no dives");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_last_dive_date_string()
|
|
|
|
|
{
|
|
|
|
|
return dive_table.nr > 0 ? get_dive_only_date_string(dive_table.dives[dive_table.nr - 1]->when) : gettextFromC::tr("no dives");
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 21:12:23 +00:00
|
|
|
|
extern "C" char *get_current_date()
|
2017-03-31 06:14:36 +00:00
|
|
|
|
{
|
|
|
|
|
QDateTime ts(QDateTime::currentDateTime());;
|
|
|
|
|
QString current_date;
|
2017-05-07 00:43:32 +00:00
|
|
|
|
|
2017-03-31 06:14:36 +00:00
|
|
|
|
current_date = loc.toString(ts, QString(prefs.date_format_short));
|
|
|
|
|
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(current_date);
|
2017-03-31 06:14:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 20:45:21 +00:00
|
|
|
|
QString get_trip_string(const dive_trip *trip)
|
2015-02-23 17:09:48 +00:00
|
|
|
|
{
|
2020-09-29 20:45:21 +00:00
|
|
|
|
if (!trip)
|
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
|
|
int nr = trip->dives.nr;
|
|
|
|
|
timestamp_t when = trip_date(trip);
|
|
|
|
|
bool getday = trip_is_single_day(trip);
|
|
|
|
|
|
2020-05-22 16:02:15 +00:00
|
|
|
|
QDateTime localTime = timestampToDateTime(when);
|
2015-10-06 19:08:27 +00:00
|
|
|
|
|
2020-09-29 20:45:21 +00:00
|
|
|
|
QString prefix = !empty_string(trip->location) ? QString(trip->location) + ", " : QString();
|
2018-07-03 14:52:20 +00:00
|
|
|
|
QString suffix = " " + gettextFromC::tr("(%n dive(s))", "", nr);
|
2018-11-16 07:56:36 +00:00
|
|
|
|
if (getday)
|
2020-09-29 20:45:21 +00:00
|
|
|
|
return prefix + loc.toString(localTime, prefs.date_format) + suffix;
|
2018-11-16 07:56:36 +00:00
|
|
|
|
else
|
2020-09-29 20:45:21 +00:00
|
|
|
|
return prefix + loc.toString(localTime, "MMM yyyy") + suffix;
|
2015-02-23 17:09:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-10 14:39:47 +00:00
|
|
|
|
static QMutex hashOfMutex;
|
|
|
|
|
static QHash<QString, QString> localFilenameOf;
|
2015-02-26 13:39:42 +00:00
|
|
|
|
|
2018-06-10 14:39:47 +00:00
|
|
|
|
static const QString hashfile_name()
|
2015-09-23 09:13:07 +00:00
|
|
|
|
{
|
|
|
|
|
return QString(system_default_directory()).append("/hashes");
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 20:07:05 +00:00
|
|
|
|
static QString thumbnailDir()
|
|
|
|
|
{
|
|
|
|
|
return QString(system_default_directory()) + "/thumbnails/";
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-25 20:01:30 +00:00
|
|
|
|
// Calculate thumbnail filename by hashing name of file.
|
2018-04-29 20:07:05 +00:00
|
|
|
|
QString thumbnailFileName(const QString &filename)
|
|
|
|
|
{
|
2018-05-25 20:01:30 +00:00
|
|
|
|
if (filename.isEmpty())
|
|
|
|
|
return QString();
|
|
|
|
|
QCryptographicHash hash(QCryptographicHash::Sha1);
|
|
|
|
|
hash.addData(filename.toUtf8());
|
|
|
|
|
return thumbnailDir() + hash.result().toHex();
|
2018-04-29 20:07:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-23 09:13:07 +00:00
|
|
|
|
extern "C" char *hashfile_name_string()
|
|
|
|
|
{
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(hashfile_name());
|
2015-09-23 09:13:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 21:10:32 +00:00
|
|
|
|
// During a transition period, convert old thumbnail-hashes to individual files
|
|
|
|
|
// TODO: remove this code in due course
|
|
|
|
|
static void convertThumbnails(const QHash <QString, QImage> &thumbnails)
|
|
|
|
|
{
|
|
|
|
|
if (thumbnails.empty())
|
|
|
|
|
return;
|
|
|
|
|
// This is a singular occurrence, therefore translating the strings seems not worth it
|
|
|
|
|
QProgressDialog progress("Convert thumbnails...", "Abort", 0, thumbnails.size());
|
|
|
|
|
progress.setWindowModality(Qt::WindowModal);
|
|
|
|
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
for (const QString &name: thumbnails.keys()) {
|
|
|
|
|
const QImage thumbnail = thumbnails[name];
|
|
|
|
|
|
|
|
|
|
if (thumbnail.isNull())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// This is duplicate code (see qt-models/divepicturemodel.cpp)
|
|
|
|
|
// Not a problem, since this routine will be removed in due course.
|
|
|
|
|
QString filename = thumbnailFileName(name);
|
|
|
|
|
if (filename.isEmpty())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
QSaveFile file(filename);
|
|
|
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
|
|
|
return;
|
|
|
|
|
QDataStream stream(&file);
|
|
|
|
|
|
|
|
|
|
quint32 type = MEDIATYPE_PICTURE;
|
|
|
|
|
stream << type;
|
|
|
|
|
stream << thumbnail;
|
|
|
|
|
file.commit();
|
|
|
|
|
|
|
|
|
|
progress.setValue(++count);
|
|
|
|
|
if (progress.wasCanceled())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-02 16:03:03 +00:00
|
|
|
|
// TODO: This is a temporary helper struct. Remove in due course with convertLocalFilename().
|
|
|
|
|
struct HashToFile {
|
|
|
|
|
QByteArray hash;
|
|
|
|
|
QString filename;
|
|
|
|
|
bool operator< (const HashToFile &h) const {
|
|
|
|
|
return hash < h.hash;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// During a transition period, convert the hash->localFilename into a canonicalFilename->localFilename.
|
|
|
|
|
// TODO: remove this code in due course
|
2018-06-10 14:39:47 +00:00
|
|
|
|
static void convertLocalFilename(const QHash<QString, QByteArray> &hashOf, const QHash<QByteArray, QString> &hashToLocal)
|
2018-06-02 16:03:03 +00:00
|
|
|
|
{
|
|
|
|
|
// Bail out early if there is nothing to do
|
|
|
|
|
if (hashToLocal.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Create a vector of hash/filename pairs and sort by hash.
|
|
|
|
|
// Elements can than be accessed with binary search.
|
|
|
|
|
QHash<QByteArray, QString> canonicalFilenameByHash;
|
|
|
|
|
QVector<HashToFile> h2f;
|
|
|
|
|
h2f.reserve(hashOf.size());
|
|
|
|
|
for (auto it = hashOf.cbegin(); it != hashOf.cend(); ++it)
|
|
|
|
|
h2f.append({ it.value(), it.key() });
|
|
|
|
|
std::sort(h2f.begin(), h2f.end());
|
|
|
|
|
|
|
|
|
|
// Make the canonical-to-local connection
|
|
|
|
|
for (auto it = hashToLocal.cbegin(); it != hashToLocal.cend(); ++it) {
|
|
|
|
|
QByteArray hash = it.key();
|
|
|
|
|
HashToFile dummy { hash, QString() };
|
|
|
|
|
for(auto it2 = std::lower_bound(h2f.begin(), h2f.end(), dummy);
|
|
|
|
|
it2 != h2f.end() && it2->hash == hash; ++it2) {
|
|
|
|
|
// Note that learnPictureFilename cares about all the special cases,
|
|
|
|
|
// i.e. either filename being empty or both filenames being equal.
|
|
|
|
|
learnPictureFilename(it2->filename, it.value());
|
|
|
|
|
}
|
|
|
|
|
QString canonicalFilename = canonicalFilenameByHash.value(it.key());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-26 13:39:42 +00:00
|
|
|
|
void read_hashes()
|
|
|
|
|
{
|
2015-09-23 09:13:07 +00:00
|
|
|
|
QFile hashfile(hashfile_name());
|
2015-02-26 13:39:42 +00:00
|
|
|
|
if (hashfile.open(QIODevice::ReadOnly)) {
|
|
|
|
|
QDataStream stream(&hashfile);
|
2018-06-02 16:03:03 +00:00
|
|
|
|
QHash<QByteArray, QString> localFilenameByHash;
|
2018-06-10 14:39:47 +00:00
|
|
|
|
QHash<QString, QByteArray> hashOf;
|
2018-06-02 16:03:03 +00:00
|
|
|
|
stream >> localFilenameByHash; // For backwards compatibility
|
2018-06-10 14:39:47 +00:00
|
|
|
|
stream >> hashOf; // For backwards compatibility
|
2018-04-29 20:07:05 +00:00
|
|
|
|
QHash <QString, QImage> thumbnailCache;
|
|
|
|
|
stream >> thumbnailCache; // For backwards compatibility
|
2018-06-10 14:39:47 +00:00
|
|
|
|
QMutexLocker locker(&hashOfMutex);
|
2018-06-02 16:03:03 +00:00
|
|
|
|
stream >> localFilenameOf;
|
2018-06-10 14:39:47 +00:00
|
|
|
|
locker.unlock();
|
2015-02-26 13:39:42 +00:00
|
|
|
|
hashfile.close();
|
2018-04-29 21:10:32 +00:00
|
|
|
|
convertThumbnails(thumbnailCache);
|
2018-06-10 14:39:47 +00:00
|
|
|
|
convertLocalFilename(hashOf, localFilenameByHash);
|
2015-02-26 13:39:42 +00:00
|
|
|
|
}
|
2018-04-29 21:10:32 +00:00
|
|
|
|
QMutexLocker locker(&hashOfMutex);
|
2016-12-02 22:43:30 +00:00
|
|
|
|
localFilenameOf.remove("");
|
2018-04-29 20:07:05 +00:00
|
|
|
|
|
|
|
|
|
// Make sure that the thumbnail directory exists
|
|
|
|
|
QDir().mkpath(thumbnailDir());
|
2015-02-26 13:39:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void write_hashes()
|
|
|
|
|
{
|
2015-09-23 09:13:07 +00:00
|
|
|
|
QSaveFile hashfile(hashfile_name());
|
2016-04-29 14:18:06 +00:00
|
|
|
|
QMutexLocker locker(&hashOfMutex);
|
|
|
|
|
|
2015-02-26 13:39:42 +00:00
|
|
|
|
if (hashfile.open(QIODevice::WriteOnly)) {
|
|
|
|
|
QDataStream stream(&hashfile);
|
2018-06-02 16:03:03 +00:00
|
|
|
|
stream << QHash<QByteArray, QString>(); // Empty hash to filename - for backwards compatibility
|
2018-06-10 14:39:47 +00:00
|
|
|
|
stream << QHash<QString, QByteArray>(); // Empty hashes - for backwards compatibility
|
2018-04-29 20:07:05 +00:00
|
|
|
|
stream << QHash<QString,QImage>(); // Empty thumbnailCache - for backwards compatibility
|
2018-06-02 16:03:03 +00:00
|
|
|
|
stream << localFilenameOf;
|
2015-02-26 13:39:42 +00:00
|
|
|
|
hashfile.commit();
|
|
|
|
|
} else {
|
2017-12-18 10:11:35 +00:00
|
|
|
|
qWarning() << "Cannot open hashfile for writing: " << hashfile.fileName();
|
2015-02-26 13:39:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-02 16:03:03 +00:00
|
|
|
|
void learnPictureFilename(const QString &originalName, const QString &localName)
|
2015-03-02 15:18:16 +00:00
|
|
|
|
{
|
2018-06-02 16:03:03 +00:00
|
|
|
|
if (originalName.isEmpty() || localName.isEmpty())
|
2016-12-15 13:31:20 +00:00
|
|
|
|
return;
|
2015-03-25 13:28:36 +00:00
|
|
|
|
QMutexLocker locker(&hashOfMutex);
|
2018-06-02 16:03:03 +00:00
|
|
|
|
// Only keep track of images where original and local names differ
|
|
|
|
|
if (originalName == localName)
|
|
|
|
|
localFilenameOf.remove(originalName);
|
|
|
|
|
else
|
|
|
|
|
localFilenameOf[originalName] = localName;
|
2015-03-02 15:18:16 +00:00
|
|
|
|
}
|
2015-02-26 13:39:42 +00:00
|
|
|
|
|
2018-02-17 14:55:50 +00:00
|
|
|
|
QString localFilePath(const QString &originalFilename)
|
2015-02-26 13:39:42 +00:00
|
|
|
|
{
|
2016-04-29 14:18:06 +00:00
|
|
|
|
QMutexLocker locker(&hashOfMutex);
|
2018-06-02 16:03:03 +00:00
|
|
|
|
return localFilenameOf.value(originalFilename, originalFilename);
|
2015-02-26 13:39:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-15 18:47:35 +00:00
|
|
|
|
// TODO: Apparently Qt has no simple way of listing the supported video
|
|
|
|
|
// codecs? Do we have to query them by hand using QMediaPlayer::hasSupport()?
|
|
|
|
|
const QStringList videoExtensionsList = {
|
2018-07-11 21:54:37 +00:00
|
|
|
|
".avi", ".mp4", ".mov", ".mpeg", ".mpg", ".wmv"
|
2018-05-15 18:47:35 +00:00
|
|
|
|
};
|
|
|
|
|
|
2018-07-13 19:06:28 +00:00
|
|
|
|
QStringList mediaExtensionFilters()
|
|
|
|
|
{
|
|
|
|
|
return imageExtensionFilters() + videoExtensionFilters();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList imageExtensionFilters()
|
|
|
|
|
{
|
|
|
|
|
QStringList filters;
|
|
|
|
|
foreach (const QString &format, QImageReader::supportedImageFormats())
|
|
|
|
|
filters.append("*." + format);
|
|
|
|
|
return filters;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList videoExtensionFilters()
|
|
|
|
|
{
|
2017-05-05 19:13:46 +00:00
|
|
|
|
QStringList filters;
|
2019-04-01 20:15:19 +00:00
|
|
|
|
for (const QString &format: videoExtensionsList)
|
2018-05-15 18:47:35 +00:00
|
|
|
|
filters.append("*" + format);
|
2017-05-05 19:13:46 +00:00
|
|
|
|
return filters;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-21 17:29:02 +00:00
|
|
|
|
extern "C" const char *local_file_path(struct picture *picture)
|
|
|
|
|
{
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(localFilePath(picture->filename));
|
2015-06-21 17:29:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-23 09:13:07 +00:00
|
|
|
|
const QString picturedir()
|
|
|
|
|
{
|
|
|
|
|
return QString(system_default_directory()).append("/picturedata/");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" char *picturedir_string()
|
|
|
|
|
{
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(picturedir());
|
2015-09-23 09:13:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-05-28 19:23:49 +00:00
|
|
|
|
QString get_gas_string(struct gasmix gas)
|
|
|
|
|
{
|
2018-08-16 17:10:10 +00:00
|
|
|
|
uint o2 = (get_o2(gas) + 5) / 10, he = (get_he(gas) + 5) / 10;
|
|
|
|
|
QString result = gasmix_is_air(gas) ? gettextFromC::tr("AIR") : he == 0 ? (o2 == 100 ? gettextFromC::tr("OXYGEN") : QString("EAN%1").arg(o2, 2, 10, QChar('0'))) : QString("%1/%2").arg(o2).arg(he);
|
2015-05-28 19:23:49 +00:00
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-09 08:09:34 +00:00
|
|
|
|
QString get_taglist_string(struct tag_entry *tag_list)
|
|
|
|
|
{
|
|
|
|
|
char *buffer = taglist_get_tagstring(tag_list);
|
|
|
|
|
QString ret = QString::fromUtf8(buffer);
|
|
|
|
|
free(buffer);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-09 23:09:17 +00:00
|
|
|
|
QStringList stringToList(const QString &s)
|
|
|
|
|
{
|
|
|
|
|
QStringList res = s.split(",", QString::SkipEmptyParts);
|
|
|
|
|
for (QString &str: res)
|
|
|
|
|
str = str.trimmed();
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-28 20:17:09 +00:00
|
|
|
|
weight_t string_to_weight(const char *str)
|
|
|
|
|
{
|
|
|
|
|
const char *end;
|
|
|
|
|
double value = strtod_flags(str, &end, 0);
|
|
|
|
|
QString rest = QString(end).trimmed();
|
2018-07-03 14:52:20 +00:00
|
|
|
|
QString local_kg = gettextFromC::tr("kg");
|
|
|
|
|
QString local_lbs = gettextFromC::tr("lbs");
|
2015-05-28 20:17:09 +00:00
|
|
|
|
weight_t weight;
|
|
|
|
|
|
|
|
|
|
if (rest.startsWith("kg") || rest.startsWith(local_kg))
|
|
|
|
|
goto kg;
|
|
|
|
|
// using just "lb" instead of "lbs" is intentional - some people might enter the singular
|
|
|
|
|
if (rest.startsWith("lb") || rest.startsWith(local_lbs))
|
|
|
|
|
goto lbs;
|
|
|
|
|
if (prefs.units.weight == prefs.units.LBS)
|
|
|
|
|
goto lbs;
|
|
|
|
|
kg:
|
2017-03-08 06:41:41 +00:00
|
|
|
|
weight.grams = lrint(value * 1000);
|
2015-05-28 20:17:09 +00:00
|
|
|
|
return weight;
|
|
|
|
|
lbs:
|
|
|
|
|
weight.grams = lbs_to_grams(value);
|
|
|
|
|
return weight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
depth_t string_to_depth(const char *str)
|
|
|
|
|
{
|
|
|
|
|
const char *end;
|
|
|
|
|
double value = strtod_flags(str, &end, 0);
|
|
|
|
|
QString rest = QString(end).trimmed();
|
2018-07-03 14:52:20 +00:00
|
|
|
|
QString local_ft = gettextFromC::tr("ft");
|
|
|
|
|
QString local_m = gettextFromC::tr("m");
|
2015-05-28 20:17:09 +00:00
|
|
|
|
depth_t depth;
|
|
|
|
|
|
2016-05-22 01:00:22 +00:00
|
|
|
|
if (value < 0)
|
|
|
|
|
value = 0;
|
2015-05-28 20:17:09 +00:00
|
|
|
|
if (rest.startsWith("m") || rest.startsWith(local_m))
|
|
|
|
|
goto m;
|
|
|
|
|
if (rest.startsWith("ft") || rest.startsWith(local_ft))
|
|
|
|
|
goto ft;
|
|
|
|
|
if (prefs.units.length == prefs.units.FEET)
|
|
|
|
|
goto ft;
|
|
|
|
|
m:
|
2017-03-08 06:41:41 +00:00
|
|
|
|
depth.mm = lrint(value * 1000);
|
2015-05-28 20:17:09 +00:00
|
|
|
|
return depth;
|
|
|
|
|
ft:
|
|
|
|
|
depth.mm = feet_to_mm(value);
|
|
|
|
|
return depth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pressure_t string_to_pressure(const char *str)
|
|
|
|
|
{
|
|
|
|
|
const char *end;
|
|
|
|
|
double value = strtod_flags(str, &end, 0);
|
|
|
|
|
QString rest = QString(end).trimmed();
|
2018-07-03 14:52:20 +00:00
|
|
|
|
QString local_psi = gettextFromC::tr("psi");
|
|
|
|
|
QString local_bar = gettextFromC::tr("bar");
|
2015-05-28 20:17:09 +00:00
|
|
|
|
pressure_t pressure;
|
|
|
|
|
|
|
|
|
|
if (rest.startsWith("bar") || rest.startsWith(local_bar))
|
|
|
|
|
goto bar;
|
|
|
|
|
if (rest.startsWith("psi") || rest.startsWith(local_psi))
|
|
|
|
|
goto psi;
|
|
|
|
|
if (prefs.units.pressure == prefs.units.PSI)
|
|
|
|
|
goto psi;
|
|
|
|
|
bar:
|
2017-03-08 06:41:41 +00:00
|
|
|
|
pressure.mbar = lrint(value * 1000);
|
2015-05-28 20:17:09 +00:00
|
|
|
|
return pressure;
|
|
|
|
|
psi:
|
|
|
|
|
pressure.mbar = psi_to_mbar(value);
|
|
|
|
|
return pressure;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
volume_t string_to_volume(const char *str, pressure_t workp)
|
|
|
|
|
{
|
|
|
|
|
const char *end;
|
|
|
|
|
double value = strtod_flags(str, &end, 0);
|
|
|
|
|
QString rest = QString(end).trimmed();
|
2018-07-03 14:52:20 +00:00
|
|
|
|
QString local_l = gettextFromC::tr("l");
|
|
|
|
|
QString local_cuft = gettextFromC::tr("cuft");
|
2015-05-28 20:17:09 +00:00
|
|
|
|
volume_t volume;
|
|
|
|
|
|
|
|
|
|
if (rest.startsWith("l") || rest.startsWith("ℓ") || rest.startsWith(local_l))
|
|
|
|
|
goto l;
|
|
|
|
|
if (rest.startsWith("cuft") || rest.startsWith(local_cuft))
|
|
|
|
|
goto cuft;
|
|
|
|
|
/*
|
|
|
|
|
* If we don't have explicit units, and there is no working
|
|
|
|
|
* pressure, we're going to assume "liter" even in imperial
|
|
|
|
|
* measurements.
|
|
|
|
|
*/
|
|
|
|
|
if (!workp.mbar)
|
|
|
|
|
goto l;
|
|
|
|
|
if (prefs.units.volume == prefs.units.LITER)
|
|
|
|
|
goto l;
|
|
|
|
|
cuft:
|
|
|
|
|
if (workp.mbar)
|
|
|
|
|
value /= bar_to_atm(workp.mbar / 1000.0);
|
|
|
|
|
value = cuft_to_l(value);
|
|
|
|
|
l:
|
2017-03-08 06:41:41 +00:00
|
|
|
|
volume.mliter = lrint(value * 1000);
|
2015-05-28 20:17:09 +00:00
|
|
|
|
return volume;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fraction_t string_to_fraction(const char *str)
|
|
|
|
|
{
|
|
|
|
|
const char *end;
|
|
|
|
|
double value = strtod_flags(str, &end, 0);
|
|
|
|
|
fraction_t fraction;
|
|
|
|
|
|
2017-03-08 06:41:41 +00:00
|
|
|
|
fraction.permille = lrint(value * 10);
|
2016-05-22 01:00:22 +00:00
|
|
|
|
/*
|
|
|
|
|
* Don't permit values less than zero or greater than 100%
|
|
|
|
|
*/
|
|
|
|
|
if (fraction.permille < 0)
|
|
|
|
|
fraction.permille = 0;
|
|
|
|
|
else if (fraction.permille > 1000)
|
|
|
|
|
fraction.permille = 1000;
|
2015-05-28 20:17:09 +00:00
|
|
|
|
return fraction;
|
|
|
|
|
}
|
2015-06-01 20:09:45 +00:00
|
|
|
|
|
|
|
|
|
int getCloudURL(QString &filename)
|
|
|
|
|
{
|
|
|
|
|
QString email = QString(prefs.cloud_storage_email);
|
2015-06-09 18:21:46 +00:00
|
|
|
|
email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), "");
|
2018-01-07 10:12:48 +00:00
|
|
|
|
if (email.isEmpty() || empty_string(prefs.cloud_storage_password))
|
2015-06-01 20:09:45 +00:00
|
|
|
|
return report_error("Please configure Cloud storage email and password in the preferences");
|
|
|
|
|
if (email != prefs.cloud_storage_email_encoded) {
|
2017-11-18 18:57:50 +00:00
|
|
|
|
free((void *)prefs.cloud_storage_email_encoded);
|
2018-02-28 22:37:09 +00:00
|
|
|
|
prefs.cloud_storage_email_encoded = copy_qstring(email);
|
2015-06-01 20:09:45 +00:00
|
|
|
|
}
|
2015-06-15 13:05:00 +00:00
|
|
|
|
filename = QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email);
|
2015-09-23 19:20:21 +00:00
|
|
|
|
if (verbose)
|
|
|
|
|
qDebug() << "cloud URL set as" << filename;
|
2015-06-01 20:09:45 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
2015-06-12 13:24:48 +00:00
|
|
|
|
|
2015-09-23 09:13:07 +00:00
|
|
|
|
extern "C" char *cloud_url()
|
|
|
|
|
{
|
|
|
|
|
QString filename;
|
|
|
|
|
getCloudURL(filename);
|
2018-02-28 22:37:09 +00:00
|
|
|
|
return copy_qstring(filename);
|
2015-09-23 09:13:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-12 13:24:48 +00:00
|
|
|
|
extern "C" bool getProxyString(char **buffer)
|
|
|
|
|
{
|
|
|
|
|
if (prefs.proxy_type == QNetworkProxy::HttpProxy) {
|
|
|
|
|
QString proxy;
|
|
|
|
|
if (prefs.proxy_auth)
|
|
|
|
|
proxy = QString("http://%1:%2@%3:%4").arg(prefs.proxy_user).arg(prefs.proxy_pass)
|
|
|
|
|
.arg(prefs.proxy_host).arg(prefs.proxy_port);
|
|
|
|
|
else
|
|
|
|
|
proxy = QString("http://%1:%2").arg(prefs.proxy_host).arg(prefs.proxy_port);
|
|
|
|
|
if (buffer)
|
2018-02-28 22:37:09 +00:00
|
|
|
|
*buffer = copy_qstring(proxy);
|
2015-06-12 13:24:48 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-06-12 13:31:16 +00:00
|
|
|
|
|
2015-06-14 01:25:36 +00:00
|
|
|
|
extern "C" void subsurface_mkdir(const char *dir)
|
|
|
|
|
{
|
|
|
|
|
QDir directory;
|
|
|
|
|
if (!directory.mkpath(QString(dir)))
|
|
|
|
|
qDebug() << "failed to create path" << dir;
|
|
|
|
|
}
|
2015-06-17 03:30:06 +00:00
|
|
|
|
|
|
|
|
|
extern "C" void parse_display_units(char *line)
|
|
|
|
|
{
|
|
|
|
|
qDebug() << line;
|
|
|
|
|
}
|
2015-08-18 08:03:51 +00:00
|
|
|
|
|
|
|
|
|
extern "C" bool in_planner()
|
|
|
|
|
{
|
2019-05-10 17:51:43 +00:00
|
|
|
|
return getAppState() == ApplicationState::PlanDive || getAppState() == ApplicationState::EditPlannedDive;
|
2015-08-18 08:03:51 +00:00
|
|
|
|
}
|
2015-11-14 18:43:37 +00:00
|
|
|
|
|
2017-01-07 02:01:14 +00:00
|
|
|
|
extern "C" enum deco_mode decoMode()
|
|
|
|
|
{
|
2017-01-07 02:11:19 +00:00
|
|
|
|
return in_planner() ? prefs.planner_deco_mode : prefs.display_deco_mode;
|
2017-01-07 02:01:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-14 18:43:37 +00:00
|
|
|
|
void init_proxy()
|
|
|
|
|
{
|
|
|
|
|
QNetworkProxy proxy;
|
|
|
|
|
proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type));
|
|
|
|
|
proxy.setHostName(prefs.proxy_host);
|
|
|
|
|
proxy.setPort(prefs.proxy_port);
|
|
|
|
|
if (prefs.proxy_auth) {
|
|
|
|
|
proxy.setUser(prefs.proxy_user);
|
|
|
|
|
proxy.setPassword(prefs.proxy_pass);
|
|
|
|
|
}
|
|
|
|
|
QNetworkProxy::setApplicationProxy(proxy);
|
|
|
|
|
}
|
2015-11-19 01:57:13 +00:00
|
|
|
|
|
|
|
|
|
QString getUUID()
|
|
|
|
|
{
|
|
|
|
|
QString uuidString;
|
2018-08-20 09:48:49 +00:00
|
|
|
|
uuidString = qPrefUpdateManager::uuidString();
|
|
|
|
|
if (uuidString != "") {
|
2015-11-19 01:57:13 +00:00
|
|
|
|
QUuid uuid = QUuid::createUuid();
|
|
|
|
|
uuidString = uuid.toString();
|
2018-08-20 09:48:49 +00:00
|
|
|
|
qPrefUpdateManager::set_uuidString(uuidString);
|
2015-11-19 01:57:13 +00:00
|
|
|
|
}
|
2019-10-04 23:47:00 +00:00
|
|
|
|
uuidString.replace("{", "").replace("}", "");
|
2015-11-19 01:57:13 +00:00
|
|
|
|
return uuidString;
|
|
|
|
|
}
|
2017-05-07 06:41:27 +00:00
|
|
|
|
|
2020-10-17 18:15:23 +00:00
|
|
|
|
void parse_seabear_header(const char *filename, struct xml_params *params)
|
2017-05-07 10:41:09 +00:00
|
|
|
|
{
|
|
|
|
|
QFile f(filename);
|
|
|
|
|
|
|
|
|
|
f.open(QFile::ReadOnly);
|
|
|
|
|
QString parseLine = f.readLine();
|
|
|
|
|
|
2017-05-07 11:19:58 +00:00
|
|
|
|
/*
|
|
|
|
|
* Parse dive number from Seabear CSV header
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
while ((parseLine = f.readLine().trimmed()).length() > 0 && !f.atEnd()) {
|
|
|
|
|
if (parseLine.contains("//DIVE NR: ")) {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add(params, "diveNro",
|
|
|
|
|
qPrintable(parseLine.replace(QString::fromLatin1("//DIVE NR: "), QString::fromLatin1(""))));
|
2017-05-07 11:19:58 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-07 12:53:35 +00:00
|
|
|
|
f.seek(0);
|
2017-05-07 11:19:58 +00:00
|
|
|
|
|
2017-05-07 10:41:09 +00:00
|
|
|
|
/*
|
|
|
|
|
* Parse header - currently only interested in sample
|
|
|
|
|
* interval and hardware version. If we have old format
|
|
|
|
|
* the interval value is missing from the header.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
while ((parseLine = f.readLine().trimmed()).length() > 0 && !f.atEnd()) {
|
|
|
|
|
if (parseLine.contains("//Hardware Version: ")) {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add(params, "hw",
|
|
|
|
|
qPrintable(parseLine.replace(QString::fromLatin1("//Hardware Version: "),
|
|
|
|
|
QString::fromLatin1("\"Seabear ")).trimmed().append("\"")));
|
2017-05-07 10:41:09 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-07 12:53:35 +00:00
|
|
|
|
f.seek(0);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
|
|
|
|
|
/*
|
2017-05-07 12:34:27 +00:00
|
|
|
|
* Grab the sample interval
|
2017-05-07 10:41:09 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
while ((parseLine = f.readLine().trimmed()).length() > 0 && !f.atEnd()) {
|
|
|
|
|
if (parseLine.contains("//Log interval: ")) {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add(params, "delta",
|
|
|
|
|
qPrintable(parseLine.remove(QString::fromLatin1("//Log interval: ")).trimmed().remove(QString::fromLatin1(" s"))));
|
2017-05-07 12:34:27 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-07 12:53:35 +00:00
|
|
|
|
f.seek(0);
|
2017-05-07 12:34:27 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Dive mode, can be: OC, APNEA, BOTTOM TIMER, CCR, CCR SENSORBOARD
|
|
|
|
|
* Note that we scan over the "Log interval" on purpose
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
while ((parseLine = f.readLine().trimmed()).length() > 0 && !f.atEnd()) {
|
|
|
|
|
QString needle = "//Mode: ";
|
|
|
|
|
if (parseLine.contains(needle)) {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add(params, "diveMode",
|
|
|
|
|
qPrintable(parseLine.replace(needle, QString::fromLatin1("")).prepend("\"").append("\"")));
|
2017-05-07 10:41:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-07 12:53:35 +00:00
|
|
|
|
f.seek(0);
|
|
|
|
|
|
2017-05-07 18:22:00 +00:00
|
|
|
|
/*
|
|
|
|
|
* Grabbing some fields for the extradata
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
while ((parseLine = f.readLine().trimmed()).length() > 0 && !f.atEnd()) {
|
|
|
|
|
QString needle = "//Firmware Version: ";
|
|
|
|
|
if (parseLine.contains(needle)) {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add(params, "Firmware",
|
|
|
|
|
qPrintable(parseLine.replace(needle, QString::fromLatin1("")).prepend("\"").append("\"")));
|
2017-05-07 18:22:00 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
f.seek(0);
|
|
|
|
|
|
|
|
|
|
while ((parseLine = f.readLine().trimmed()).length() > 0 && !f.atEnd()) {
|
|
|
|
|
QString needle = "//Serial number: ";
|
|
|
|
|
if (parseLine.contains(needle)) {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add(params, "Serial",
|
|
|
|
|
qPrintable(parseLine.replace(needle, QString::fromLatin1("")).prepend("\"").append("\"")));
|
2017-05-07 18:22:00 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
f.seek(0);
|
|
|
|
|
|
|
|
|
|
while ((parseLine = f.readLine().trimmed()).length() > 0 && !f.atEnd()) {
|
|
|
|
|
QString needle = "//GF: ";
|
|
|
|
|
if (parseLine.contains(needle)) {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add(params, "GF",
|
|
|
|
|
qPrintable(parseLine.replace(needle, QString::fromLatin1("")).prepend("\"").append("\"")));
|
2017-05-07 18:22:00 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
f.seek(0);
|
|
|
|
|
|
2017-05-07 12:53:35 +00:00
|
|
|
|
while ((parseLine = f.readLine().trimmed()).length() > 0 && !f.atEnd()) {
|
|
|
|
|
}
|
2017-05-07 10:41:09 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Parse CSV fields
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
parseLine = f.readLine().trimmed();
|
|
|
|
|
|
2019-04-01 20:15:19 +00:00
|
|
|
|
const QStringList currColumns = parseLine.split(';');
|
2017-05-07 10:41:09 +00:00
|
|
|
|
unsigned short index = 0;
|
2019-04-01 20:15:19 +00:00
|
|
|
|
for (const QString &columnText: currColumns) {
|
2017-05-07 10:41:09 +00:00
|
|
|
|
if (columnText == "Time") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "timeField", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "Depth") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "depthField", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "Temperature") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "tempField", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "NDT") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "ndlField", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "TTS") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "ttsField", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "pO2_1") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "o2sensor1Field", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "pO2_2") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "o2sensor2Field", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "pO2_3") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "o2sensor3Field", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "Ceiling") {
|
|
|
|
|
/* TODO: Add support for dive computer reported ceiling*/
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "ceilingField", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else if (columnText == "Tank pressure") {
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "pressureField", index++);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
} else {
|
|
|
|
|
// We do not know about this value
|
|
|
|
|
qDebug() << "Seabear import found an un-handled field: " << columnText;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Separator is ';' and the index for that in DiveLogImportDialog constructor is 2 */
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "separatorIndex", 2);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
|
|
|
|
|
/* And metric units */
|
2020-10-17 18:15:23 +00:00
|
|
|
|
xml_params_add_int(params, "units", 0);
|
2017-05-07 10:41:09 +00:00
|
|
|
|
|
|
|
|
|
f.close();
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-09 14:43:38 +00:00
|
|
|
|
extern "C" void print_qt_versions()
|
|
|
|
|
{
|
2018-02-25 12:51:41 +00:00
|
|
|
|
printf("%s\n", qPrintable(QStringLiteral("built with Qt Version %1, runtime from Qt Version %2").arg(QT_VERSION_STR).arg(qVersion())));
|
2017-11-09 14:43:38 +00:00
|
|
|
|
}
|
2017-08-29 09:41:30 +00:00
|
|
|
|
|
|
|
|
|
QMutex planLock;
|
|
|
|
|
|
|
|
|
|
extern "C" void lock_planner()
|
|
|
|
|
{
|
|
|
|
|
planLock.lock();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" void unlock_planner()
|
|
|
|
|
{
|
|
|
|
|
planLock.unlock();
|
|
|
|
|
}
|
2018-02-21 21:35:50 +00:00
|
|
|
|
|
2018-02-28 22:37:09 +00:00
|
|
|
|
char *copy_qstring(const QString &s)
|
|
|
|
|
{
|
|
|
|
|
return strdup(qPrintable(s));
|
|
|
|
|
}
|
2020-03-05 16:57:02 +00:00
|
|
|
|
|
2020-04-01 13:55:12 +00:00
|
|
|
|
// function to call to allow the UI to show updates for longer running activities
|
|
|
|
|
void (*uiNotificationCallback)(QString msg) = nullptr;
|
|
|
|
|
|
|
|
|
|
void uiNotification(const QString &msg)
|
|
|
|
|
{
|
|
|
|
|
if (uiNotificationCallback != nullptr)
|
|
|
|
|
uiNotificationCallback(msg);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-05 16:57:02 +00:00
|
|
|
|
// function to call to get changes for a git commit
|
|
|
|
|
QString (*changesCallback)() = nullptr;
|
|
|
|
|
|
|
|
|
|
extern "C" char *get_changes_made()
|
|
|
|
|
{
|
|
|
|
|
if (changesCallback != nullptr)
|
|
|
|
|
return copy_qstring(changesCallback());
|
|
|
|
|
else
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2020-02-23 18:24:06 +00:00
|
|
|
|
|
|
|
|
|
// Generate a cylinder-renumber map for use when the n-th cylinder
|
|
|
|
|
// of a dive with count cylinders is removed. It fills an int vector
|
|
|
|
|
// with 0..n, -1, n..count-1. Each entry in the vector represents
|
|
|
|
|
// the new id of the cylinder, whereby <0 means that this particular
|
|
|
|
|
// cylinder does not get any new id. This should probably be moved
|
|
|
|
|
// to the C-core, but using std::vector is simply more convenient.
|
|
|
|
|
// The function assumes that n < count!
|
|
|
|
|
std::vector<int> get_cylinder_map_for_remove(int count, int n)
|
|
|
|
|
{
|
|
|
|
|
// 1) Fill mapping[0]..mapping[n-1] with 0..n-1
|
|
|
|
|
// 2) Set mapping[n] to -1
|
|
|
|
|
// 3) Fill mapping[n+1]..mapping[count-1] with n..count-2
|
|
|
|
|
std::vector<int> mapping(count);
|
|
|
|
|
std::iota(mapping.begin(), mapping.begin() + n, 0);
|
|
|
|
|
mapping[n] = -1;
|
|
|
|
|
std::iota(mapping.begin() + n + 1, mapping.end(), n);
|
|
|
|
|
return mapping;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate a cylinder-renumber map for use when a cylinder is added
|
|
|
|
|
// before the n-th cylinder. It fills an int vector with
|
|
|
|
|
// with 0..n-1, n+1..count. Each entry in the vector represents
|
|
|
|
|
// the new id of the cylinder. This probably should be moved
|
|
|
|
|
// to the C-core, but using std::vector is simply more convenient.
|
|
|
|
|
// This function assumes that that n <= count!
|
|
|
|
|
std::vector<int> get_cylinder_map_for_add(int count, int n)
|
|
|
|
|
{
|
|
|
|
|
std::vector<int> mapping(count);
|
|
|
|
|
std::iota(mapping.begin(), mapping.begin() + n, 0);
|
|
|
|
|
std::iota(mapping.begin() + n, mapping.end(), n + 1);
|
|
|
|
|
return mapping;
|
|
|
|
|
}
|
cleanup: invert control-flow when resetting the core structures
To reset the core data structures, the mobile and desktop UIs
were calling into the dive-list models, which then reset the
core data structures, themselves and the unrelated
locationinformation model. The UI code then reset various other
things, such as the TankInformation model or the map. . This was
unsatisfying from a control-flow perspective, as the models should
display the core data, not act on it. Moreover, this meant lots
of intricate intermodule-dependencies.
Thus, straighten up the control flow: give the C core the
possibility to send a "all data reset" event. And do that
in those functions that reset the core data structures.
Let each module react to this event by itself. This removes
inter-module dependencies. For example, the MainWindow now
doesn't have to reset the TankInfoModel or the MapWidget.
Then, to reset the core data structures, let the UI code
simply directly call the respective core functions.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2020-05-04 22:12:36 +00:00
|
|
|
|
|
|
|
|
|
extern "C" void emit_reset_signal()
|
|
|
|
|
{
|
|
|
|
|
emit diveListNotifier.dataReset();
|
|
|
|
|
}
|