summaryrefslogtreecommitdiffstats
path: root/subsurface-core/qthelper.cpp
diff options
context:
space:
mode:
authorGravatar Tomaz Canabrava <tomaz.canabrava@intel.com>2015-09-02 20:52:34 -0300
committerGravatar Dirk Hohndel <dirk@hohndel.org>2015-10-30 10:36:48 -0700
commit4c0156e3d51b389db8eccc3fa3da4b8f248f9b13 (patch)
tree966868d29150fdba13a5a56fb4305bc432ec7a72 /subsurface-core/qthelper.cpp
parenta0798214231c652ac6142228f5ddfc4b65c921f8 (diff)
downloadsubsurface-4c0156e3d51b389db8eccc3fa3da4b8f248f9b13.tar.gz
Move all core-functionality to subsurface-core
And adapt a new CMakeLists.txt file for it. On the way I've also found out that we where double-compilling a few files. I've also set the subsurface-core as a include_path but that was just to reduce the noise on this commit, since I plan to remove it from the include path to make it obligatory to specify something like include "subsurface-core/dive.h" for the header files. Since the app is growing quite a bit we ended up having a few different files with almost same name that did similar things, I want to kill that (for instance Dive.h, dive.h, PrintDive.h and such). Signed-off-by: Tomaz Canabrava <tomaz.canabrava@intel.com> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Diffstat (limited to 'subsurface-core/qthelper.cpp')
-rw-r--r--subsurface-core/qthelper.cpp1653
1 files changed, 1653 insertions, 0 deletions
diff --git a/subsurface-core/qthelper.cpp b/subsurface-core/qthelper.cpp
new file mode 100644
index 000000000..a12d25333
--- /dev/null
+++ b/subsurface-core/qthelper.cpp
@@ -0,0 +1,1653 @@
+#include "qthelper.h"
+#include "helpers.h"
+#include "gettextfromc.h"
+#include "statistics.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 "prefs-macros.h"
+#include <QFile>
+#include <QRegExp>
+#include <QDir>
+#include <QDebug>
+#include <QSettings>
+#include <QStandardPaths>
+#include <QJsonDocument>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QNetworkAccessManager>
+#include <QNetworkProxy>
+#include <QDateTime>
+#include <QImageReader>
+#include <QtConcurrent>
+#include <QFont>
+#include <QApplication>
+#include <QTextDocument>
+
+#include <libxslt/documents.h>
+
+const char *existing_filename;
+static QString shortDateFormat;
+static QString dateFormat;
+static QString timeFormat;
+static QLocale loc;
+
+#define translate(_context, arg) trGettext(arg)
+static const QString DEGREE_SIGNS("dD" UTF8_DEGREE);
+
+Dive::Dive() :
+ m_number(-1),
+ dive(NULL)
+{
+}
+
+Dive::~Dive()
+{
+}
+
+int Dive::number() const
+{
+ return m_number;
+}
+
+int Dive::id() const
+{
+ return m_id;
+}
+
+QString Dive::date() const
+{
+ return m_date;
+}
+
+timestamp_t Dive::timestamp() const
+{
+ return m_timestamp;
+}
+
+QString Dive::time() const
+{
+ return m_time;
+}
+
+QString Dive::location() const
+{
+ return m_location;
+}
+
+QString Dive::duration() const
+{
+ return m_duration;
+}
+
+QString Dive::depth() const
+{
+ return m_depth;
+}
+
+QString Dive::divemaster() const
+{
+ return m_divemaster;
+}
+
+QString Dive::buddy() const
+{
+ return m_buddy;
+}
+
+QString Dive::airTemp() const
+{
+ return m_airTemp;
+}
+
+QString Dive::waterTemp() const
+{
+ return m_waterTemp;
+}
+
+QString Dive::notes() const
+{
+ return m_notes;
+}
+
+QString Dive::tags() const
+{
+ return m_tags;
+}
+
+QString Dive::gas() const
+{
+ return m_gas;
+}
+
+QString Dive::sac() const
+{
+ return m_sac;
+}
+
+QString Dive::weight() const
+{
+ return m_weight;
+}
+
+QString Dive::suit() const
+{
+ return m_suit;
+}
+
+QString Dive::cylinder() const
+{
+ return m_cylinder;
+}
+
+QString Dive::trip() const
+{
+ return m_trip;
+}
+
+int Dive::rating() const
+{
+ return m_rating;
+}
+
+void Dive::put_divemaster()
+{
+ if (!dive->divemaster)
+ m_divemaster = "--";
+ else
+ m_divemaster = dive->divemaster;
+}
+
+void Dive::put_date_time()
+{
+ QDateTime localTime = QDateTime::fromTime_t(dive->when - gettimezoneoffset(displayed_dive.when));
+ localTime.setTimeSpec(Qt::UTC);
+ m_date = localTime.date().toString(dateFormat);
+ m_time = localTime.time().toString(timeFormat);
+}
+
+void Dive::put_timestamp()
+{
+ m_timestamp = dive->when;
+}
+
+void Dive::put_location()
+{
+ m_location = QString::fromUtf8(get_dive_location(dive));
+ if (m_location.isEmpty()) {
+ m_location = "--";
+ }
+}
+
+void Dive::put_depth()
+{
+ m_depth = get_depth_string(dive->dc.maxdepth.mm, true, true);
+}
+
+void Dive::put_duration()
+{
+ m_duration = get_dive_duration_string(dive->duration.seconds, QObject::tr("h:"), QObject::tr("min"));
+}
+
+void Dive::put_buddy()
+{
+ if (!dive->buddy)
+ m_buddy = "--";
+ else
+ m_buddy = dive->buddy;
+}
+
+void Dive::put_temp()
+{
+ m_airTemp = get_temperature_string(dive->airtemp, true);
+ m_waterTemp = get_temperature_string(dive->watertemp, true);
+ if (m_airTemp.isEmpty()) {
+ m_airTemp = "--";
+ }
+ if (m_waterTemp.isEmpty()) {
+ m_waterTemp = "--";
+ }
+}
+
+void Dive::put_notes()
+{
+ if (same_string(dive->dc.model, "planned dive")) {
+ QTextDocument notes;
+ notes.setHtml(QString::fromUtf8(dive->notes));
+ m_notes = notes.toPlainText();
+ } else {
+ m_notes = QString::fromUtf8(dive->notes);
+ }
+ if (m_notes.isEmpty()) {
+ m_notes = "--";
+ }
+}
+
+void Dive::put_tags()
+{
+ char buffer[256];
+ taglist_get_tagstring(dive->tag_list, buffer, 256);
+ m_tags = QString(buffer);
+}
+
+void Dive::put_gas()
+{
+ int added = 0;
+ QString gas, gases;
+ for (int i = 0; i < MAX_CYLINDERS; i++) {
+ if (!is_cylinder_used(dive, i))
+ continue;
+ gas = dive->cylinder[i].type.description;
+ gas += QString(!gas.isEmpty() ? " " : "") + gasname(&dive->cylinder[i].gasmix);
+ // if has a description and if such gas is not already present
+ if (!gas.isEmpty() && gases.indexOf(gas) == -1) {
+ if (added > 0)
+ gases += QString(" / ");
+ gases += gas;
+ added++;
+ }
+ }
+ m_gas = gases;
+}
+
+void Dive::put_sac()
+{
+ if (dive->sac) {
+ const char *unit;
+ int decimal;
+ double value = get_volume_units(dive->sac, &decimal, &unit);
+ m_sac = QString::number(value, 'f', decimal).append(unit);
+ }
+}
+
+void Dive::put_weight()
+{
+ weight_t tw = { total_weight(dive) };
+ m_weight = weight_string(tw.grams);
+}
+
+void Dive::put_suit()
+{
+ m_suit = QString(dive->suit);
+}
+
+void Dive::put_cylinder()
+{
+ m_cylinder = QString(dive->cylinder[0].type.description);
+}
+
+void Dive::put_trip()
+{
+ dive_trip *trip = dive->divetrip;
+ if (trip) {
+ m_trip = QString(trip->location);
+ }
+}
+
+QString weight_string(int weight_in_grams)
+{
+ QString str;
+ if (get_units()->weight == units::KG) {
+ int gr = weight_in_grams % 1000;
+ int kg = weight_in_grams / 1000;
+ if (kg >= 20.0) {
+ str = QString("0");
+ } else {
+ str = QString("%1.%2").arg(kg).arg((unsigned)(gr) / 100);
+ }
+ } else {
+ double lbs = grams_to_lbs(weight_in_grams);
+ str = QString("%1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1);
+ }
+ return (str);
+}
+
+QString distance_string(int distanceInMeters)
+{
+ QString str;
+ if(get_units()->length == units::METERS) {
+ if (distanceInMeters >= 1000)
+ str = QString(translate("gettextFromC", "%1km")).arg(distanceInMeters / 1000);
+ else
+ str = QString(translate("gettextFromC", "%1m")).arg(distanceInMeters);
+ } else {
+ double miles = m_to_mile(distanceInMeters);
+ if (miles >= 1.0)
+ str = QString(translate("gettextFromC", "%1mi")).arg((int)miles);
+ else
+ str = QString(translate("gettextFromC", "%1yd")).arg((int)(miles * 1760));
+ }
+ return str;
+}
+
+extern "C" const char *printGPSCoords(int lat, int lon)
+{
+ unsigned int latdeg, londeg;
+ unsigned int latmin, lonmin;
+ double latsec, lonsec;
+ QString lath, lonh, result;
+
+ if (!lat && !lon)
+ 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)
+{
+ 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;
+ } else if (positives.indexOf(txt[pos]) >= 0) {
+ if (sign != 0)
+ return false;
+ sign = 1;
+ if (degreesDefined || numberDefined) {
+ //sign after the degrees =>
+ //at the end of the coordinate
+ ++pos;
+ break;
+ }
+ } else if (negatives.indexOf(txt[pos]) >= 0) {
+ 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;
+ }
+ } else if (others.indexOf(txt[pos]) >= 0) {
+ //we are at the next coordinate.
+ break;
+ } else if (DEGREE_SIGNS.indexOf(txt[pos]) >= 0 ||
+ (txt[pos].isSpace() && !degreesDefined && numberDefined)) {
+ 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;
+ } 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] == '"' || (txt[pos].isSpace() && !secondsDefined && numberDefined)) {
+ if (!numberDefined || secondsDefined)
+ return false;
+ value += number / 3600.0;
+ numberDefined = false;
+ secondsDefined = true;
+ } else if ((numberDefined || minutesDefined || secondsDefined) &&
+ (txt[pos] == ',' || txt[pos] == ';')) {
+ // next coordinate coming up
+ // eat the ',' and any subsequent white space
+ while (txt[++pos].isSpace())
+ /* nothing */ ;
+ break;
+ } else {
+ return false;
+ }
+ ++pos;
+ }
+ if (!degreesDefined && numberDefined) {
+ value = number; //just a single number => degrees
+ } 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)
+{
+ 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;
+ 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;
+ int latudeg, longudeg;
+ bool ignore;
+ bool *parsed = parsed_out ?: &ignore;
+ *parsed = true;
+
+ /* if we have a master and the dive's gps address is different from it,
+ * don't change the dive */
+ if (master && (master->latitude.udeg != dive->latitude.udeg ||
+ master->longitude.udeg != dive->longitude.udeg))
+ return false;
+
+ if (!(*parsed = parseGpsText(gps_text, &latitude, &longitude)))
+ return false;
+
+ latudeg = rint(1000000 * latitude);
+ longudeg = rint(1000000 * longitude);
+
+ /* if dive gps didn't change, nothing changed */
+ if (dive->latitude.udeg == latudeg && dive->longitude.udeg == longudeg)
+ return false;
+ /* ok, update the dive and mark things changed */
+ dive->latitude.udeg = latudeg;
+ dive->longitude.udeg = longudeg;
+ return true;
+}
+#endif
+
+QList<int> getDivesInTrip(dive_trip_t *trip)
+{
+ QList<int> ret;
+ int i;
+ struct dive *d;
+ for_each_dive (i, d) {
+ if (d->divetrip == trip) {
+ ret.push_back(get_divenr(d));
+ }
+ }
+ return ret;
+}
+
+// we need this to be uniq, but also make sure
+// it doesn't change during the life time of a Subsurface session
+// oh, and it has no meaning whatsoever - that's why we have the
+// silly initial number and increment by 3 :-)
+int dive_getUniqID(struct dive *d)
+{
+ static QSet<int> ids;
+ static int maxId = 83529;
+
+ int id = d->id;
+ if (id) {
+ if (!ids.contains(id)) {
+ qDebug() << "WTF - only I am allowed to create IDs";
+ ids.insert(id);
+ }
+ return id;
+ }
+ maxId += 3;
+ id = maxId;
+ Q_ASSERT(!ids.contains(id));
+ ids.insert(id);
+ return id;
+}
+
+
+static xmlDocPtr get_stylesheet_doc(const xmlChar *uri, xmlDictPtr, int, void *, xsltLoadType)
+{
+ QFile f(QLatin1String(":/xslt/") + (const char *)uri);
+ 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();
+
+ 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;
+}
+
+
+extern "C" timestamp_t picture_get_timestamp(char *filename)
+{
+ EXIFInfo exif;
+ memblock mem;
+ int retval;
+
+ // 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);
+ if (retval != PARSE_EXIF_SUCCESS)
+ return 0;
+ return exif.epoch();
+}
+
+extern "C" char *move_away(const char *old_path)
+{
+ if (verbose > 1)
+ qDebug() << "move away" << old_path;
+ QDir oldDir(old_path);
+ QDir newDir;
+ QString newPath;
+ int i = 0;
+ do {
+ newPath = QString(old_path) + QString(".%1").arg(++i);
+ newDir.setPath(newPath);
+ } while(newDir.exists());
+ if (verbose > 1)
+ qDebug() << "renaming to" << newPath;
+ if (!oldDir.rename(old_path, newPath)) {
+ if (verbose)
+ qDebug() << "rename of" << old_path << "to" << newPath << "failed";
+ // 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("");
+ }
+ return strdup(qPrintable(newPath));
+}
+
+extern "C" char *get_file_name(const char *fileName)
+{
+ QFileInfo fileInfo(fileName);
+ return strdup(fileInfo.fileName().toUtf8());
+}
+
+extern "C" void copy_image_and_overwrite(const char *cfileName, const char *path, const char *cnewName)
+{
+ QString fileName(cfileName);
+ QString newName(path);
+ newName += cnewName;
+ QFile file(newName);
+ if (file.exists())
+ file.remove();
+ if (!QFile::copy(fileName, newName))
+ qDebug() << "copy of" << fileName << "to" << newName << "failed";
+}
+
+extern "C" bool string_sequence_contains(const char *string_sequence, const char *text)
+{
+ if (same_string(text, "") || same_string(string_sequence, ""))
+ return false;
+
+ QString stringSequence(string_sequence);
+ QStringList strings = stringSequence.split(",", QString::SkipEmptyParts);
+ Q_FOREACH (const QString& string, strings) {
+ if (string.trimmed().compare(QString(text).trimmed(), Qt::CaseInsensitive) == 0)
+ return true;
+ }
+ return false;
+}
+
+static bool lessThan(const QPair<QString, int> &a, const QPair<QString, int> &b)
+{
+ return a.second < b.second;
+}
+
+void selectedDivesGasUsed(QVector<QPair<QString, int> > &gasUsedOrdered)
+{
+ int i, j;
+ struct dive *d;
+ QMap<QString, int> gasUsed;
+ for_each_dive (i, d) {
+ if (!d->selected)
+ continue;
+ volume_t diveGases[MAX_CYLINDERS] = {};
+ get_gas_used(d, diveGases);
+ for (j = 0; j < MAX_CYLINDERS; j++)
+ if (diveGases[j].mliter) {
+ QString gasName = gasname(&d->cylinder[j].gasmix);
+ gasUsed[gasName] += diveGases[j].mliter;
+ }
+ }
+ Q_FOREACH(const QString& gas, gasUsed.keys()) {
+ gasUsedOrdered.append(qMakePair(gas, gasUsed[gas]));
+ }
+ 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("");
+}
+
+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;
+}
+
+void copyPath(QString src, QString dst)
+{
+ QDir dir(src);
+ if (!dir.exists())
+ return;
+ foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
+ QString dst_path = dst + QDir::separator() + d;
+ dir.mkpath(dst_path);
+ copyPath(src + QDir::separator() + d, dst_path);
+ }
+ foreach (QString f, dir.entryList(QDir::Files))
+ QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f);
+}
+
+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());
+}
+
+bool is_same_day(timestamp_t trip_when, timestamp_t dive_when)
+{
+ static timestamp_t twhen = (timestamp_t) 0;
+ static struct tm tmt;
+ struct tm tmd;
+
+ utc_mkdate(dive_when, &tmd);
+
+ if (twhen != trip_when) {
+ twhen = trip_when;
+ utc_mkdate(twhen, &tmt);
+ }
+
+ return ((tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year));
+}
+
+QString get_trip_date_string(timestamp_t when, int nr, bool getday)
+{
+ struct tm tm;
+ utc_mkdate(when, &tm);
+ QDateTime localTime = QDateTime::fromTime_t(when);
+ localTime.setTimeSpec(Qt::UTC);
+ QString ret ;
+
+ QString suffix = " " + QObject::tr("(%n dive(s))", "", nr);
+ if (getday) {
+ ret = localTime.date().toString(dateFormat) + suffix;
+ } else {
+ ret = localTime.date().toString("MMM yy") + suffix;
+ }
+ return ret;
+
+}
+
+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();
+}
+
+const QString hashfile_name()
+{
+ return QString(system_default_directory()).append("/hashes");
+}
+
+extern "C" char *hashfile_name_string()
+{
+ return strdup(hashfile_name().toUtf8().data());
+}
+
+void read_hashes()
+{
+ QFile hashfile(hashfile_name());
+ if (hashfile.open(QIODevice::ReadOnly)) {
+ QDataStream stream(&hashfile);
+ stream >> localFilenameOf;
+ stream >> hashOf;
+ hashfile.close();
+ }
+}
+
+void write_hashes()
+{
+ QSaveFile hashfile(hashfile_name());
+ if (hashfile.open(QIODevice::WriteOnly)) {
+ QDataStream stream(&hashfile);
+ stream << localFilenameOf;
+ stream << hashOf;
+ 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);
+ if (imagefile.exists() && imagefile.open(QIODevice::ReadOnly)) {
+ hash.addData(&imagefile);
+ add_hash(filename, hash.result());
+ return hash.result();
+ } else {
+ return QByteArray();
+ }
+}
+
+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 hashPicture(struct picture *picture)
+{
+ char *oldHash = copy_string(picture->hash);
+ learnHash(picture, hashFile(QString(picture->filename)));
+ if (!same_string(picture->hash, "") && !same_string(picture->hash, oldHash))
+ mark_divelist_changed((true));
+ free(oldHash);
+}
+
+extern "C" void cache_picture(struct picture *picture)
+{
+ QString filename = picture->filename;
+ if (!hashOf.contains(filename))
+ QtConcurrent::run(hashPicture, picture);
+}
+
+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" const char *local_file_path(struct picture *picture)
+{
+ QString hashString = picture->hash;
+ if (hashString.isEmpty()) {
+ QByteArray hash = hashFile(picture->filename);
+ free(picture->hash);
+ picture->hash = strdup(hash.toHex().data());
+ }
+ QString localFileName = fileFromHash(picture->hash);
+ if (localFileName.isEmpty())
+ localFileName = picture->filename;
+ return strdup(qPrintable(localFileName));
+}
+
+extern "C" bool picture_exists(struct picture *picture)
+{
+ QString localFilename = fileFromHash(picture->hash);
+ QByteArray hash = hashFile(localFilename);
+ return same_string(hash.toHex().data(), picture->hash);
+}
+
+const QString picturedir()
+{
+ return QString(system_default_directory()).append("/picturedata/");
+}
+
+extern "C" char *picturedir_string()
+{
+ return strdup(picturedir().toUtf8().data());
+}
+
+/* when we get a picture from git storage (local or remote) and can't find the picture
+ * based on its hash, we create a local copy with the hash as filename and the appropriate
+ * suffix */
+extern "C" void savePictureLocal(struct picture *picture, const char *data, int len)
+{
+ QString dirname = picturedir();
+ QDir localPictureDir(dirname);
+ localPictureDir.mkpath(dirname);
+ QString suffix(picture->filename);
+ suffix.replace(QRegularExpression(".*\\."), "");
+ QString filename(dirname + picture->hash + "." + suffix);
+ QSaveFile out(filename);
+ if (out.open(QIODevice::WriteOnly)) {
+ out.write(data, len);
+ out.commit();
+ add_hash(filename, QByteArray::fromHex(picture->hash));
+ }
+}
+
+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;
+}
+
+QString get_gas_string(struct gasmix gas)
+{
+ uint o2 = (get_o2(&gas) + 5) / 10, he = (get_he(&gas) + 5) / 10;
+ QString result = gasmix_is_air(&gas) ? QObject::tr("AIR") : he == 0 ? (o2 == 100 ? QObject::tr("OXYGEN") : QString("EAN%1").arg(o2, 2, 10, QChar('0'))) : QString("%1/%2").arg(o2).arg(he);
+ return result;
+}
+
+QString get_divepoint_gas_string(const divedatapoint &p)
+{
+ return get_gas_string(p.gasmix);
+}
+
+weight_t string_to_weight(const char *str)
+{
+ const char *end;
+ double value = strtod_flags(str, &end, 0);
+ QString rest = QString(end).trimmed();
+ QString local_kg = QObject::tr("kg");
+ QString local_lbs = QObject::tr("lbs");
+ 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:
+ weight.grams = rint(value * 1000);
+ 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();
+ QString local_ft = QObject::tr("ft");
+ QString local_m = QObject::tr("m");
+ depth_t depth;
+
+ 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:
+ depth.mm = rint(value * 1000);
+ 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();
+ QString local_psi = QObject::tr("psi");
+ QString local_bar = QObject::tr("bar");
+ 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:
+ pressure.mbar = rint(value * 1000);
+ 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();
+ QString local_l = QObject::tr("l");
+ QString local_cuft = QObject::tr("cuft");
+ 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:
+ volume.mliter = rint(value * 1000);
+ return volume;
+}
+
+fraction_t string_to_fraction(const char *str)
+{
+ const char *end;
+ double value = strtod_flags(str, &end, 0);
+ fraction_t fraction;
+
+ fraction.permille = rint(value * 10);
+ return fraction;
+}
+
+int getCloudURL(QString &filename)
+{
+ QString email = QString(prefs.cloud_storage_email);
+ email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), "");
+ if (email.isEmpty() || same_string(prefs.cloud_storage_password, ""))
+ return report_error("Please configure Cloud storage email and password in the preferences");
+ if (email != prefs.cloud_storage_email_encoded) {
+ free(prefs.cloud_storage_email_encoded);
+ prefs.cloud_storage_email_encoded = strdup(qPrintable(email));
+ }
+ filename = QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email);
+ if (verbose)
+ qDebug() << "cloud URL set as" << filename;
+ return 0;
+}
+
+extern "C" char *cloud_url()
+{
+ QString filename;
+ getCloudURL(filename);
+ return strdup(filename.toUtf8().data());
+}
+
+void loadPreferences()
+{
+ QSettings s;
+ QVariant v;
+
+ s.beginGroup("Units");
+ if (s.value("unit_system").toString() == "metric") {
+ prefs.unit_system = METRIC;
+ prefs.units = SI_units;
+ } else if (s.value("unit_system").toString() == "imperial") {
+ prefs.unit_system = IMPERIAL;
+ prefs.units = IMPERIAL_units;
+ } else {
+ prefs.unit_system = PERSONALIZE;
+ GET_UNIT("length", length, units::FEET, units::METERS);
+ GET_UNIT("pressure", pressure, units::PSI, units::BAR);
+ GET_UNIT("volume", volume, units::CUFT, units::LITER);
+ GET_UNIT("temperature", temperature, units::FAHRENHEIT, units::CELSIUS);
+ GET_UNIT("weight", weight, units::LBS, units::KG);
+ }
+ GET_UNIT("vertical_speed_time", vertical_speed_time, units::MINUTES, units::SECONDS);
+ GET_BOOL("coordinates", coordinates_traditional);
+ s.endGroup();
+ s.beginGroup("TecDetails");
+ GET_BOOL("po2graph", pp_graphs.po2);
+ GET_BOOL("pn2graph", pp_graphs.pn2);
+ GET_BOOL("phegraph", pp_graphs.phe);
+ GET_DOUBLE("po2threshold", pp_graphs.po2_threshold);
+ GET_DOUBLE("pn2threshold", pp_graphs.pn2_threshold);
+ GET_DOUBLE("phethreshold", pp_graphs.phe_threshold);
+ GET_BOOL("mod", mod);
+ GET_DOUBLE("modpO2", modpO2);
+ GET_BOOL("ead", ead);
+ GET_BOOL("redceiling", redceiling);
+ GET_BOOL("dcceiling", dcceiling);
+ GET_BOOL("calcceiling", calcceiling);
+ GET_BOOL("calcceiling3m", calcceiling3m);
+ GET_BOOL("calcndltts", calcndltts);
+ GET_BOOL("calcalltissues", calcalltissues);
+ GET_BOOL("hrgraph", hrgraph);
+ GET_BOOL("tankbar", tankbar);
+ GET_BOOL("percentagegraph", percentagegraph);
+ GET_INT("gflow", gflow);
+ GET_INT("gfhigh", gfhigh);
+ GET_BOOL("gf_low_at_maxdepth", gf_low_at_maxdepth);
+ GET_BOOL("show_ccr_setpoint",show_ccr_setpoint);
+ GET_BOOL("show_ccr_sensors",show_ccr_sensors);
+ GET_BOOL("zoomed_plot", zoomed_plot);
+ set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth);
+ GET_BOOL("show_sac", show_sac);
+ GET_BOOL("display_unused_tanks", display_unused_tanks);
+ GET_BOOL("show_average_depth", show_average_depth);
+ s.endGroup();
+
+ s.beginGroup("GeneralSettings");
+ GET_TXT("default_filename", default_filename);
+ GET_INT("default_file_behavior", default_file_behavior);
+ if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) {
+ // undefined, so check if there's a filename set and
+ // use that, otherwise go with no default file
+ if (QString(prefs.default_filename).isEmpty())
+ prefs.default_file_behavior = NO_DEFAULT_FILE;
+ else
+ prefs.default_file_behavior = LOCAL_DEFAULT_FILE;
+ }
+ GET_TXT("default_cylinder", default_cylinder);
+ GET_BOOL("use_default_file", use_default_file);
+ GET_INT("defaultsetpoint", defaultsetpoint);
+ GET_INT("o2consumption", o2consumption);
+ GET_INT("pscr_ratio", pscr_ratio);
+ s.endGroup();
+
+ s.beginGroup("Display");
+ // get the font from the settings or our defaults
+ // respect the system default font size if none is explicitly set
+ QFont defaultFont = s.value("divelist_font", prefs.divelist_font).value<QFont>();
+ if (IS_FP_SAME(system_divelist_default_font_size, -1.0)) {
+ prefs.font_size = qApp->font().pointSizeF();
+ system_divelist_default_font_size = prefs.font_size; // this way we don't save it on exit
+ }
+ prefs.font_size = s.value("font_size", prefs.font_size).toFloat();
+ // painful effort to ignore previous default fonts on Windows - ridiculous
+ QString fontName = defaultFont.toString();
+ if (fontName.contains(","))
+ fontName = fontName.left(fontName.indexOf(","));
+ if (subsurface_ignore_font(fontName.toUtf8().constData())) {
+ defaultFont = QFont(prefs.divelist_font);
+ } else {
+ free((void *)prefs.divelist_font);
+ prefs.divelist_font = strdup(fontName.toUtf8().constData());
+ }
+ defaultFont.setPointSizeF(prefs.font_size);
+ qApp->setFont(defaultFont);
+ GET_INT("displayinvalid", display_invalid_dives);
+ s.endGroup();
+
+ s.beginGroup("Animations");
+ GET_INT("animation_speed", animation_speed);
+ s.endGroup();
+
+ s.beginGroup("Network");
+ GET_INT_DEF("proxy_type", proxy_type, QNetworkProxy::DefaultProxy);
+ GET_TXT("proxy_host", proxy_host);
+ GET_INT("proxy_port", proxy_port);
+ GET_BOOL("proxy_auth", proxy_auth);
+ GET_TXT("proxy_user", proxy_user);
+ GET_TXT("proxy_pass", proxy_pass);
+ s.endGroup();
+
+ s.beginGroup("CloudStorage");
+ GET_TXT("email", cloud_storage_email);
+ GET_BOOL("save_password_local", save_password_local);
+ if (prefs.save_password_local) { // GET_TEXT macro is not a single statement
+ GET_TXT("password", cloud_storage_password);
+ }
+ GET_INT("cloud_verification_status", cloud_verification_status);
+ GET_BOOL("cloud_background_sync", cloud_background_sync);
+
+ // creating the git url here is simply a convenience when C code wants
+ // to compare against that git URL - it's always derived from the base URL
+ GET_TXT("cloud_base_url", cloud_base_url);
+ prefs.cloud_git_url = strdup(qPrintable(QString(prefs.cloud_base_url) + "/git"));
+ s.endGroup();
+
+ // Subsurface webservice id is stored outside of the groups
+ GET_TXT("subsurface_webservice_uid", userid);
+
+ // GeoManagement
+ s.beginGroup("geocoding");
+#ifdef DISABLED
+ GET_BOOL("enable_geocoding", geocoding.enable_geocoding);
+ GET_BOOL("parse_dive_without_gps", geocoding.parse_dive_without_gps);
+ GET_BOOL("tag_existing_dives", geocoding.tag_existing_dives);
+#else
+ prefs.geocoding.enable_geocoding = true;
+#endif
+ GET_ENUM("cat0", taxonomy_category, geocoding.category[0]);
+ GET_ENUM("cat1", taxonomy_category, geocoding.category[1]);
+ GET_ENUM("cat2", taxonomy_category, geocoding.category[2]);
+ s.endGroup();
+
+}
+
+extern "C" bool isCloudUrl(const char *filename)
+{
+ QString email = QString(prefs.cloud_storage_email);
+ email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), "");
+ if (!email.isEmpty() &&
+ QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email) == filename)
+ return true;
+ return false;
+}
+
+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)
+ *buffer = strdup(qPrintable(proxy));
+ return true;
+ }
+ return false;
+}
+
+extern "C" void subsurface_mkdir(const char *dir)
+{
+ QDir directory;
+ if (!directory.mkpath(QString(dir)))
+ qDebug() << "failed to create path" << dir;
+}
+
+extern "C" void parse_display_units(char *line)
+{
+ qDebug() << line;
+}
+
+static QByteArray currentApplicationState;
+
+QByteArray getCurrentAppState()
+{
+ return currentApplicationState;
+}
+
+void setCurrentAppState(QByteArray state)
+{
+ currentApplicationState = state;
+}
+
+extern "C" bool in_planner()
+{
+ return (currentApplicationState == "PlanDive" || currentApplicationState == "EditPlannedDive");
+}