Compare commits

...

7 commits

Author SHA1 Message Date
Egbert de Pauw
f5842f286f
Merge 9bd1631103 into 077f078151 2024-10-27 11:59:50 -04:00
Victor Arvidsson
077f078151 Updated user-maunal with new instructions for subtitles.
Signed-off-by: Victor Arvidsson <victarv@gmail.com>
2024-10-27 01:37:04 +13:00
Victor Arvidsson
e58824e636 Fixed small bug in replace_all.
Start_pos should of course be 0...

Signed-off-by: Victor Arvidsson <victarv@gmail.com>
2024-10-27 01:37:04 +13:00
Victor Arvidsson
6886c9ccf8 Make the "Save dive data as subtitles" feature more configurable.
Previously, the subtitle generation was hardcoded, making it unsuitable
if you didn't want all of the displayed values. This has been replaced
by a format string that is configurable in the settings, using predefined
tags that are replaced with the values. The default value for this has
been set to (mostly) match the currently generated subtitle string. This
also provides a good starting point for users that want to modify the string.

Signed-off-by: Victor Arvidsson <victarv@gmail.com>
2024-10-27 01:37:04 +13:00
Egbert de Pauw
9bd1631103
Merge branch 'subsurface:master' into qt6devel 2024-08-28 15:40:29 +02:00
Egbertdepauw
7201a40c5f Removed some testing code.
There was an unneeded line in the code, just for my own testing
purposes.

Signed-off-by: Egbertdepauw <egbert@despaankamer.nl>
2024-08-27 14:51:39 +02:00
Egbertdepauw
c5e5535f51 qt6: Make subsurface buildable with Qt6 on Macos
Building on macos with the Qt6 framework.

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

Tested versions: 6.5.2, 6.6.3, 6.7.2

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

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

Signed-off-by: Egbertdepauw <egbert@despaankamer.nl>
2024-08-27 13:51:37 +02:00
12 changed files with 643 additions and 49 deletions

View file

@ -1,3 +1,4 @@
desktop: update "Save dive data as subtitles" feature to make it more configurable.
bluetooth: fix crash on MacOS when doing first download from a new BLE device bluetooth: fix crash on MacOS when doing first download from a new BLE device
statistics: show proper dates in January statistics: show proper dates in January
desktop: add country to the fields indexed for full text search desktop: add country to the fields indexed for full text search

View file

@ -1816,15 +1816,52 @@ from the _Media_ tab as well as the dive profile.
By right-clicking on a video and selecting the "Save dive data as subtitles" option, a subtitles By right-clicking on a video and selecting the "Save dive data as subtitles" option, a subtitles
file with the same name as the video but with an ".ass" extension is created that contains file with the same name as the video but with an ".ass" extension is created that contains
time dependent dive data (runtime, depth, temperature, NDL, TTS, surface GF) to be overlayed time dependent dive data to be overlayed with the video. The format of the subtitle is specified
with the video. The VLC video player automatically finds this file upon playing the video in the _Media preferences_ (_File->Preferences->Media_). The tags used in the format string are
and overlays the dive data. Alternatively, the ffmpeg video encoder can be used to create a replaced with the actual values, if present, or removed if not present in the data.
The VLC video player automatically finds this file upon playing
the video and overlays the dive data. Alternatively, the ffmpeg video encoder can be used to create a
new video file with the dive data encoded in the video stream. To do so run new video file with the dive data encoded in the video stream. To do so run
ffmpeg -v video.mp4 -vf "ass=video.ass" video_with_data.mp4 ffmpeg -v video.mp4 -vf "ass=video.ass" video_with_data.mp4
from the command line. You need to have the libass library installed. from the command line. You need to have the libass library installed.
Available tags are:
* Dive time: [time]
* Depth: [depth]
* Temperature: [temperature]
* Ceiling: [ceiling]
* NDL: [ndl]
* TTS: [tts]
* RBT: [rbt]
* Stop time: [stoptime]
* Stop depth: [stopdepth]
* CNS: [cns]
* SAC: [sac]
* pO₂: [p_o2]
* pN₂: [p_n2]
* pHe: [p_he]
* O₂ pressure (rebreather): [o2_pressure]
* O₂ setpoint: [o2_setpoint]
* SCR ΔpO₂: [scr_oc_po2]
* MOD: [mod]
* EAD: [ead]
* END: [end]
* EADD: [eadd]
* Vertical speed: [speed]
* In deco (calculated): [in_deco]
* NDL (calculated): [ndl_calc]
* TTS (calculated): [tts_calc]
* Stop time (calculated): [stoptime_calc]
* Stop depth (calculated): [stopdepth_calc]
* Heartrate: [heartrate]
* Bearing: [bearing]
* Surface GF: [surface_gf]
* Current GF: [current_gf]
* Density: [density]
* ICD Warning: [icd_warning]
==== Media on an external hard disk ==== Media on an external hard disk
Most underwater photographers store media on an external drive. If such a drive can be mapped by the operating system Most underwater photographers store media on an external drive. If such a drive can be mapped by the operating system
(almost always the case) the media can be directly accessed by _Subsurface_. This eases the interaction (almost always the case) the media can be directly accessed by _Subsurface_. This eases the interaction

View file

@ -113,6 +113,7 @@ struct preferences {
bool extract_video_thumbnails; bool extract_video_thumbnails;
int extract_video_thumbnails_position; // position in stream: 0=first 100=last second int extract_video_thumbnails_position; // position in stream: 0=first 100=last second
std::string ffmpeg_executable; // path of ffmpeg binary std::string ffmpeg_executable; // path of ffmpeg binary
std::string subtitles_format_string; // Format string for subtitles generated from the dive data
int defaultsetpoint; // default setpoint in mbar int defaultsetpoint; // default setpoint in mbar
std::string default_filename; std::string default_filename;
enum def_file_behavior default_file_behavior; enum def_file_behavior default_file_behavior;

View file

@ -6,7 +6,9 @@
#include "core/errorhelper.h" #include "core/errorhelper.h"
#include "core/file.h" #include "core/file.h"
#include "core/format.h" #include "core/format.h"
#include "core/gettext.h"
#include "core/membuffer.h" #include "core/membuffer.h"
#include "core/pref.h"
#include "core/subsurface-string.h" #include "core/subsurface-string.h"
#include "core/version.h" #include "core/version.h"
#include <errno.h> #include <errno.h>
@ -45,6 +47,16 @@ static std::string video_time(int secs)
return format_string_std("%d:%02d:%02d.000,", hours, mins, secs); return format_string_std("%d:%02d:%02d.000,", hours, mins, secs);
} }
static void replace_all(std::string &str, const std::string &old_value, const std::string &new_value) {
if (old_value.empty())
return;
size_t start_pos = 0;
while ((start_pos = str.find(old_value, start_pos)) != std::string::npos) {
str.replace(start_pos, old_value.length(), new_value);
start_pos += new_value.length(); // In case 'new_value' contains 'old_value', like replacing 'x' with 'yx'
}
}
static void put_pd(struct membuffer *b, const struct plot_info &pi, int idx) static void put_pd(struct membuffer *b, const struct plot_info &pi, int idx)
{ {
const struct plot_data &entry = pi.entry[idx]; const struct plot_data &entry = pi.entry[idx];
@ -175,32 +187,214 @@ static std::string format_st_event(const plot_data &entry, const plot_data &next
double value; double value;
int decimals; int decimals;
const char *unit; const char *unit;
if (next_entry.sec < offset || entry.sec > offset + length)
if (entry.sec < offset || entry.sec > offset + length)
return {}; return {};
std::string res = "Dialogue: 0,"; std::string res = "Dialogue: 0,";
res += video_time(entry.sec - offset); res += video_time(std::max(entry.sec - offset, 0));
res += video_time(next_entry.sec - offset < length ? next_entry.sec - offset : length); res += video_time(std::min(next_entry.sec - offset, length));
res += "Default,,0,0,0,,"; res += "Default,,0,0,0,,";
res += format_string_std("%d:%02d ", FRACTION_TUPLE(entry.sec, 60));
std::string format_string = prefs.subtitles_format_string;
replace_all(format_string, "[time]", format_string_std("%d:%02d", FRACTION_TUPLE(entry.sec, 60)));
value = get_depth_units(entry.depth, &decimals, &unit); value = get_depth_units(entry.depth, &decimals, &unit);
res += format_string_std("D=%02.2f %s ", value, unit); replace_all(format_string, "[depth]", format_string_std("%02.2f %s", value, unit));
if (entry.temperature) { if (entry.temperature) {
value = get_temp_units(entry.temperature, &unit); value = get_temp_units(entry.temperature, &unit);
res += format_string_std("T=%.1f%s ", value, unit); replace_all(format_string,"[temperature]", format_string_std("%.1f%s", value, unit));
} else {
replace_all(format_string, "[temperature]", "");
} }
// Only show NDL if it is not essentially infinite, show TTS for mandatory stops.
if (entry.ndl_calc < 3600) { if (entry.ceiling) {
if (entry.ndl_calc > 0) value = get_depth_units(entry.ceiling, &decimals, &unit);
res += format_string_std("NDL=%d:%02d ", FRACTION_TUPLE(entry.ndl_calc, 60)); replace_all(format_string,"[ceiling]", format_string_std("%02.2f %s", value, unit));
else } else {
if (entry.tts_calc > 0) replace_all(format_string, "[ceiling]", "");
res += format_string_std("TTS=%d:%02d ", FRACTION_TUPLE(entry.tts_calc, 60));
} }
if (entry.ndl > 0) {
if (entry.ndl < 7200) {
replace_all(format_string,"[ndl]", format_string_std("%d:%02d", FRACTION_TUPLE(entry.ndl, 60)));
} else {
replace_all(format_string,"[ndl]", ">2h");
}
} else {
replace_all(format_string, "[ndl]", "");
}
if (entry.tts > 0) {
replace_all(format_string,"[tts]", format_string_std("%d:%02d", FRACTION_TUPLE(entry.tts, 60)));
} else {
replace_all(format_string, "[tts]", "");
}
if (entry.rbt > 0) {
replace_all(format_string,"[rbt]", format_string_std("%d:%02d", FRACTION_TUPLE(entry.rbt, 60)));
} else {
replace_all(format_string, "[rbt]", "");
}
if (entry.stoptime > 0) {
replace_all(format_string,"[stoptime]", format_string_std("%d:%02d", FRACTION_TUPLE(entry.stoptime, 60)));
} else {
replace_all(format_string, "[stoptime]", "");
}
if (entry.stopdepth > 0) {
value = get_depth_units(entry.stopdepth, &decimals, &unit);
replace_all(format_string, "[stopdepth]", format_string_std("%02.2f %s", value, unit));
} else {
replace_all(format_string, "[stopdepth]", "");
}
replace_all(format_string, "[cns]", format_string_std("%u%%", entry.cns));
if (entry.sac > 0) {
value = get_volume_units(entry.sac, &decimals, &unit);
replace_all(format_string, "[sac]", format_string_std("%02.2f %s", value, unit));
} else {
replace_all(format_string, "[sac]", "");
}
if (entry.pressures.o2 > 0) {
replace_all(format_string, "[p_o2]", format_string_std("%.2fbar", entry.pressures.o2));
} else {
replace_all(format_string, "[p_o2]", "");
}
if (entry.pressures.n2 > 0) {
replace_all(format_string, "[p_n2]", format_string_std("%.2fbar", entry.pressures.n2));
} else {
replace_all(format_string, "[p_n2]", "");
}
if (entry.pressures.he > 0) {
replace_all(format_string, "[p_he]", format_string_std("%.2fbar", entry.pressures.he));
} else {
replace_all(format_string, "[p_he]", "");
}
if (entry.o2pressure.mbar > 0) {
replace_all(format_string, "[o2_pressure]", format_string_std("%.2fbar", entry.o2pressure.mbar/1000.0));
} else {
replace_all(format_string, "[o2_pressure]", "");
}
if (entry.o2setpoint.mbar > 0) {
replace_all(format_string, "[o2_setpoint]", format_string_std("%.2fbar", entry.o2setpoint.mbar/1000.0));
} else {
replace_all(format_string, "[o2_setpoint]", "");
}
if (entry.scr_OC_pO2.mbar > 0 && entry.pressures.o2 > 0) {
replace_all(format_string, "[scr_oc_po2]", format_string_std("%.2fbar", entry.scr_OC_pO2.mbar/1000.0 - entry.pressures.o2));
} else {
replace_all(format_string, "[scr_oc_po2]", "");
}
if (entry.mod > 0) {
value = get_depth_units(entry.mod, &decimals, &unit);
replace_all(format_string, "[mod]", format_string_std("%02.2f %s", value, unit));
} else {
replace_all(format_string, "[mod]", "");
}
if (entry.ead > 0) {
value = get_depth_units(entry.ead, &decimals, &unit);
replace_all(format_string, "[ead]", format_string_std("%02.2f %s", value, unit));
} else {
replace_all(format_string, "[ead]", "");
}
if (entry.end > 0) {
value = get_depth_units(entry.end, &decimals, &unit);
replace_all(format_string, "[end]", format_string_std("%02.2f %s", value, unit));
} else {
replace_all(format_string, "[end]", "");
}
if (entry.eadd > 0) {
value = get_depth_units(entry.eadd, &decimals, &unit);
replace_all(format_string, "[eadd]", format_string_std("%02.2f %s", value, unit));
} else {
replace_all(format_string, "[eadd]", "");
}
value = get_vertical_speed_units(entry.speed, &decimals, &unit);
if (entry.speed > 0)
/* Ascending speeds are positive, descending are negative */
value *= -1;
replace_all(format_string, "[speed]", format_string_std("%02.2f %s", value, unit));
if (entry.in_deco) {
replace_all(format_string, "[in_deco]", translate("gettextFromC", "In deco"));
} else {
replace_all(format_string, "[in_deco]", "");
}
if (entry.ndl_calc > 0) {
if (entry.ndl_calc < 7200) {
replace_all(format_string,"[ndl_calc]", format_string_std("%d:%02d", FRACTION_TUPLE(entry.ndl_calc, 60)));
} else {
replace_all(format_string,"[ndl_calc]", ">2h");
}
} else {
replace_all(format_string, "[ndl_calc]", "");
}
if (entry.tts_calc > 0) {
replace_all(format_string,"[tts_calc]", format_string_std("%d:%02d", FRACTION_TUPLE(entry.tts_calc, 60)));
} else {
replace_all(format_string, "[tts_calc]", "");
}
if (entry.stoptime_calc > 0) {
replace_all(format_string,"[stoptime_calc]", format_string_std("%d:%02d", FRACTION_TUPLE(entry.stoptime_calc, 60)));
} else {
replace_all(format_string, "[stoptime_calc]", "");
}
if (entry.stopdepth_calc > 0) {
value = get_depth_units(entry.stopdepth_calc, &decimals, &unit);
replace_all(format_string, "[stopdepth_calc]", format_string_std("%02.2f %s", value, unit));
} else {
replace_all(format_string, "[stopdepth_calc]", "");
}
if (entry.heartbeat > 0) {
replace_all(format_string, "[heartrate]", format_string_std("%d", entry.heartbeat));
} else {
replace_all(format_string, "[heartrate]", "");
}
if (entry.surface_gf > 0.0) { if (entry.surface_gf > 0.0) {
res += format_string_std("sGF=%.1f%% ", entry.surface_gf); replace_all(format_string, "[surface_gf]", format_string_std("%.1f%%", entry.surface_gf));
} else {
replace_all(format_string, "[surface_gf]", "");
} }
if (entry.current_gf > 0.0) {
replace_all(format_string, "[current_gf]", format_string_std("%.1f%%", entry.current_gf));
} else {
replace_all(format_string, "[current_gf]", "");
}
if (entry.density > 0) {
replace_all(format_string, "[density]", format_string_std("%.1fg/", entry.density));
} else {
replace_all(format_string, "[density]", "");
}
if (entry.icd_warning) {
replace_all(format_string, "[icd_warning]", translate("gettextFromC", "ICD in leading tissue"));
} else {
replace_all(format_string, "[icd_warning]", "");
}
res += format_string;
res += "\n"; res += "\n";
return res; return res;
} }

View file

@ -15,6 +15,7 @@ void qPrefMedia::loadSync(bool doSync)
disk_extract_video_thumbnails(doSync); disk_extract_video_thumbnails(doSync);
disk_extract_video_thumbnails_position(doSync); disk_extract_video_thumbnails_position(doSync);
disk_ffmpeg_executable(doSync); disk_ffmpeg_executable(doSync);
disk_subtitles_format_string(doSync);
disk_auto_recalculate_thumbnails(doSync); disk_auto_recalculate_thumbnails(doSync);
disk_auto_recalculate_thumbnails(doSync); disk_auto_recalculate_thumbnails(doSync);
} }
@ -23,3 +24,4 @@ HANDLE_PREFERENCE_BOOL(Media, "auto_recalculate_thumbnails", auto_recalculate_th
HANDLE_PREFERENCE_BOOL(Media, "extract_video_thumbnails", extract_video_thumbnails); HANDLE_PREFERENCE_BOOL(Media, "extract_video_thumbnails", extract_video_thumbnails);
HANDLE_PREFERENCE_INT(Media, "extract_video_thumbnails_position", extract_video_thumbnails_position); HANDLE_PREFERENCE_INT(Media, "extract_video_thumbnails_position", extract_video_thumbnails_position);
HANDLE_PREFERENCE_TXT(Media, "ffmpeg_executable", ffmpeg_executable); HANDLE_PREFERENCE_TXT(Media, "ffmpeg_executable", ffmpeg_executable);
HANDLE_PREFERENCE_TXT(Media, "subtitles_format_string", subtitles_format_string);

View file

@ -11,6 +11,7 @@ class qPrefMedia : public QObject {
Q_PROPERTY(bool extract_video_thumbnails READ extract_video_thumbnails WRITE set_extract_video_thumbnails NOTIFY extract_video_thumbnailsChanged) Q_PROPERTY(bool extract_video_thumbnails READ extract_video_thumbnails WRITE set_extract_video_thumbnails NOTIFY extract_video_thumbnailsChanged)
Q_PROPERTY(int extract_video_thumbnails_position READ extract_video_thumbnails_position WRITE set_extract_video_thumbnails_position NOTIFY extract_video_thumbnails_positionChanged) Q_PROPERTY(int extract_video_thumbnails_position READ extract_video_thumbnails_position WRITE set_extract_video_thumbnails_position NOTIFY extract_video_thumbnails_positionChanged)
Q_PROPERTY(QString ffmpeg_executable READ ffmpeg_executable WRITE set_ffmpeg_executable NOTIFY ffmpeg_executableChanged) Q_PROPERTY(QString ffmpeg_executable READ ffmpeg_executable WRITE set_ffmpeg_executable NOTIFY ffmpeg_executableChanged)
Q_PROPERTY(QString subtitles_format_string READ subtitles_format_string WRITE set_subtitles_format_string NOTIFY subtitles_format_stringChanged)
public: public:
static qPrefMedia *instance(); static qPrefMedia *instance();
@ -25,18 +26,21 @@ public:
static bool extract_video_thumbnails() { return prefs.extract_video_thumbnails; } static bool extract_video_thumbnails() { return prefs.extract_video_thumbnails; }
static int extract_video_thumbnails_position() { return prefs.extract_video_thumbnails_position; } static int extract_video_thumbnails_position() { return prefs.extract_video_thumbnails_position; }
static QString ffmpeg_executable() { return QString::fromStdString(prefs.ffmpeg_executable); } static QString ffmpeg_executable() { return QString::fromStdString(prefs.ffmpeg_executable); }
static QString subtitles_format_string() { return QString::fromStdString(prefs.subtitles_format_string); }
public slots: public slots:
static void set_auto_recalculate_thumbnails(bool value); static void set_auto_recalculate_thumbnails(bool value);
static void set_extract_video_thumbnails(bool value); static void set_extract_video_thumbnails(bool value);
static void set_extract_video_thumbnails_position(int value); static void set_extract_video_thumbnails_position(int value);
static void set_ffmpeg_executable(const QString& value); static void set_ffmpeg_executable(const QString& value);
static void set_subtitles_format_string(const QString& value);
signals: signals:
void auto_recalculate_thumbnailsChanged(bool value); void auto_recalculate_thumbnailsChanged(bool value);
void extract_video_thumbnailsChanged(bool value); void extract_video_thumbnailsChanged(bool value);
void extract_video_thumbnails_positionChanged(int value); void extract_video_thumbnails_positionChanged(int value);
void ffmpeg_executableChanged(const QString& value); void ffmpeg_executableChanged(const QString& value);
void subtitles_format_stringChanged(const QString& value);
private: private:
qPrefMedia() {} qPrefMedia() {}
@ -45,6 +49,7 @@ private:
static void disk_extract_video_thumbnails(bool doSync); static void disk_extract_video_thumbnails(bool doSync);
static void disk_extract_video_thumbnails_position(bool doSync); static void disk_extract_video_thumbnails_position(bool doSync);
static void disk_ffmpeg_executable(bool doSync); static void disk_ffmpeg_executable(bool doSync);
static void disk_subtitles_format_string(bool doSync);
}; };

View file

@ -197,6 +197,7 @@ void setup_system_prefs()
default_prefs.divelist_font = system_divelist_default_font; default_prefs.divelist_font = system_divelist_default_font;
default_prefs.font_size = system_divelist_default_font_size; default_prefs.font_size = system_divelist_default_font_size;
default_prefs.ffmpeg_executable = "ffmpeg"; default_prefs.ffmpeg_executable = "ffmpeg";
default_prefs.subtitles_format_string = "[time] D=[depth] T=[temperature] sGF=[surface_gf]";
#if !defined(SUBSURFACE_MOBILE) #if !defined(SUBSURFACE_MOBILE)
default_prefs.default_filename = system_default_filename(); default_prefs.default_filename = system_default_filename();

View file

@ -71,6 +71,7 @@ void PreferencesMedia::refreshSettings()
ui->extractVideoThumbnails->setChecked(qPrefMedia::extract_video_thumbnails()); ui->extractVideoThumbnails->setChecked(qPrefMedia::extract_video_thumbnails());
ui->videoThumbnailPosition->setValue(qPrefMedia::extract_video_thumbnails_position()); ui->videoThumbnailPosition->setValue(qPrefMedia::extract_video_thumbnails_position());
ui->ffmpegExecutable->setText(qPrefMedia::ffmpeg_executable()); ui->ffmpegExecutable->setText(qPrefMedia::ffmpeg_executable());
ui->subtitlesFormatString->setText(qPrefMedia::subtitles_format_string());
ui->auto_recalculate_thumbnails->setChecked(prefs.auto_recalculate_thumbnails); ui->auto_recalculate_thumbnails->setChecked(prefs.auto_recalculate_thumbnails);
} }
@ -81,5 +82,6 @@ void PreferencesMedia::syncSettings()
media->set_extract_video_thumbnails(ui->extractVideoThumbnails->isChecked()); media->set_extract_video_thumbnails(ui->extractVideoThumbnails->isChecked());
media->set_extract_video_thumbnails_position(ui->videoThumbnailPosition->value()); media->set_extract_video_thumbnails_position(ui->videoThumbnailPosition->value());
media->set_ffmpeg_executable(ui->ffmpegExecutable->text()); media->set_ffmpeg_executable(ui->ffmpegExecutable->text());
media->set_subtitles_format_string(ui->subtitlesFormatString->text());
qPrefMedia::set_auto_recalculate_thumbnails(ui->auto_recalculate_thumbnails->isChecked()); qPrefMedia::set_auto_recalculate_thumbnails(ui->auto_recalculate_thumbnails->isChecked());
} }

View file

@ -170,6 +170,319 @@
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Dive data as subtitles</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="horizontalSpacing">
<number>5</number>
</property>
<property name="verticalSpacing">
<number>5</number>
</property>
<property name="margin">
<number>5</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="subtitlesFormatStringLabel">
<property name="text">
<string>Subtitle format string:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="subtitlesFormatString"/>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_help_video_4">
<property name="toolTip">
<string extracomment="Help info 4"/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="text">
<string>Available tags:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="subtitlesTimeLabel">
<property name="text">
<string>Dive time: [time]</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="subtitlesDepthLabel">
<property name="text">
<string>Depth: [depth]</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="subtitlesTemperatureLabel">
<property name="text">
<string>Temperature: [temperature]</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="subtitlesCeilingLabel">
<property name="text">
<string>Ceiling: [ceiling]</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="subtitlesNdlLabel">
<property name="text">
<string>NDL: [ndl]</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="subtitlesTtsLabel">
<property name="text">
<string>TTS: [tts]</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="subtitlesRbtLabel">
<property name="text">
<string>RBT: [rbt]</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="subtitlesStoptimeLabel">
<property name="text">
<string>Stop time: [stoptime]</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="subtitlesStopdepthLabel">
<property name="text">
<string>Stop depth: [stopdepth]</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="subtitlesCnsLabel">
<property name="text">
<string>CNS: [cns]</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="subtitlesSacLabel">
<property name="text">
<string>SAC: [sac]</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="subtitlesPO2Label">
<property name="text">
<string>pO₂: [p_o2]</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="subtitlesPN2Label">
<property name="text">
<string>pN₂: [p_n2]</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="subtitlesPHeLabel">
<property name="text">
<string>pHe: [p_he]</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="subtitlesO2pressureLabel">
<property name="text">
<string>O₂ pressure (rebreather): [o2_pressure]</string>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="subtitlesO2setpointLabel">
<property name="text">
<string>O₂ setpoint: [o2_setpoint]</string>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QLabel" name="subtitlesScr_oc_p02Label">
<property name="text">
<string>SCR ΔpO₂: [scr_oc_po2]</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="subtitlesModLabel">
<property name="text">
<string>MOD: [mod]</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="subtitlesEadLabel">
<property name="text">
<string>EAD: [ead]</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="subtitlesEndLabel">
<property name="text">
<string>END: [end]</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="subtitlesEaddLabel">
<property name="text">
<string>EADD: [eadd]</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="subtitlesSpeedLabel">
<property name="text">
<string>Vertical speed: [speed]</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="subtitlesIn_deco_calcLabel">
<property name="text">
<string>In deco (calculated): [in_deco]</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="subtitlesNdlCalcLabel">
<property name="text">
<string>NDL (calculated): [ndl_calc]</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="subtitlesTts_calcLabel">
<property name="text">
<string>TTS (calculated): [tts_calc]</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="subtitlesStoptime_calcLabel">
<property name="text">
<string>Stop time (calculated): [stoptime_calc]</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="subtitlesStopdepth_calcLabel">
<property name="text">
<string>Stop depth (calculated): [stopdepth_calc]</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QLabel" name="subtitlesHeartrateLabel">
<property name="text">
<string>Heartrate: [heartrate]</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QLabel" name="subtitlesBearingLabel">
<property name="text">
<string>Bearing: [bearing]</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QLabel" name="subtitlesSurface_gfLabel">
<property name="text">
<string>Surface GF: [surface_gf]</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QLabel" name="subtitlesCurrent_gfLabel">
<property name="text">
<string>Current GF: [current_gf]</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QLabel" name="subtitlesDensityLabel">
<property name="text">
<string>Density: [density]</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QLabel" name="subtitlesIcd_warningLabel">
<property name="text">
<string>ICD Warning: [icd_warning]</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="5" column="0"> <item row="5" column="0">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
@ -186,6 +499,7 @@
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>
</connections> </connections>

View file

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

View file

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

View file

@ -169,8 +169,9 @@ for package in "${PACKAGES[@]}" ; do
git_checkout_library breeze-icons $CURRENT_BREEZE_ICONS https://github.com/kde/breeze-icons.git git_checkout_library breeze-icons $CURRENT_BREEZE_ICONS https://github.com/kde/breeze-icons.git
;; ;;
googlemaps) googlemaps)
git_checkout_library googlemaps master https://github.com/Subsurface/googlemaps.git #git_checkout_library googlemaps master https://github.com/Subsurface/googlemaps.git
;; git_checkout_library googlemaps master https://github.com/vladest/googlemaps.git
;;
hidapi) hidapi)
git_checkout_library hidapi master https://github.com/libusb/hidapi.git git_checkout_library hidapi master https://github.com/libusb/hidapi.git
;; ;;