summaryrefslogtreecommitdiffstats
path: root/qthelper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'qthelper.cpp')
-rw-r--r--qthelper.cpp673
1 files changed, 621 insertions, 52 deletions
diff --git a/qthelper.cpp b/qthelper.cpp
index acb1e103a..bbe908e6f 100644
--- a/qthelper.cpp
+++ b/qthelper.cpp
@@ -1,6 +1,15 @@
#include "qthelper.h"
+#include "helpers.h"
#include "gettextfromc.h"
#include "statistics.h"
+#include "usersurvey.h"
+#include "membuffer.h"
+#include "subsurfacesysinfo.h"
+#include "version.h"
+#include "divecomputer.h"
+#include "time.h"
+#include "gettextfromc.h"
+#include <sys/time.h>
#include <exif.h>
#include "file.h"
#include <QFile>
@@ -8,8 +17,42 @@
#include <QDir>
#include <QDebug>
#include <QSettings>
+#include <QStandardPaths>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QNetworkAccessManager>
+#include <QUrlQuery>
+#include <QEventLoop>
+#include <QDateTime>
+#include <QSaveFile>
+#include <QDir>
+#include <QImageReader>
+#include <QtConcurrent>
+#include "divepicturewidget.h"
+
#include <libxslt/documents.h>
+const char *existing_filename;
+static QString shortDateFormat;
+static QString dateFormat;
+static QString timeFormat;
+static QLocale loc;
+
+#if defined(Q_OS_WIN) && QT_VERSION < 0x050000
+static QByteArray encodeUtf8(const QString &fname)
+{
+ return fname.toUtf8();
+}
+
+static QString decodeUtf8(const QByteArray &fname)
+{
+ return QString::fromUtf8(fname);
+}
+#endif
+
#define translate(_context, arg) trGettext(arg)
static const QString DEGREE_SIGNS("dD" UTF8_DEGREE);
@@ -31,7 +74,7 @@ QString weight_string(int weight_in_grams)
return (str);
}
-QString printGPSCoords(int lat, int lon)
+extern "C" const char *printGPSCoords(int lat, int lon)
{
unsigned int latdeg, londeg;
unsigned int latmin, lonmin;
@@ -39,24 +82,31 @@ QString printGPSCoords(int lat, int lon)
QString lath, lonh, result;
if (!lat && !lon)
- return QString();
-
- lath = lat >= 0 ? translate("gettextFromC", "N") : translate("gettextFromC", "S");
- lonh = lon >= 0 ? translate("gettextFromC", "E") : translate("gettextFromC", "W");
- 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;
- result.sprintf("%u%s%02d\'%06.3f\"%s %u%s%02d\'%06.3f\"%s",
- latdeg, UTF8_DEGREE, latmin / 1000000, latsec / 1000000, lath.toUtf8().data(),
- londeg, UTF8_DEGREE, lonmin / 1000000, lonsec / 1000000, lonh.toUtf8().data());
- return result;
+ return strdup("");
+
+ if (prefs.coordinates_traditional) {
+ lath = lat >= 0 ? translate("gettextFromC", "N") : translate("gettextFromC", "S");
+ lonh = lon >= 0 ? translate("gettextFromC", "E") : translate("gettextFromC", "W");
+ 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;
+ result.sprintf("%u%s%02d\'%06.3f\"%s %u%s%02d\'%06.3f\"%s",
+ latdeg, UTF8_DEGREE, latmin / 1000000, latsec / 1000000, lath.toUtf8().data(),
+ londeg, UTF8_DEGREE, lonmin / 1000000, lonsec / 1000000, lonh.toUtf8().data());
+ } else {
+ result.sprintf("%f %f", (double) lat / 1000000.0, (double) lon / 1000000.0);
+ }
+ return strdup(result.toUtf8().data());
}
+/**
+* Try to parse in a generic manner a coordinate.
+*/
static bool parseCoord(const QString& txt, int& pos, const QString& positives,
const QString& negatives, const QString& others,
double& value)
@@ -78,7 +128,7 @@ static bool parseCoord(const QString& txt, int& pos, const QString& positives,
numberDefined = true;
posBeforeNumber = pos;
pos += numberRe.cap(1).size() - 1;
- } else if (positives.indexOf(txt[pos].toUpper()) >= 0) {
+ } else if (positives.indexOf(txt[pos]) >= 0) {
if (sign != 0)
return false;
sign = 1;
@@ -88,7 +138,7 @@ static bool parseCoord(const QString& txt, int& pos, const QString& positives,
++pos;
break;
}
- } else if (negatives.indexOf(txt[pos].toUpper()) >= 0) {
+ } else if (negatives.indexOf(txt[pos]) >= 0) {
if (sign != 0) {
if (others.indexOf(txt[pos]) >= 0)
//special case for the '-' sign => next coordinate
@@ -102,10 +152,11 @@ static bool parseCoord(const QString& txt, int& pos, const QString& positives,
++pos;
break;
}
- } else if (others.indexOf(txt[pos].toUpper()) >= 0) {
+ } else if (others.indexOf(txt[pos]) >= 0) {
//we are at the next coordinate.
break;
- } else if (DEGREE_SIGNS.indexOf(txt[pos]) >= 0) {
+ } else if (DEGREE_SIGNS.indexOf(txt[pos]) >= 0 ||
+ (txt[pos].isSpace() && !degreesDefined && numberDefined)) {
if (!numberDefined)
return false;
if (degreesDefined) {
@@ -117,20 +168,18 @@ static bool parseCoord(const QString& txt, int& pos, const QString& positives,
value += number;
numberDefined = false;
degreesDefined = true;
- } else if (txt[pos] == '\'') {
+ } else if (txt[pos] == '\'' || (txt[pos].isSpace() && !minutesDefined && numberDefined)) {
if (!numberDefined || minutesDefined)
return false;
value += number / 60.0;
numberDefined = false;
minutesDefined = true;
- } else if (txt[pos] == '"') {
+ } else if (txt[pos] == '"' || (txt[pos].isSpace() && !secondsDefined && numberDefined)) {
if (!numberDefined || secondsDefined)
return false;
value += number / 3600.0;
numberDefined = false;
secondsDefined = true;
- } else if (txt[pos] == ' ' || txt[pos] == '\t') {
- //ignore spaces
} else {
return false;
}
@@ -138,33 +187,56 @@ static bool parseCoord(const QString& txt, int& pos, const QString& positives,
}
if (!degreesDefined && numberDefined) {
value = number; //just a single number => degrees
- numberDefined = false;
- degreesDefined = true;
- }
- if (!degreesDefined || numberDefined)
+ } else if (!minutesDefined && numberDefined) {
+ value += number / 60.0;
+ } else if (!secondsDefined && numberDefined) {
+ value += number / 3600.0;
+ } else if (numberDefined) {
return false;
+ }
if (sign == -1) value *= -1.0;
return true;
}
+/**
+* Parse special coordinate formats that cannot be handled by parseCoord.
+*/
+static bool parseSpecialCoords(const QString& txt, double& latitude, double& longitude) {
+ QRegExp xmlFormat("(-?\\d+(?:\\.\\d+)?)\\s+(-?\\d+(?:\\.\\d+)?)");
+ if (xmlFormat.exactMatch(txt)) {
+ latitude = xmlFormat.cap(1).toDouble();
+ longitude = xmlFormat.cap(2).toDouble();
+ return true;
+ }
+ return false;
+}
+
bool parseGpsText(const QString &gps_text, double *latitude, double *longitude)
{
- const QString trimmed = gps_text.trimmed();
- if (trimmed.isEmpty()) {
+ static const QString POS_LAT = QString("+N") + translate("gettextFromC", "N");
+ static const QString NEG_LAT = QString("-S") + translate("gettextFromC", "S");
+ static const QString POS_LON = QString("+E") + translate("gettextFromC", "E");
+ static const QString NEG_LON = QString("-W") + translate("gettextFromC", "W");
+
+ //remove the useless spaces (but keep the ones separating numbers)
+ static const QRegExp SPACE_CLEANER("\\s*([" + POS_LAT + NEG_LAT + POS_LON +
+ NEG_LON + DEGREE_SIGNS + "'\"\\s])\\s*");
+ const QString normalized = gps_text.trimmed().toUpper().replace(SPACE_CLEANER, "\\1");
+
+ if (normalized.isEmpty()) {
*latitude = 0.0;
*longitude = 0.0;
return true;
}
+ if (parseSpecialCoords(normalized, *latitude, *longitude))
+ return true;
int pos = 0;
- static const QString POS_LAT = QString("+N") + translate("gettextFromC", "N");
- static const QString NEG_LAT = QString("-S") + translate("gettextFromC", "S");
- static const QString POS_LON = QString("+E") + translate("gettextFromC", "E");
- static const QString NEG_LON = QString("-W") + translate("gettextFromC", "W");
- return parseCoord(gps_text, pos, POS_LAT, NEG_LAT, POS_LON + NEG_LON, *latitude) &&
- parseCoord(gps_text, pos, POS_LON, NEG_LON, "", *longitude) &&
- pos == gps_text.size();
+ return parseCoord(normalized, pos, POS_LAT, NEG_LAT, POS_LON + NEG_LON, *latitude) &&
+ parseCoord(normalized, pos, POS_LON, NEG_LON, "", *longitude) &&
+ pos == normalized.size();
}
+#if 0 // we'll need something like this for the dive site management, eventually
bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out)
{
double latitude, longitude;
@@ -193,6 +265,7 @@ bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_te
dive->longitude.udeg = longudeg;
return true;
}
+#endif
QList<int> getDivesInTrip(dive_trip_t *trip)
{
@@ -235,9 +308,12 @@ int dive_getUniqID(struct dive *d)
static xmlDocPtr get_stylesheet_doc(const xmlChar *uri, xmlDictPtr, int, void *, xsltLoadType)
{
QFile f(QLatin1String(":/xslt/") + (const char *)uri);
- if (!f.open(QIODevice::ReadOnly))
- return NULL;
-
+ if (!f.open(QIODevice::ReadOnly)) {
+ if (verbose > 0) {
+ qDebug() << "cannot open stylesheet" << QLatin1String(":/xslt/") + (const char *)uri;
+ return NULL;
+ }
+ }
/* Load and parse the data */
QByteArray source = f.readAll();
@@ -265,22 +341,41 @@ extern "C" xsltStylesheetPtr get_stylesheet(const char *name)
return xslt;
}
-extern "C" void picture_load_exif_data(struct picture *p, timestamp_t *timestamp)
+
+extern "C" timestamp_t picture_get_timestamp(char *filename)
{
EXIFInfo exif;
memblock mem;
+ int retval;
- if (readfile(p->filename, &mem) <= 0)
- goto picture_load_exit;
- if (exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size) != PARSE_EXIF_SUCCESS)
- goto picture_load_exit;
- *timestamp = exif.epoch();
- p->longitude.udeg= lrint(1000000.0 * exif.GeoLocation.Longitude);
- p->latitude.udeg = lrint(1000000.0 * exif.GeoLocation.Latitude);
-
-picture_load_exit:
+ // filename might not be the actual filename, so let's go via the hash.
+ if (readfile(localFilePath(QString(filename)).toUtf8().data(), &mem) <= 0)
+ return 0;
+ retval = exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size);
free(mem.buffer);
- return;
+ if (retval != PARSE_EXIF_SUCCESS)
+ return 0;
+ return exif.epoch();
+}
+
+extern "C" const char *system_default_directory(void)
+{
+ static char filename[PATH_MAX];
+
+ if (!*filename) {
+ enum QStandardPaths::StandardLocation location;
+#if QT_VERSION >= 0x050400
+ location = QStandardPaths::AppDataLocation;
+#else
+ location = QStandardPaths::DataLocation;
+#endif
+ QString name = QStandardPaths::standardLocations(location).first();
+ QDir dir(name);
+ dir.mkpath(name);
+ // Why no "dir.encodeName()"? Crazy Qt
+ strncpy(filename, QFile::encodeName(name), PATH_MAX-1);
+ }
+ return filename;
}
extern "C" char *get_file_name(const char *fileName)
@@ -340,3 +435,477 @@ void selectedDivesGasUsed(QVector<QPair<QString, int> > &gasUsedOrdered)
}
qSort(gasUsedOrdered.begin(), gasUsedOrdered.end(), lessThan);
}
+
+QString getUserAgent()
+{
+ QString arch;
+ // fill in the system data - use ':' as separator
+ // replace all other ':' with ' ' so that this is easy to parse
+ QString userAgent = QString("Subsurface:%1:").arg(subsurface_version());
+ userAgent.append(SubsurfaceSysInfo::prettyOsName().replace(':', ' ') + ":");
+ arch = SubsurfaceSysInfo::buildCpuArchitecture().replace(':', ' ');
+ userAgent.append(arch);
+ if (arch == "i386")
+ userAgent.append("/" + SubsurfaceSysInfo::currentCpuArchitecture());
+ userAgent.append(":" + uiLanguage(NULL));
+ return userAgent;
+
+}
+
+QString uiLanguage(QLocale *callerLoc)
+{
+ QSettings s;
+ s.beginGroup("Language");
+
+ if (!s.value("UseSystemLanguage", true).toBool()) {
+ loc = QLocale(s.value("UiLanguage", QLocale().uiLanguages().first()).toString());
+ } else {
+ loc = QLocale(QLocale().uiLanguages().first());
+ }
+
+ QString uiLang = loc.uiLanguages().first();
+ s.endGroup();
+
+ // 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;
+ uiLang = loc2.uiLanguages().first();
+ }
+ if (callerLoc)
+ *callerLoc = loc;
+
+ // the short format is fine
+ // 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
+ shortDateFormat = loc.dateFormat(QLocale::ShortFormat);
+ 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");
+ 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", "");
+ return uiLang;
+}
+
+QLocale getLocale()
+{
+ return loc;
+}
+
+QString getDateFormat()
+{
+ return dateFormat;
+}
+void set_filename(const char *filename, bool force)
+{
+ if (!force && existing_filename)
+ return;
+ free((void *)existing_filename);
+ if (filename)
+ existing_filename = strdup(filename);
+ else
+ existing_filename = NULL;
+}
+
+const QString get_dc_nickname(const char *model, uint32_t deviceid)
+{
+ const DiveComputerNode *existNode = dcList.getExact(model, deviceid);
+
+ if (existNode && !existNode->nickName.isEmpty())
+ return existNode->nickName;
+ else
+ return model;
+}
+
+QString get_depth_string(int mm, bool showunit, bool showdecimal)
+{
+ if (prefs.units.length == units::METERS) {
+ double meters = mm / 1000.0;
+ return QString("%1%2").arg(meters, 0, 'f', (showdecimal && meters < 20.0) ? 1 : 0).arg(showunit ? translate("gettextFromC", "m") : "");
+ } else {
+ double feet = mm_to_feet(mm);
+ return QString("%1%2").arg(feet, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "ft") : "");
+ }
+}
+
+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)
+ return QString("%1").arg(translate("gettextFromC", "m"));
+ else
+ return QString("%1").arg(translate("gettextFromC", "ft"));
+}
+
+QString get_weight_string(weight_t weight, bool showunit)
+{
+ QString str = weight_string(weight.grams);
+ if (get_units()->weight == units::KG) {
+ str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "kg") : "");
+ } else {
+ str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "lbs") : "");
+ }
+ return (str);
+}
+
+QString get_weight_unit()
+{
+ if (prefs.units.weight == units::KG)
+ return QString("%1").arg(translate("gettextFromC", "kg"));
+ else
+ return QString("%1").arg(translate("gettextFromC", "lbs"));
+}
+
+/* these methods retrieve used gas per cylinder */
+static unsigned start_pressure(cylinder_t *cyl)
+{
+ return cyl->start.mbar ?: cyl->sample_start.mbar;
+}
+
+static unsigned end_pressure(cylinder_t *cyl)
+{
+ return cyl->end.mbar ?: cyl->sample_end.mbar;
+}
+
+QString get_cylinder_used_gas_string(cylinder_t *cyl, bool showunit)
+{
+ int decimals;
+ const char *unit;
+ double gas_usage;
+ /* Get the cylinder gas use in mbar */
+ gas_usage = start_pressure(cyl) - end_pressure(cyl);
+ /* Can we turn it into a volume? */
+ if (cyl->type.size.mliter) {
+ gas_usage = bar_to_atm(gas_usage / 1000);
+ gas_usage *= cyl->type.size.mliter;
+ gas_usage = get_volume_units(gas_usage, &decimals, &unit);
+ } else {
+ gas_usage = get_pressure_units(gas_usage, &unit);
+ decimals = 0;
+ }
+ // translate("gettextFromC","%.*f %s"
+ return QString("%1 %2").arg(gas_usage, 0, 'f', decimals).arg(showunit ? unit : "");
+}
+
+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);
+ return QString("%1%2%3").arg(celsius, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "C") : "");
+ } else {
+ double fahrenheit = mkelvin_to_F(temp.mkelvin);
+ return QString("%1%2%3").arg(fahrenheit, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "F") : "");
+ }
+}
+
+QString get_temp_unit()
+{
+ if (prefs.units.temperature == units::CELSIUS)
+ return QString(UTF8_DEGREE "C");
+ else
+ return QString(UTF8_DEGREE "F");
+}
+
+QString get_volume_string(volume_t volume, bool showunit, int mbar)
+{
+ const char *unit;
+ int decimals;
+ double value = get_volume_units(volume.mliter, &decimals, &unit);
+ if (mbar) {
+ // we are showing a tank size
+ // fix the weird imperial way of denominating size and provide
+ // reasonable number of decimals
+ if (prefs.units.volume == units::CUFT)
+ value *= bar_to_atm(mbar / 1000.0);
+ decimals = (value > 20.0) ? 0 : (value > 2.0) ? 1 : 2;
+ }
+ return QString("%1%2").arg(value, 0, 'f', decimals).arg(showunit ? unit : "");
+}
+
+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;
+ return QString("%1%2").arg(bar, 0, 'f', 1).arg(showunit ? translate("gettextFromC", "bar") : "");
+ } else {
+ double psi = mbar_to_PSI(pressure.mbar);
+ return QString("%1%2").arg(psi, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "psi") : "");
+ }
+}
+
+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();
+ return QString("");
+}
+
+int gettimezoneoffset(timestamp_t when)
+{
+ QDateTime dt1, dt2;
+ if (when == 0)
+ dt1 = QDateTime::currentDateTime();
+ else
+ dt1 = QDateTime::fromMSecsSinceEpoch(when * 1000);
+ dt2 = dt1.toUTC();
+ dt1.setTimeSpec(Qt::UTC);
+ return dt2.secsTo(dt1);
+}
+
+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();
+ 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;
+ }
+ return mkelvin;
+}
+
+QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText)
+{
+ int hrs, mins;
+ mins = (when + 59) / 60;
+ hrs = mins / 60;
+ mins -= hrs * 60;
+
+ QString displayTime;
+ if (hrs)
+ displayTime = QString("%1%2%3%4").arg(hrs).arg(hourText).arg(mins, 2, 10, QChar('0')).arg(minutesText);
+ else
+ displayTime = QString("%1%2").arg(mins).arg(minutesText);
+
+ return displayTime;
+}
+
+QString get_dive_date_string(timestamp_t when)
+{
+ QDateTime ts;
+ ts.setMSecsSinceEpoch(when * 1000L);
+ return loc.toString(ts.toUTC(), dateFormat + " " + timeFormat);
+}
+
+QString get_short_dive_date_string(timestamp_t when)
+{
+ QDateTime ts;
+ ts.setMSecsSinceEpoch(when * 1000L);
+ return loc.toString(ts.toUTC(), shortDateFormat + " " + timeFormat);
+}
+
+const char *get_dive_date_c_string(timestamp_t when)
+{
+ QString text = get_dive_date_string(when);
+ return strdup(text.toUtf8().data());
+}
+
+QString get_trip_date_string(timestamp_t when, int nr)
+{
+ struct tm tm;
+ utc_mkdate(when, &tm);
+ if (nr != 1) {
+ QString ret = translate("gettextFromC", "%1 %2 (%3 dives)");
+ return ret.arg(monthname(tm.tm_mon))
+ .arg(tm.tm_year + 1900)
+ .arg(nr);
+ } else {
+ QString ret = translate("gettextFromC", "%1 %2 (1 dive)");
+ return ret.arg(monthname(tm.tm_mon))
+ .arg(tm.tm_year + 1900);
+ }
+}
+
+extern "C" void reverseGeoLookup(degrees_t latitude, degrees_t longitude, uint32_t uuid)
+{
+ QNetworkRequest request;
+ QNetworkAccessManager *rgl = new QNetworkAccessManager();
+ request.setUrl(QString("http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&accept-language=%1&lat=%2&lon=%3")
+ .arg(uiLanguage(NULL)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0));
+ request.setRawHeader("Accept", "text/json");
+ request.setRawHeader("User-Agent", getUserAgent().toUtf8());
+ QNetworkReply *reply = rgl->get(request);
+ QEventLoop loop;
+ QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
+ loop.exec();
+ QJsonParseError errorObject;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &errorObject);
+ if (errorObject.error != QJsonParseError::NoError) {
+ qDebug() << errorObject.errorString();
+ } else {
+ QJsonObject obj = jsonDoc.object();
+ QJsonObject address = obj.value("address").toObject();
+ qDebug() << "found country:" << address.value("country").toString();
+ struct dive_site *ds = get_dive_site_by_uuid(uuid);
+ ds->notes = add_to_string(ds->notes, "countrytag: %s", address.value("country").toString().toUtf8().data());
+ }
+}
+
+QHash<QString, QByteArray> hashOf;
+QMutex hashOfMutex;
+QHash<QByteArray, QString> localFilenameOf;
+
+extern "C" char * hashstring(char * filename)
+{
+ return hashOf[QString(filename)].toHex().data();
+}
+
+void read_hashes()
+{
+ QFile hashfile(QString(system_default_directory()).append("/hashes"));
+ if (hashfile.open(QIODevice::ReadOnly)) {
+ QDataStream stream(&hashfile);
+ stream >> localFilenameOf;
+ hashfile.close();
+ }
+}
+
+void write_hashes()
+{
+ QSaveFile hashfile(QString(system_default_directory()).append("/hashes"));
+ if (hashfile.open(QIODevice::WriteOnly)) {
+ QDataStream stream(&hashfile);
+ stream << localFilenameOf;
+ hashfile.commit();
+ } else {
+ qDebug() << "cannot open" << hashfile.fileName();
+ }
+}
+
+void add_hash(const QString filename, QByteArray hash)
+{
+ QMutexLocker locker(&hashOfMutex);
+ hashOf[filename] = hash;
+ localFilenameOf[hash] = filename;
+}
+
+QByteArray hashFile(const QString filename)
+{
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ QFile imagefile(filename);
+ imagefile.open(QIODevice::ReadOnly);
+ hash.addData(&imagefile);
+ add_hash(filename, hash.result());
+ return hash.result();
+}
+
+void learnHash(struct picture *picture, QByteArray hash)
+{
+ if (picture->hash)
+ free(picture->hash);
+ QMutexLocker locker(&hashOfMutex);
+ hashOf[QString(picture->filename)] = hash;
+ picture->hash = strdup(hash.toHex());
+}
+
+QString localFilePath(const QString originalFilename)
+{
+ if (hashOf.contains(originalFilename) && localFilenameOf.contains(hashOf[originalFilename]))
+ return localFilenameOf[hashOf[originalFilename]];
+ else
+ return originalFilename;
+}
+
+QString fileFromHash(char *hash)
+{
+ return localFilenameOf[QByteArray::fromHex(hash)];
+}
+
+void updateHash(struct picture *picture) {
+ QByteArray hash = hashFile(fileFromHash(picture->hash));
+ QMutexLocker locker(&hashOfMutex);
+ hashOf[QString(picture->filename)] = hash;
+ char *old = picture->hash;
+ picture->hash = strdup(hash.toHex());
+ free(old);
+}
+
+void learnImages(const QDir dir, int max_recursions, bool recursed)
+{
+ QDir current(dir);
+ QStringList filters, files;
+
+ if (max_recursions) {
+ foreach (QString dirname, dir.entryList(QStringList(), QDir::NoDotAndDotDot | QDir::Dirs)) {
+ learnImages(QDir(dir.filePath(dirname)), max_recursions - 1, true);
+ }
+ }
+
+ foreach (QString format, QImageReader::supportedImageFormats()) {
+ filters.append(QString("*.").append(format));
+ }
+
+ foreach (QString file, dir.entryList(filters, QDir::Files)) {
+ files.append(dir.absoluteFilePath(file));
+ }
+
+ QtConcurrent::blockingMap(files, hashFile);
+}
+
+extern "C" void picture_load_exif_data(struct picture *p)
+{
+ EXIFInfo exif;
+ memblock mem;
+
+ if (readfile(localFilePath(QString(p->filename)).toUtf8().data(), &mem) <= 0)
+ goto picture_load_exit;
+ if (exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size) != PARSE_EXIF_SUCCESS)
+ goto picture_load_exit;
+ p->longitude.udeg= lrint(1000000.0 * exif.GeoLocation.Longitude);
+ p->latitude.udeg = lrint(1000000.0 * exif.GeoLocation.Latitude);
+
+picture_load_exit:
+ free(mem.buffer);
+ return;
+}