Compare commits

..

1 Commits

Author SHA1 Message Date
Berthold Stoeger
b5d216a870 import: fix memory leak when importing dives
A long standing issue: the dives_to_add, etc. tables need to be
manually freed. This kind of problem wouldn't arise with proper
C++ data structures.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-05-26 16:36:53 +02:00
63 changed files with 509 additions and 529 deletions

View File

@ -26,7 +26,11 @@ jobs:
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y -q \
software-properties-common
software-properties-common \
apt-transport-https \
curl
curl https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add -
add-apt-repository -y ppa:savoury1/qt-5-15
add-apt-repository -y ppa:savoury1/kde-5-80
@ -52,8 +56,7 @@ jobs:
qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \
qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \
qtpositioning5-dev qtscript5-dev qttools5-dev qttools5-dev-tools \
qtquickcontrols2-5-dev xvfb libbluetooth-dev libmtp-dev liblzma-dev \
curl
qtquickcontrols2-5-dev xvfb libbluetooth-dev libmtp-dev liblzma-dev
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 \
--slave /usr/bin/g++ g++ /usr/bin/g++-9

View File

@ -23,11 +23,13 @@ logger.setLevel(logging.INFO)
APPLICATION = "subsurface-ci"
LAUNCHPAD = "production"
RELEASE = "bionic"
TEAM = "subsurface"
SOURCE_NAME = "subsurface"
SNAPS = {
"subsurface": {
"stable": {"recipe": "subsurface-stable"},
"candidate": {"recipe": "subsurface-candidate"},
},
}

View File

@ -135,7 +135,7 @@ msgid ""
"mailto:subsurface@subsurface-divelog.org[our mailing list] and report bugs "
"at https://github.com/Subsurface/subsurface/issues[our bugtracker]. "
"For instructions on how to build the software and (if needed) its "
"dependencies please consult the INSTALL.md file included with the source code."
"dependencies please consult the INSTALL file included with the source code."
msgstr ""
#. type: Plain text

View File

@ -175,7 +175,7 @@ msgid ""
"an email to mailto:subsurface@subsurface-divelog.org[our mailing list] and "
"report bugs at https://github.com/Subsurface-divelog/subsurface/issues[our "
"bugtracker]. For instructions on how to build the software and (if needed) "
"its dependencies please consult the INSTALL.md file included with the source "
"its dependencies please consult the INSTALL file included with the source "
"code."
msgstr ""
"Ce manuel explique comment utiliser le programme _Subsurface_. Pour "
@ -184,7 +184,7 @@ msgstr ""
"pouvez envoyer un e-mail sur mailto:subsurface@subsurface-divelog.org[notre "
"liste de diffusion] et rapportez les bogues sur http://trac.hohndel."
"org[notre bugtracker]. Pour des instructions de compilation du logiciel et "
"(si besoin) de ses dépendances, merci de consulter le fichier INSTALL.md inclus "
"(si besoin) de ses dépendances, merci de consulter le fichier INSTALL inclus "
"dans les sources logicielles."
#. type: Plain text

View File

@ -460,7 +460,7 @@ the software, consult the <em>Downloads</em> page on the
Please discuss issues with this program by sending an email to
<a href="mailto:subsurface@subsurface-divelog.org">our mailing list</a> and report bugs at
<a href="https://github.com/Subsurface/subsurface/issues">our bugtracker</a>. For instructions on how to build the
software and (if needed) its dependencies please consult the INSTALL.md file
software and (if needed) its dependencies please consult the INSTALL file
included with the source code.</p></div>
<div class="paragraph"><p><strong>Audience</strong>: Recreational Scuba Divers, Free Divers, Tec Divers, Professional
Divers</p></div>

View File

@ -34,7 +34,7 @@ https://subsurface-divelog.org/[_Subsurface_ web site].
Please discuss issues with this program by sending an email to
mailto:subsurface@subsurface-divelog.org[our mailing list] and report bugs at
https://github.com/Subsurface/subsurface/issues[our bugtracker]. For instructions on how to build the
software and (if needed) its dependencies please consult the INSTALL.md file
software and (if needed) its dependencies please consult the INSTALL file
included with the source code.
*Audience*: Recreational Scuba Divers, Free Divers, Tec Divers, Professional

View File

@ -517,7 +517,7 @@ web</a>. Por favor, comenta los problemas que tengas con este programa enviando
mail a <a href="mailto:subsurface@subsurface-divelog.org">nuestra lista de correo</a> e informa de
fallos en <a href="https://github.com/Subsurface/subsurface/issues">nuestro bugtracker</a>.
Para instrucciones acerca de como compilar el software y (en caso necesario)
sus dependencias, por favor, consulta el archivo INSTALL.md incluido con el código
sus dependencias, por favor, consulta el archivo INSTALL incluido con el código
fuente.</p></div>
<div class="paragraph"><p><strong>Audiencia</strong>: Buceadores recreativos, Buceadores en apnea, Buceadores técnicos,
Buceadores profesionales.</p></div>

View File

@ -61,7 +61,7 @@ web]. Por favor, comenta los problemas que tengas con este programa enviando un
mail a mailto:subsurface@subsurface-divelog.org[nuestra lista de correo] e informa de
fallos en https://github.com/Subsurface/subsurface/issues[nuestro bugtracker].
Para instrucciones acerca de como compilar el software y (en caso necesario)
sus dependencias, por favor, consulta el archivo INSTALL.md incluido con el código
sus dependencias, por favor, consulta el archivo INSTALL incluido con el código
fuente.
*Audiencia*: Buceadores recreativos, Buceadores en apnea, Buceadores técnicos,

View File

@ -526,7 +526,7 @@ problème, vous pouvez envoyer un e-mail sur
<a href="mailto:subsurface@subsurface-divelog.org">notre liste de diffusion</a> et
rapportez les bogues sur <a href="http://trac.hohndel.org">notre bugtracker</a>. Pour
des instructions de compilation du logiciel et (si besoin) de ses
dépendances, merci de consulter le fichier INSTALL.md inclus dans les sources
dépendances, merci de consulter le fichier INSTALL inclus dans les sources
logicielles.</p></div>
<div class="paragraph"><p><strong>Public</strong> : Plongeurs loisirs, apnéistes, plongeurs Tek et plongeurs
professionnels</p></div>

View File

@ -61,7 +61,7 @@ problème, vous pouvez envoyer un e-mail sur
mailto:subsurface@subsurface-divelog.org[notre liste de diffusion] et
rapportez les bogues sur http://trac.hohndel.org[notre bugtracker]. Pour
des instructions de compilation du logiciel et (si besoin) de ses
dépendances, merci de consulter le fichier INSTALL.md inclus dans les sources
dépendances, merci de consulter le fichier INSTALL inclus dans les sources
logicielles.
*Public* : Plongeurs loisirs, apnéistes, plongeurs Tek et plongeurs

View File

@ -516,7 +516,7 @@ het programma kunnen bij de ontwikkelaars gemeld worden via email op
<a href="mailto:subsurface@subsurface-divelog.org">onze mailinglijst</a>. Fouten kunnen
ook gemeld worden op <a href="https://github.com/Subsurface/subsurface/issues">onze bugtracker</a>.
Instructies hoe <em>Subsurface</em> zelf te compileren vanuit de broncode staan ook op
onze website en in het INSTALL.md bestand in de broncode.</p></div>
onze website en in het INSTALL bestand in de broncode.</p></div>
<div class="paragraph"><p><strong>Doelgroep</strong>: Recreatieve duikers, Tec duikers, Apneu duikers,
Professionele duikers.</p></div>
<div id="toc">

View File

@ -59,7 +59,7 @@ het programma kunnen bij de ontwikkelaars gemeld worden via email op
mailto:subsurface@subsurface-divelog.org[onze mailinglijst]. Fouten kunnen
ook gemeld worden op https://github.com/Subsurface/subsurface/issues[onze bugtracker].
Instructies hoe _Subsurface_ zelf te compileren vanuit de broncode staan ook op
onze website en in het INSTALL.md bestand in de broncode.
onze website en in het INSTALL bestand in de broncode.
*Doelgroep*: Recreatieve duikers, Tec duikers, Apneu duikers,
Professionele duikers.

View File

@ -1,4 +1,5 @@
# Building Subsurface from Source
Building Subsurface from Source
===============================
Subsurface uses quite a few open source libraries and frameworks to do its
job. The most important ones include libdivecomputer, Qt, libxml2, libxslt,
@ -12,27 +13,23 @@ Below are instructions for building Subsurface
- iOS (cross-building)
## Getting Subsurface source
Getting Subsurface source
-------------------------
You can get the sources to the latest development version from our git
repository:
```
git clone http://github.com/Subsurface/subsurface.git
cd subsurface
git submodule init # this will give you our flavor of libdivecomputer
```
git clone http://github.com/Subsurface/subsurface.git
cd subsurface
git submodule init # this will give you our flavor of libdivecomputer
You keep it updated by doing:
```
git checkout master
git pull -r
git submodule update
```
git checkout master
git pull -r
git submodule update
### Our flavor of libdivecomputer
Our flavor of libdivecomputer
-----------------------------
Subsurface requires its own flavor of libdivecomputer which is inclduded
above as git submodule
@ -40,7 +37,7 @@ above as git submodule
The branches won't have a pretty history and will include ugly merges,
but they should always allow a fast forward pull that tracks what we
believe developers should build against. All our patches are contained
in the `Subsurface-DS9` branch.
in the "Subsurface-DS9" branch.
This should allow distros to see which patches we have applied on top of
upstream. They will receive force pushes as we rebase to newer versions of
@ -56,7 +53,8 @@ Subsurface or trying to understand what we have done relative to their
respective upstreams.
### Getting Qt5
Getting Qt5
-----------
We use Qt5 in order to only maintain one UI across platforms.
@ -76,41 +74,36 @@ significantly reduced flexibility.
As of this writing, there is thankfully a thirdparty offline installer still
available:
```
pip3 install aqtinstall
aqt install -O <Qt Location> 5.15.2 mac desktop
```
pip3 install aqtinstall
aqt install -O <Qt Location> 5.15.2 mac desktop
(or whatever version / OS you need). This installer is surprisingly fast
and seems well maintained - note that we don't use this for Windows as
that is completely built from source using MXE.
In order to use this Qt installation, simply add it to your PATH:
```
PATH=<Qt Location>/<version>/<type>/bin:$PATH
```
QtWebKit is needed, if you want to print, but no longer part of Qt5,
so you need to download it and compile. In case you just want to test
without print possibility omit this step.
```
git clone -b 5.212 https://github.com/qt/qtwebkit
mkdir -p qtwebkit/WebKitBuild/Release
cd qtwebkit/WebKitBuild/Release
cmake -DPORT=Qt -DCMAKE_BUILD_TYPE=Release -DQt5_DIR=/<Qt Location>/<version>/<type>/lib/cmake/Qt5 ../..
make install
```
git clone -b 5.212 https://github.com/qt/qtwebkit
mkdir -p qtwebkit/WebKitBuild/Release
cd qtwebkit/WebKitBuild/Release
cmake -DPORT=Qt -DCMAKE_BUILD_TYPE=Release -DQt5_DIR=/<Qt Location>/<version>/<type>/lib/cmake/Qt5 ../..
make install
### Other third party library dependencies
Other third party library dependencies
--------------------------------------
In order for our cloud storage to be fully functional you need
libgit2 0.26 or newer.
### cmake build system
cmake build system
------------------
Our main build system is based on cmake. But qmake is needed
for the googlemaps plugin and the iOS build.
@ -121,35 +114,32 @@ distribution (see build instructions).
## Build options for Subsurface
Build options for Subsurface
----------------------------
The following options are recognised when passed to cmake:
`-DCMAKE_BUILD_TYPE=Release` create a release build
`-DCMAKE_BUILD_TYPE=Debug` create a debug build
-DCMAKE_BUILD_TYPE=Release create a release build
-DCMAKE_BUILD_TYPE=Debug create a debug build
The Makefile that was created using cmake can be forced into a much more
verbose mode by calling
```
make VERBOSE=1
```
make VERBOSE=1
Many more variables are supported, the easiest way to interact with them is
to call
```
ccmake .
```
ccmake .
in your build directory.
### Building the development version of Subsurface under Linux
Building the development version of Subsurface under Linux
----------------------------------------------------------
On Fedora you need
```
sudo dnf install autoconf automake bluez-libs-devel cmake gcc-c++ git \
libcurl-devel libsqlite3x-devel libssh2-devel libtool libudev-devel \
libusbx-devel libxml2-devel libxslt-devel make \
@ -157,12 +147,10 @@ sudo dnf install autoconf automake bluez-libs-devel cmake gcc-c++ git \
qt5-qtlocation-devel qt5-qtscript-devel qt5-qtsvg-devel \
qt5-qttools-devel qt5-qtwebkit-devel redhat-rpm-config \
bluez-libs-devel libgit2-devel libzip-devel libmtp-devel
```
Package names are sadly different on OpenSUSE
```
sudo zypper install git gcc-c++ make autoconf automake libtool cmake libzip-devel \
libxml2-devel libxslt-devel sqlite3-devel libusb-1_0-devel \
libqt5-linguist-devel libqt5-qttools-devel libQt5WebKitWidgets-devel \
@ -170,11 +158,9 @@ sudo zypper install git gcc-c++ make autoconf automake libtool cmake libzip-deve
libqt5-qtscript-devel libqt5-qtdeclarative-devel \
libqt5-qtconnectivity-devel libqt5-qtlocation-devel libcurl-devel \
bluez-devel libgit2-devel libmtp-devel
```
On Debian Bookworm this seems to work
```
sudo apt install \
autoconf automake cmake g++ git libbluetooth-dev libcrypto++-dev \
libcurl4-openssl-dev libgit2-dev libqt5qml5 libqt5quick5 libqt5svg5-dev \
@ -184,21 +170,17 @@ sudo apt install \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev
```
In order to build and run mobile-on-desktop, you also need
```
sudo apt install \
qtquickcontrols2-5-dev qml-module-qtquick-window2 qml-module-qtquick-dialogs \
qml-module-qtquick-layouts qml-module-qtquick-controls2 qml-module-qtquick-templates2 \
qml-module-qtgraphicaleffects qml-module-qtqml-models2 qml-module-qtquick-controls
```
Package names for Ubuntu 21.04
```
sudo apt install \
autoconf automake cmake g++ git libbluetooth-dev libcrypto++-dev \
libcurl4-gnutls-dev libgit2-dev libqt5qml5 libqt5quick5 libqt5svg5-dev \
@ -208,21 +190,17 @@ sudo apt install \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev
```
In order to build and run mobile-on-desktop, you also need
```
sudo apt install \
qtquickcontrols2-5-dev qml-module-qtquick-window2 qml-module-qtquick-dialogs \
qml-module-qtquick-layouts qml-module-qtquick-controls2 qml-module-qtquick-templates2 \
qml-module-qtgraphicaleffects qml-module-qtqml-models2 qml-module-qtquick-controls
```
On Raspberry Pi (Raspian Buster and Ubuntu Mate 20.04.1) this seems to work
```
sudo apt install \
autoconf automake cmake g++ git libbluetooth-dev libcrypto++-dev \
libcurl4-gnutls-dev libgit2-dev libqt5qml5 libqt5quick5 libqt5svg5-dev \
@ -232,16 +210,13 @@ sudo apt install \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev
```
In order to build and run mobile-on-desktop, you also need
```
sudo apt install \
qtquickcontrols2-5-dev qml-module-qtquick-window2 qml-module-qtquick-dialogs \
qml-module-qtquick-layouts qml-module-qtquick-controls2 qml-module-qtquick-templates2 \
qml-module-qtgraphicaleffects qml-module-qtqml-models2 qml-module-qtquick-controls
```
Note that on Ubuntu Mate on the Raspberry Pi, you may need to configure
@ -251,7 +226,6 @@ swap space configured by default. See the dphys-swapfile package.
On Raspberry Pi OS with Desktop (64-bit) Released April 4th, 2022, this seems
to work
```
sudo apt install \
autoconf automake cmake g++ git libbluetooth-dev libcrypto++-dev \
libcurl4-gnutls-dev libgit2-dev libqt5qml5 libqt5quick5 libqt5svg5-dev \
@ -261,16 +235,15 @@ sudo apt install \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev
```
Note that you'll need to increase the swap space as the default of 100MB
doesn't seem to be enough. 1024MB worked on a 3B+.
If maps aren't working, copy the googlemaps plugin
from `<build_dir>/subsurface/googlemaps/build/libqtgeoservices_googlemaps.so`
to `/usr/lib/aarch64-linux-gnu/qt5/plugins/geoservices/`.
from <build_dir>/subsurface/googlemaps/build/libqtgeoservices_googlemaps.so
to /usr/lib/aarch64-linux-gnu/qt5/plugins/geoservices.
If Subsurface can't seem to see your dive computer on `/dev/ttyUSB0`, even after
If Subsurface can't seem to see your dive computer on /dev/ttyUSB0, even after
adjusting your account's group settings (see note below about usermod), it
might be that the FTDI driver doesn't recognize the VendorID/ProductID of your
computer. Follow the instructions here:
@ -283,14 +256,12 @@ follow TN_101.
On PCLinuxOS you appear to need the following packages
```
su -c "apt-get install -y autoconf automake cmake gcc-c++ git libtool \
lib64bluez-devel lib64qt5bluetooth-devel lib64qt5concurrent-devel \
lib64qt5help-devel lib64qt5location-devel lib64qt5quicktest-devel \
lib64qt5quickwidgets-devel lib64qt5script-devel lib64qt5svg-devel \
lib64qt5test-devel lib64qt5webkitwidgets-devel lib64qt5xml-devel \
lib64ssh2-devel lib64usb1.0-devel lib64zip-devel qttools5 qttranslations5"
```
lib64bluez-devel lib64qt5bluetooth-devel lib64qt5concurrent-devel \
lib64qt5help-devel lib64qt5location-devel lib64qt5quicktest-devel \
lib64qt5quickwidgets-devel lib64qt5script-devel lib64qt5svg-devel \
lib64qt5test-devel lib64qt5webkitwidgets-devel lib64qt5xml-devel \
lib64ssh2-devel lib64usb1.0-devel lib64zip-devel qttools5 qttranslations5"
In order to build Subsurface, use the supplied build script. This should
work on most systems that have all the prerequisite packages installed.
@ -298,121 +269,114 @@ work on most systems that have all the prerequisite packages installed.
You should have Subsurface sources checked out in a sane place, something
like this:
```
mkdir -p ~/src
cd ~/src
git clone https://github.com/Subsurface/subsurface.git
./subsurface/scripts/build.sh # <- this step will take quite a while as it
# compiles a handful of libraries before
# building Subsurface
```
Now you can run Subsurface like this:
```
cd ~/src/subsurface/build
./subsurface
```
Note: on many Linux versions (for example on Kubuntu 15.04) the user must
belong to the `dialout` group.
belong to the dialout group.
You may need to run something like
```
sudo usermod -a -G dialout $USER
```
sudo usermod -a -G dialout username
with your correct username and log out and log in again for that to take
effect.
If you get errors like:
```
./subsurface: error while loading shared libraries: libGrantlee_Templates.so.5: cannot open shared object file: No such file or directory
```
You can run the following command:
```
sudo ldconfig ~/src/install-root/lib
```
### Building Subsurface under MacOSX
Building Subsurface under MacOSX
--------------------------------
While it is possible to build all required components completely from source,
at this point the preferred way to build Subsurface is to set up the build
infrastructure via Homebrew and then build the dependencies from source.
0. You need to have XCode installed. The first time (and possibly after updating OSX)
0) You need to have XCode installed. The first time (and possibly after updating OSX)
```
xcode-select --install
```
1. install Homebrew (see https://brew.sh) and then the required build infrastructure:
1) install Homebrew (see https://brew.sh) and then the required build infrastructure:
```
brew install autoconf automake libtool pkg-config gettext
```
2. install Qt
2) install Qt
download the macOS installer from https://download.qt.io/official_releases/online_installers
and use it to install the desired Qt version. At this point the latest Qt5 version is still
preferred over Qt6.
If you plan to deploy your build to an Apple Silicon Mac, you may have better results with
Bluetooth connections if you install Qt5.15.13. If Qt5.15.13 is not available via the
Bluetooth connections if you install Qt5.15.13. If Qt5.15.13 is not availablevia the
installer, you can download from https://download.qt.io/official_releases/qt/5.15/5.15.13
and build using the usual configure, make, and make install.
3. now build Subsurface
3) now build Subsurface
```
cd ~/src; bash subsurface/scripts/build.sh -build-deps
```
if you are building against Qt6 (still experimental) you can create a universal binary with
```
cd ~/src; bash subsurface/scripts/build.sh -build-with-qt6 -build-deps -fat-build
```
After the above is done, Subsurface.app will be available in the
subsurface/build directory. You can run Subsurface with the command
A. `open subsurface/build/Subsurface.app`
A) open subsurface/build/Subsurface.app
this will however not show diagnostic output
B. `subsurface/build/Subsurface.app/Contents/MacOS/Subsurface`
the [Tab] key is your friend :-)
B) subsurface/build/Subsurface.app/Contents/MacOS/Subsurface
the TAB key is your friend :-)
Debugging can be done with either Xcode or QtCreator.
To install the app for all users, move subsurface/build/Subsurface.app to /Applications.
### Cross-building Subsurface on MacOSX for iOS
Cross-building Subsurface on MacOSX for iOS
-------------------------------------------
0. build SubSurface under MacOSX and iOS
1) build SubSurface under MacOSX and iOS
1. `cd <repo>/..; bash <repo>/scripts/build.sh -build-deps -both`
1.1) cd <repo>/..; bash <repo>/scripts/build.sh -build-deps -both
note: this is mainly done to ensure all external dependencies are downloaded and set
to the correct versions
2. follow [these instructions](packaging/ios/README.md)
2) continue as described in subsurface/packaging/ios
### Cross-building Subsurface on Linux for Windows
Cross-building Subsurface on Linux for Windows
----------------------------------------------
Subsurface for Windows builds on linux by using the [MXE (M cross environment)](https://github.com/mxe/mxe). The easiest way to do this is to use a Docker container with a pre-built MXE for Subsurface by following [these instructions](packaging/windows/README.md).
Subsurface builds nicely with MinGW - the official builds are done as
cross builds under Linux (currently on Ubuntu 20.04). A shell script to do
that (plus the .nsi file to create the installer with makensis) are
included in the packaging/windows directory.
Please read through the explanations and instructions in
packaging/windows/README.md, packaging/windows/create-win-installer.sh, and
packaging/windows/mxe-based-build.sh if you want to build the Windows version
on your Linux system.
### Building Subsurface on Windows
Building Subsurface on Windows
------------------------------
This is NOT RECOMMENDED. To the best of our knowledge there is one single
person who regularly does this. The Subsurface team does not provide support
@ -422,9 +386,8 @@ The lack of a working package management system for Windows makes it
really painful to build Subsurface natively under Windows,
so we don't support that at all.
But if you want to build Subsurface on a Windows system, the docker based [cross-build for Windows](packaging/windows/README.md) works just fine in WSL2 on Windows.
Cross-building Subsurface on Linux for Android
----------------------------------------------
### Cross-building Subsurface on Linux for Android
Follow [these instructions](packaging/android/README.md).
Follow the instructions in packaging/android/README

View File

@ -21,9 +21,16 @@ Report bugs and issues at https://github.com/Subsurface/subsurface/issues
License: GPLv2
We are releasing 'nightly' builds of Subsurface that are built from the latest version of the code. Versions of this build for Windows, macOS, Android (requiring sideloading), and a Linux AppImage can be downloaded from the [Latest Dev Release](https://www.subsurface-divelog.org/latest-release/) page on [our website](https://www.subsurface-divelog.org/). Alternatively, they can be downloaded [directly from GitHub](https://github.com/subsurface/nightly-builds/releases). Additionally, those same versions are
We frequently make new test versions of Subsurface available at
http://subsurface-divelog.org/downloads/test/ and there you can always get
the latest builds for Mac, Windows, Linux AppImage and Android (with some
caveats about installability). Additionally, those same versions are
posted to the Subsurface-daily repos on Ubuntu Launchpad, Fedora COPR, and
OpenSUSE OBS, and released to [Snapcraft](https://snapcraft.io/subsurface) into the 'edge' channel of subsurface.
OpenSUSE OBS.
These tend to contain the latest bug fixes and features, but also
occasionally the latest bugs and issues. Please understand when using them
that these are primarily intended for testing.
You can get the sources to the latest development version from the git
repository:
@ -35,11 +42,17 @@ git clone https://github.com/Subsurface/subsurface.git
You can also fork the repository and browse the sources at the same site,
simply using https://github.com/Subsurface/subsurface
Additionally, artifacts for Windows, macOS, Android, Linux AppImage, and iOS (simulator build) are generated for all open pull requests and linked in pull request comments. Use these if you want to test the changes in a specific pull request and provide feedback before it has been merged.
If you want the latest release (instead of the bleeding edge
development version) you can either get this via git or the release tar
ball. After cloning run the following command:
If you want a more stable version that is a little bit more tested you can get this from the [Curent Release](https://www.subsurface-divelog.org/current-release/) page on [our website](https://www.subsurface-divelog.org/).
```
git checkout v5.0.10 (or whatever the last release is)
```
Detailed build instructions can be found in the [INSTALL.md](/INSTALL.md) file.
or download a tarball from http://subsurface-divelog.org/downloads/Subsurface-5.0.10.tgz
Detailed build instructions can be found in the INSTALL file.
## System Requirements

View File

@ -100,7 +100,6 @@ enum class EditProfileType {
ADD,
REMOVE,
MOVE,
EDIT,
};
void replanDive(dive *d); // dive computer(s) and cylinder(s) of first argument will be consumed!
void editProfile(const dive *d, int dcNr, EditProfileType type, int count);

View File

@ -879,7 +879,6 @@ QString editProfileTypeToString(EditProfileType type, int count)
case EditProfileType::ADD: return Command::Base::tr("Add stop");
case EditProfileType::REMOVE: return Command::Base::tr("Remove %n stop(s)", "", count);
case EditProfileType::MOVE: return Command::Base::tr("Move %n stop(s)", "", count);
case EditProfileType::EDIT: return Command::Base::tr("Edit stop");
}
}
@ -905,7 +904,7 @@ EditProfile::EditProfile(const dive *source, int dcNr, EditProfileType type, int
copy_samples(sdc, &dc);
copy_events(sdc, &dc);
setText(editProfileTypeToString(type, count) + " " + diveNumberOrDate(d));
setText(editProfileTypeToString(type, count) + diveNumberOrDate(d));
}
EditProfile::~EditProfile()
@ -926,7 +925,6 @@ void EditProfile::undo()
std::swap(sdc->samples, dc.samples);
std::swap(sdc->alloc_samples, dc.alloc_samples);
std::swap(sdc->sample, dc.sample);
std::swap(sdc->events, dc.events);
std::swap(sdc->maxdepth, dc.maxdepth);
std::swap(d->maxdepth, maxdepth);
std::swap(d->meandepth, meandepth);
@ -1127,7 +1125,7 @@ AddCylinder::AddCylinder(bool currentDiveOnly) :
setText(Command::Base::tr("Add cylinder"));
else
setText(Command::Base::tr("Add cylinder (%n dive(s))", "", dives.size()));
cyl = create_new_manual_cylinder(dives[0]);
cyl = create_new_cylinder(dives[0]);
indexes.reserve(dives.size());
}
@ -1319,7 +1317,8 @@ EditCylinder::EditCylinder(int index, cylinder_t cylIn, EditCylinderType typeIn,
void EditCylinder::redo()
{
for (size_t i = 0; i < dives.size(); ++i) {
set_tank_info_data(&tank_info_table, cyl[i].type.description, cyl[i].type.size, cyl[i].type.workingpressure);
set_tank_info_size(&tank_info_table, cyl[i].type.description, cyl[i].type.size);
set_tank_info_workingpressure(&tank_info_table, cyl[i].type.description, cyl[i].type.workingpressure);
std::swap(*get_cylinder(dives[i], indexes[i]), cyl[i]);
update_cylinder_related_info(dives[i]);
emit diveListNotifier.cylinderEdited(dives[i], indexes[i]);

View File

@ -2309,8 +2309,8 @@ static int likely_same_dive(const struct dive *a, const struct dive *b)
int match, fuzz = 20 * 60;
/* don't merge manually added dives with anything */
if (is_dc_manually_added_dive(&a->dc) ||
is_dc_manually_added_dive(&b->dc))
if (is_manually_added_dc(&a->dc) ||
is_manually_added_dc(&b->dc))
return 0;
/*
@ -3244,11 +3244,11 @@ extern "C" int depth_to_mbar(int depth, const struct dive *dive)
extern "C" double depth_to_mbarf(int depth, const struct dive *dive)
{
// For downloaded and planned dives, use DC's values
// To downloaded and planned dives, use DC's values
int salinity = dive->dc.salinity;
pressure_t surface_pressure = dive->dc.surface_pressure;
if (is_dc_manually_added_dive(&dive->dc)) { // For manual dives, salinity and pressure in another place...
if (is_manually_added_dc(&dive->dc)) { // To manual dives, salinity and pressure in another place...
surface_pressure = dive->surface_pressure;
salinity = dive->user_salinity;
}
@ -3271,8 +3271,8 @@ extern "C" double depth_to_atm(int depth, const struct dive *dive)
* take care of this, but the Uemis we support natively */
extern "C" int rel_mbar_to_depth(int mbar, const struct dive *dive)
{
// For downloaded and planned dives, use DC's salinity. Manual dives, use user's salinity
int salinity = is_dc_manually_added_dive(&dive->dc) ? dive->user_salinity : dive->dc.salinity;
// To downloaded and planned dives, use DC's salinity. Manual dives, use user's salinity
int salinity = is_manually_added_dc(&dive->dc) ? dive->user_salinity : dive->dc.salinity;
if (!salinity)
salinity = SEAWATER_SALINITY;
@ -3283,8 +3283,8 @@ extern "C" int rel_mbar_to_depth(int mbar, const struct dive *dive)
extern "C" int mbar_to_depth(int mbar, const struct dive *dive)
{
// For downloaded and planned dives, use DC's pressure. Manual dives, use user's pressure
pressure_t surface_pressure = is_dc_manually_added_dive(&dive->dc)
// To downloaded and planned dives, use DC's pressure. Manual dives, use user's pressure
pressure_t surface_pressure = is_manually_added_dc(&dive->dc)
? dive->surface_pressure
: dive->dc.surface_pressure;

View File

@ -492,6 +492,11 @@ void add_extra_data(struct divecomputer *dc, const char *key, const char *value)
}
}
bool is_dc_planner(const struct divecomputer *dc)
{
return same_string(dc->model, "planned dive");
}
/*
* Match two dive computer entries against each other, and
* tell if it's the same dive. Return 0 if "don't know",
@ -543,27 +548,14 @@ void free_dc(struct divecomputer *dc)
free(dc);
}
static const char *planner_dc_name = "planned dive";
bool is_dc_planner(const struct divecomputer *dc)
static const char *manual_dc_name = "manually added dive";
bool is_manually_added_dc(const struct divecomputer *dc)
{
return dc && same_string(dc->model, planner_dc_name);
return dc && dc->samples <= 50 &&
same_string(dc->model, manual_dc_name);
}
void make_planner_dc(struct divecomputer *dc)
{
free((void *)dc->model);
dc->model = strdup(planner_dc_name);
}
const char *manual_dc_name = "manually added dive";
bool is_dc_manually_added_dive(const struct divecomputer *dc)
{
return dc && same_string(dc->model, manual_dc_name);
}
void make_manually_added_dive_dc(struct divecomputer *dc)
void make_manually_added_dc(struct divecomputer *dc)
{
free((void *)dc->model);
dc->model = strdup(manual_dc_name);

View File

@ -67,12 +67,10 @@ extern void add_event_to_dc(struct divecomputer *dc, struct event *ev);
extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name);
extern void remove_event_from_dc(struct divecomputer *dc, struct event *event);
extern void add_extra_data(struct divecomputer *dc, const char *key, const char *value);
extern uint32_t calculate_string_hash(const char *str);
extern bool is_dc_planner(const struct divecomputer *dc);
extern void make_planner_dc(struct divecomputer *dc);
extern const char *manual_dc_name;
extern bool is_dc_manually_added_dive(const struct divecomputer *dc);
extern void make_manually_added_dive_dc(struct divecomputer *dc);
extern uint32_t calculate_string_hash(const char *str);
extern bool is_manually_added_dc(const struct divecomputer *dc);
extern void make_manually_added_dc(struct divecomputer *dc);
/* Check if two dive computer entries are the exact same dive (-1=no/0=maybe/1=yes) */
extern int match_one_dc(const struct divecomputer *a, const struct divecomputer *b);

View File

@ -109,7 +109,7 @@ void add_tank_info_imperial(struct tank_info_table *table, const char *name, int
add_to_tank_info_table(table, table->nr, info);
}
static struct tank_info *get_tank_info(struct tank_info_table *table, const char *name)
extern struct tank_info *get_tank_info(struct tank_info_table *table, const char *name)
{
for (int i = 0; i < table->nr; ++i) {
if (same_string(table->infos[i].name, name))
@ -118,41 +118,34 @@ static struct tank_info *get_tank_info(struct tank_info_table *table, const char
return NULL;
}
extern void set_tank_info_data(struct tank_info_table *table, const char *name, volume_t size, pressure_t working_pressure)
extern void set_tank_info_size(struct tank_info_table *table, const char *name, volume_t size)
{
struct tank_info *info = get_tank_info(table, name);
if (info) {
if (info->ml != 0 || info->bar != 0) {
info->bar = working_pressure.mbar / 1000;
// Try to be smart about metric vs. imperial
if (info->cuft == 0 && info->psi == 0)
info->ml = size.mliter;
else
info->cuft = lrint(ml_to_cuft(size.mliter));
} else {
info->psi = lrint(to_PSI(working_pressure));
info->cuft = lrint(ml_to_cuft(size.mliter) * mbar_to_atm(working_pressure.mbar));
}
} else {
// Metric is a better choice as the volume is independent of the working pressure
add_tank_info_metric(table, name, size.mliter, working_pressure.mbar / 1000);
// By default add metric...?
add_tank_info_metric(table, name, size.mliter, 0);
}
}
extern void extract_tank_info(const struct tank_info *info, volume_t *size, pressure_t *working_pressure)
{
working_pressure->mbar = info->bar != 0 ? info->bar * 1000 : psi_to_mbar(info->psi);
if (info->ml != 0)
size->mliter = info->ml;
else if (working_pressure->mbar != 0)
size->mliter = lrint(cuft_to_l(info->cuft) * 1000 / mbar_to_atm(working_pressure->mbar));
}
extern bool get_tank_info_data(struct tank_info_table *table, const char *name, volume_t *size, pressure_t *working_pressure)
extern void set_tank_info_workingpressure(struct tank_info_table *table, const char *name, pressure_t working_pressure)
{
struct tank_info *info = get_tank_info(table, name);
if (info) {
extract_tank_info(info, size, working_pressure);
return true;
// Try to be smart about metric vs. imperial
if (info->cuft == 0 && info->psi == 0)
info->bar = working_pressure.mbar / 1000;
else
info->psi = lrint(mbar_to_PSI(working_pressure.mbar));
} else {
// By default add metric...?
add_tank_info_metric(table, name, 0, working_pressure.mbar / 1000);
}
return false;
}
/* placeholders for a few functions that we need to redesign for the Qt UI */
@ -214,6 +207,13 @@ void add_cloned_weightsystem(struct weightsystem_table *t, weightsystem_t ws)
add_to_weightsystem_table(t, t->nr, clone_weightsystem(ws));
}
/* Add a clone of a weightsystem to the end of a weightsystem table.
* Cloned means that the description-string is copied. */
void add_cloned_weightsystem_at(struct weightsystem_table *t, weightsystem_t ws)
{
add_to_weightsystem_table(t, t->nr, clone_weightsystem(ws));
}
cylinder_t clone_cylinder(cylinder_t cyl)
{
cylinder_t res = cyl;
@ -510,37 +510,11 @@ cylinder_t create_new_cylinder(const struct dive *d)
cylinder_t cyl = empty_cylinder;
fill_default_cylinder(d, &cyl);
cyl.start = cyl.type.workingpressure;
cyl.manually_added = true;
cyl.cylinder_use = OC_GAS;
return cyl;
}
cylinder_t create_new_manual_cylinder(const struct dive *d)
{
cylinder_t cyl = create_new_cylinder(d);
cyl.manually_added = true;
return cyl;
}
void add_default_cylinder(struct dive *d)
{
// Only add if there are no cylinders yet
if (d->cylinders.nr > 0)
return;
cylinder_t cyl;
if (!empty_string(prefs.default_cylinder)) {
cyl = create_new_cylinder(d);
} else {
cyl = empty_cylinder;
// roughly an AL80
cyl.type.description = strdup(translate("gettextFromC", "unknown"));
cyl.type.size.mliter = 11100;
cyl.type.workingpressure.mbar = 207000;
}
add_cylinder(&d->cylinders, 0, cyl);
reset_cylinders(d, false);
}
static bool show_cylinder(const struct dive *d, int i)
{
if (is_cylinder_used(d, i))

View File

@ -93,8 +93,7 @@ extern void reset_cylinders(struct dive *dive, bool track_gas);
extern int gas_volume(const cylinder_t *cyl, pressure_t p); /* Volume in mliter of a cylinder at pressure 'p' */
extern int find_best_gasmix_match(struct gasmix mix, const struct cylinder_table *cylinders);
extern void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl); /* dive is needed to fill out MOD, which depends on salinity. */
extern cylinder_t create_new_manual_cylinder(const struct dive *dive); /* dive is needed to fill out MOD, which depends on salinity. */
extern void add_default_cylinder(struct dive *dive);
extern cylinder_t create_new_cylinder(const struct dive *dive); /* dive is needed to fill out MOD, which depends on salinity. */
extern int first_hidden_cylinder(const struct dive *d);
#ifdef DEBUG_CYL
extern void dump_cylinders(struct dive *dive, bool verbose);
@ -126,9 +125,9 @@ extern void reset_tank_info_table(struct tank_info_table *table);
extern void clear_tank_info_table(struct tank_info_table *table);
extern void add_tank_info_metric(struct tank_info_table *table, const char *name, int ml, int bar);
extern void add_tank_info_imperial(struct tank_info_table *table, const char *name, int cuft, int psi);
extern void extract_tank_info(const struct tank_info *info, volume_t *size, pressure_t *working_pressure);
extern bool get_tank_info_data(struct tank_info_table *table, const char *name, volume_t *size, pressure_t *pressure);
extern void set_tank_info_data(struct tank_info_table *table, const char *name, volume_t size, pressure_t working_pressure);
extern void set_tank_info_size(struct tank_info_table *table, const char *name, volume_t size);
extern void set_tank_info_workingpressure(struct tank_info_table *table, const char *name, pressure_t working_pressure);
extern struct tank_info *get_tank_info(struct tank_info_table *table, const char *name);
struct ws_info_t {
const char *name;

View File

@ -343,6 +343,71 @@ QString vqasprintf_loc(const char *fmt, va_list ap_in)
return ret;
}
// Put a formated string respecting the default locale into a C-style array in UTF-8 encoding.
// The only complication arises from the fact that we don't want to cut through multi-byte UTF-8 code points.
extern "C" int snprintf_loc(char *dst, size_t size, const char *cformat, ...)
{
va_list ap;
va_start(ap, cformat);
int res = vsnprintf_loc(dst, size, cformat, ap);
va_end(ap);
return res;
}
extern "C" int vsnprintf_loc(char *dst, size_t size, const char *cformat, va_list ap)
{
QByteArray utf8 = vqasprintf_loc(cformat, ap).toUtf8();
const char *data = utf8.constData();
size_t utf8_size = utf8.size();
if (size == 0)
return utf8_size;
if (size < utf8_size + 1) {
memcpy(dst, data, size - 1);
if ((data[size - 1] & 0xC0) == 0x80) {
// We truncated a multi-byte UTF-8 encoding.
--size;
// Jump to last copied byte.
if (size > 0)
--size;
while(size > 0 && (dst[size] & 0xC0) == 0x80)
--size;
dst[size] = 0;
} else {
dst[size - 1] = 0;
}
} else {
memcpy(dst, data, utf8_size + 1); // QByteArray guarantees a trailing 0
}
return utf8_size;
}
int asprintf_loc(char **dst, const char *cformat, ...)
{
va_list ap;
va_start(ap, cformat);
int res = vasprintf_loc(dst, cformat, ap);
va_end(ap);
return res;
}
int vasprintf_loc(char **dst, const char *cformat, va_list ap)
{
QByteArray utf8 = vqasprintf_loc(cformat, ap).toUtf8();
*dst = strdup(utf8.constData());
return utf8.size();
}
extern "C" void put_vformat_loc(struct membuffer *b, const char *fmt, va_list args)
{
QByteArray utf8 = vqasprintf_loc(fmt, args).toUtf8();
const char *data = utf8.constData();
size_t utf8_size = utf8.size();
make_room(b, utf8_size);
memcpy(b->buffer + b->len, data, utf8_size);
b->len += utf8_size;
}
// TODO: Avoid back-and-forth conversion between UTF16 and UTF8.
std::string casprintf_loc(const char *cformat, ...)
{

View File

@ -12,7 +12,22 @@
__printf(1, 2) QString qasprintf_loc(const char *cformat, ...);
__printf(1, 0) QString vqasprintf_loc(const char *cformat, va_list ap);
__printf(1, 2) std::string casprintf_loc(const char *cformat, ...);
#endif
#ifdef __cplusplus
extern "C" {
#endif
__printf(3, 4) int snprintf_loc(char *dst, size_t size, const char *cformat, ...);
__printf(3, 0) int vsnprintf_loc(char *dst, size_t size, const char *cformat, va_list ap);
__printf(2, 3) int asprintf_loc(char **dst, const char *cformat, ...);
__printf(2, 0) int vasprintf_loc(char **dst, const char *cformat, va_list ap);
#ifdef __cplusplus
}
__printf(1, 2) std::string format_string_std(const char *fmt, ...);
#endif
#endif

View File

@ -169,6 +169,15 @@ void put_format(struct membuffer *b, const char *fmt, ...)
va_end(args);
}
void put_format_loc(struct membuffer *b, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
put_vformat_loc(b, fmt, args);
va_end(args);
}
void put_milli(struct membuffer *b, const char *pre, int value, const char *post)
{
int i;

View File

@ -75,7 +75,9 @@ extern void strip_mb(struct membuffer *);
/* The pointer obtained by mb_cstring is invalidated by any modifictation to the membuffer! */
extern const char *mb_cstring(struct membuffer *);
extern __printf(2, 0) void put_vformat(struct membuffer *, const char *, va_list);
extern __printf(2, 0) void put_vformat_loc(struct membuffer *, const char *, va_list);
extern __printf(2, 3) void put_format(struct membuffer *, const char *fmt, ...);
extern __printf(2, 3) void put_format_loc(struct membuffer *, const char *fmt, ...);
extern __printf(2, 0) char *add_to_string_va(char *old, const char *fmt, va_list args);
extern __printf(2, 3) char *add_to_string(char *old, const char *fmt, ...);

View File

@ -71,7 +71,6 @@ int get_picture_idx(const struct picture_table *t, const char *filename)
return -1;
}
#if !defined(SUBSURFACE_MOBILE)
/* Return distance of timestamp to time of dive. Result is always positive, 0 means during dive. */
static timestamp_t time_from_dive(const struct dive *d, timestamp_t timestamp)
{
@ -119,6 +118,7 @@ static bool dive_check_picture_time(const struct dive *d, timestamp_t timestamp)
return time_from_dive(d, timestamp) < D30MIN;
}
#if !defined(SUBSURFACE_MOBILE)
/* Creates a picture and indicates the dive to which this picture should be added.
* The caller is responsible for actually adding the picture to the dive.
* If no appropriate dive was found, no picture is created and NULL is returned.

View File

@ -153,6 +153,17 @@ dive_trip_t *create_trip_from_dive(struct dive *dive)
return trip;
}
dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive, struct trip_table *trip_table_arg)
{
dive_trip_t *dive_trip;
dive_trip = create_trip_from_dive(dive);
add_dive_to_trip(dive, dive_trip);
insert_trip(dive_trip, trip_table_arg);
return dive_trip;
}
/* random threshold: three days without diving -> new trip
* this works very well for people who usually dive as part of a trip and don't
* regularly dive at a local facility; this is why trips are an optional feature */

View File

@ -43,6 +43,7 @@ extern void sort_trip_table(struct trip_table *table);
extern dive_trip_t *alloc_trip(void);
extern dive_trip_t *create_trip_from_dive(struct dive *dive);
extern dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive, struct trip_table *trip_table_arg);
extern dive_trip_t *get_dives_to_autogroup(struct dive_table *table, int start, int *from, int *to, bool *allocated);
extern dive_trip_t *get_trip_for_new_dive(struct dive *new_dive, bool *allocated);
extern dive_trip_t *get_trip_by_uniq_id(int tripId);

View File

@ -185,12 +185,13 @@ void DivePlannerWidget::heightChanged(const int height)
void DivePlannerWidget::waterTypeUpdateTexts()
{
double density;
/* Do not set text in last/custom element */
for (int i = 0; i < ui.waterType->count()-1; i++) {
if (ui.waterType->itemData(i) != QVariant::Invalid) {
QString densityText = ui.waterType->itemText(i).split("(")[0].trimmed();
double density = ui.waterType->itemData(i).toInt() / 10000.0;
densityText.append(QStringLiteral(" (%L1%2)").arg(density, 0, 'f', 3).arg(tr("kg/")));
density = ui.waterType->itemData(i).toInt() / 10000.0;
densityText.append(QString(" (%L1%2)").arg(density, 0, 'f', 2).arg(tr("kg/")));
ui.waterType->setItemText(i, densityText);
}
}

View File

@ -210,7 +210,7 @@
</property>
<property name="maximumSize">
<size>
<width>100</width>
<width>90</width>
<height>16777215</height>
</size>
</property>
@ -232,9 +232,6 @@
<property name="value">
<double>1.000000000000000</double>
</property>
<property name="decimals">
<double>3</double>
</property>
</widget>
</item>
<item row="4" column="0" colspan="4">

View File

@ -665,10 +665,8 @@ void MainWindow::on_actionReplanDive_triggered()
{
if (!plannerStateClean() || !current_dive || !userMayChangeAppState())
return;
const struct divecomputer *dc = get_dive_dc(current_dive, profile->dc);
if (!(is_dc_planner(dc) || is_dc_manually_added_dive(dc))) {
if (QMessageBox::warning(this, tr("Warning"), tr("Trying to replan a dive profile that has not been manually added."),
else if (!is_dc_planner(get_dive_dc(current_dive, profile->dc))) {
if (QMessageBox::warning(this, tr("Warning"), tr("Trying to replan a dive dive profile that is not a dive plan."),
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
return;
}
@ -709,9 +707,8 @@ void MainWindow::on_actionAddDive_triggered()
d.dc.duration.seconds = 40 * 60;
d.dc.maxdepth.mm = M_OR_FT(15, 45);
d.dc.meandepth.mm = M_OR_FT(13, 39); // this creates a resonable looking safety stop
make_manually_added_dive_dc(&d.dc);
make_manually_added_dc(&d.dc);
fake_dc(&d.dc);
add_default_cylinder(&d);
fixup_dive(&d);
Command::addDive(&d, divelog.autogroup, true);

View File

@ -218,13 +218,18 @@ void TankInfoDelegate::setModelData(QWidget *, QAbstractItemModel *model, const
mymodel->setData(IDX(CylindersModel::TYPE), cylinderName, CylindersModel::TEMP_ROLE);
return;
}
int tankSize = 0;
int tankPressure = 0;
tank_info *info = get_tank_info(&tank_info_table, qPrintable(cylinderName));
if (info) {
// OMG, the units here are a mess.
tankSize = info->ml != 0 ? info->ml : lrint(cuft_to_l(info->cuft) * 1000.0);
tankPressure = info->bar != 0 ? info->bar * 1000 : psi_to_mbar(info->psi);
}
volume_t tankSize = {0};
pressure_t tankPressure = {0};
get_tank_info_data(&tank_info_table, qPrintable(cylinderName), &tankSize, &tankPressure);
mymodel->setData(IDX(CylindersModel::TYPE), cylinderName, CylindersModel::TEMP_ROLE);
mymodel->setData(IDX(CylindersModel::WORKINGPRESS), tankPressure.mbar, CylindersModel::TEMP_ROLE);
mymodel->setData(IDX(CylindersModel::SIZE), tankSize.mliter, CylindersModel::TEMP_ROLE);
mymodel->setData(IDX(CylindersModel::WORKINGPRESS), tankPressure, CylindersModel::TEMP_ROLE);
mymodel->setData(IDX(CylindersModel::SIZE), tankSize, CylindersModel::TEMP_ROLE);
}
static QAbstractItemModel *createTankInfoModel(QWidget *parent)

View File

@ -52,7 +52,7 @@ void EmptyView::resizeEvent(QResizeEvent *)
update();
}
ProfileWidget::ProfileWidget() : d(nullptr), dc(0), placingCommand(false)
ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placingCommand(false)
{
ui.setupUi(this);
@ -122,13 +122,9 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), placingCommand(false)
connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &ProfileWidget::divesChanged);
connect(&diveListNotifier, &DiveListNotifier::settingsChanged, view.get(), &ProfileWidget2::settingsChanged);
connect(&diveListNotifier, &DiveListNotifier::cylinderAdded, this, &ProfileWidget::cylindersChanged);
connect(&diveListNotifier, &DiveListNotifier::cylinderRemoved, this, &ProfileWidget::cylindersChanged);
connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &ProfileWidget::cylindersChanged);
connect(view.get(), &ProfileWidget2::stopAdded, this, &ProfileWidget::stopAdded);
connect(view.get(), &ProfileWidget2::stopRemoved, this, &ProfileWidget::stopRemoved);
connect(view.get(), &ProfileWidget2::stopMoved, this, &ProfileWidget::stopMoved);
connect(view.get(), &ProfileWidget2::stopEdited, this, &ProfileWidget::stopEdited);
ui.profCalcAllTissues->setChecked(qPrefTechnicalDetails::calcalltissues());
ui.profCalcCeiling->setChecked(qPrefTechnicalDetails::calcceiling());
@ -196,10 +192,6 @@ void ProfileWidget::plotCurrentDive()
void ProfileWidget::plotDive(dive *dIn, int dcIn)
{
bool endEditMode = false;
if (editedDive && (dIn != d || dcIn != dc))
endEditMode = true;
d = dIn;
if (dcIn >= 0)
@ -210,7 +202,7 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn)
dc = std::min(dc, (int)number_of_computers(current_dive) - 1);
// Exit edit mode if the dive changed
if (endEditMode)
if (editedDive && (originalDive != d || editedDc != dc))
exitEditMode();
// If this is a manually added dive and we are not in the planner
@ -218,13 +210,13 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn)
if (d && !editedDive &&
DivePlannerPointsModel::instance()->currentMode() == DivePlannerPointsModel::NOTHING) {
struct divecomputer *comp = get_dive_dc(d, dc);
if (comp && is_dc_manually_added_dive(comp) && comp->samples && comp->samples <= 50)
if (comp && is_manually_added_dc(comp) && comp->samples)
editDive();
}
setEnabledToolbar(d != nullptr);
if (editedDive) {
view->plotDive(editedDive.get(), dc);
view->plotDive(editedDive.get(), editedDc);
setDive(editedDive.get(), dc);
} else if (d) {
view->setProfileState(d, dc);
@ -264,44 +256,24 @@ void ProfileWidget::rotateDC(int dir)
void ProfileWidget::divesChanged(const QVector<dive *> &dives, DiveField field)
{
// If the current dive is not in list of changed dives, do nothing.
// Only if duration or depth changed, the profile needs to be replotted.
// Also, if we are currently placing a command, don't do anything.
// Note that we cannot use Command::placingCommand(), because placing
// a depth or time change on the maintab requires an update.
if (!d || !dives.contains(d) || !(field.duration || field.depth) || placingCommand)
return;
// If we're editing the current dive and not currently
// If were editing the current dive and not currently
// placing command, we have to update the edited dive.
if (editedDive) {
copy_dive(d, editedDive.get());
// TODO: Holy moly that function sends too many signals. Fix it!
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), editedDc);
}
// Only if duration or depth changed, the profile needs to be replotted.
if (field.duration || field.depth)
plotCurrentDive();
}
void ProfileWidget::cylindersChanged(struct dive *changed, int pos)
{
// If the current dive is not in list of changed dives, do nothing.
// Only if duration or depth changed, the profile needs to be replotted.
// Also, if we are currently placing a command, don't do anything.
// Note that we cannot use Command::placingCommand(), because placing
// a depth or time change on the maintab requires an update.
if (!d || changed != d || !editedDive)
return;
// If we're editing the current dive we have to update the
// cylinders of the edited dive.
if (editedDive) {
copy_cylinders(&d->cylinders, &editedDive.get()->cylinders);
// TODO: Holy moly that function sends too many signals. Fix it!
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
}
}
void ProfileWidget::setPlanState(const struct dive *d, int dcNr)
{
exitEditMode();
@ -325,20 +297,22 @@ void ProfileWidget::unsetProfTissues()
void ProfileWidget::editDive()
{
editedDive.reset(alloc_dive());
editedDc = dc;
copy_dive(d, editedDive.get()); // Work on a copy of the dive
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::EDIT);
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
view->setEditState(editedDive.get(), dc);
originalDive = d;
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD);
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), editedDc);
view->setEditState(editedDive.get(), editedDc);
}
void ProfileWidget::exitEditMode()
{
if (!editedDive)
return;
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING);
view->setProfileState(d, dc); // switch back to original dive before erasing the copy.
editedDive.reset();
originalDive = nullptr;
}
// Update depths of edited dive
@ -364,34 +338,25 @@ void ProfileWidget::stopAdded()
{
if (!editedDive)
return;
calcDepth(*editedDive, dc);
calcDepth(*editedDive, editedDc);
Setter s(placingCommand, true);
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::ADD, 0);
Command::editProfile(editedDive.get(), editedDc, Command::EditProfileType::ADD, 0);
}
void ProfileWidget::stopRemoved(int count)
{
if (!editedDive)
return;
calcDepth(*editedDive, dc);
calcDepth(*editedDive, editedDc);
Setter s(placingCommand, true);
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::REMOVE, count);
Command::editProfile(editedDive.get(), editedDc, Command::EditProfileType::REMOVE, count);
}
void ProfileWidget::stopMoved(int count)
{
if (!editedDive)
return;
calcDepth(*editedDive, dc);
calcDepth(*editedDive, editedDc);
Setter s(placingCommand, true);
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::MOVE, count);
}
void ProfileWidget::stopEdited()
{
if (!editedDive)
return;
Setter s(placingCommand, true);
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::EDIT, 0);
Command::editProfile(editedDive.get(), editedDc, Command::EditProfileType::MOVE, count);
}

View File

@ -34,13 +34,11 @@ public:
private
slots:
void divesChanged(const QVector<dive *> &dives, DiveField field);
void cylindersChanged(struct dive *changed, int pos);
void unsetProfHR();
void unsetProfTissues();
void stopAdded();
void stopRemoved(int count);
void stopMoved(int count);
void stopEdited();
private:
std::unique_ptr<EmptyView> emptyView;
std::vector<QAction *> toolbarActions;
@ -51,6 +49,8 @@ private:
void exitEditMode();
void rotateDC(int dir);
OwningDivePtr editedDive;
int editedDc;
dive *originalDive;
bool placingCommand;
};

View File

@ -224,8 +224,11 @@ void TabDiveInformation::updateData(const std::vector<dive *> &, dive *currentDi
setIndexNoSignal(ui->atmPressType, 0); // Set the atmospheric pressure combo box to mbar
salinity_value = get_dive_salinity(currentDive);
if (salinity_value) { // Set water type indicator (EN13319 = 1.020 g/l)
if (ui->waterTypeCombo->isVisible()) { // If water salinity is editable then set correct water type in combobox:
setIndexNoSignal(ui->waterTypeCombo, updateSalinityComboIndex(salinity_value));
} else { // If water salinity is not editable: show water type as a text label
ui->waterTypeText->setText(get_water_type_string(salinity_value));
}
ui->salinityText->setText(get_salinity_string(salinity_value));
} else {
setIndexNoSignal(ui->waterTypeCombo, -1);
@ -346,7 +349,6 @@ void TabDiveInformation::divesChanged(const QVector<dive *> &dives, DiveField fi
else
salinity_value = currentDive->salinity;
setIndexNoSignal(ui->waterTypeCombo, updateSalinityComboIndex(salinity_value));
ui->waterTypeText->setText(get_water_type_string(salinity_value));
ui->salinityText->setText(QString("%L1g/").arg(salinity_value / 10.0));
}

View File

@ -254,7 +254,7 @@ void TabDiveNotes::updateData(const std::vector<dive *> &, dive *currentDive, in
ui.LocationLabel->setText(tr("Location"));
ui.NotesLabel->setText(tr("Notes"));
ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tag_list)));
bool isManual = is_dc_manually_added_dive(&currentDive->dc);
bool isManual = is_manually_added_dc(&currentDive->dc);
ui.depth->setVisible(isManual);
ui.depthLabel->setVisible(isManual);
ui.duration->setVisible(isManual);

@ -1 +1 @@
Subproject commit 9641883f2fc63928c6513895959fe72ed990e117
Subproject commit 9b12e8e638807d8af24e1e948134ee26edfe3192

View File

@ -399,6 +399,8 @@ Kirigami.Page {
delegate: Flickable {
id: internalScrollView
width: diveDetailsListView.width
height: diveDetailsListView.height
contentHeight: diveDetails.height
boundsBehavior: Flickable.StopAtBounds
property var modelData: model
DiveDetailsView {
@ -411,7 +413,7 @@ Kirigami.Page {
ScrollIndicator.horizontal: ScrollIndicator { }
Connections {
target: swipeModel
function onCurrentDiveChanged(index) {
onCurrentDiveChanged: {
currentIndex = index.row
diveDetailsListView.positionViewAtIndex(currentIndex, ListView.End)
}
@ -423,6 +425,7 @@ Kirigami.Page {
anchors.fill: parent
leftMargin: Kirigami.Units.smallSpacing
rightMargin: Kirigami.Units.smallSpacing
contentHeight: detailsEdit.height
// start invisible and scaled down, to get the transition
// off to the right start
visible: false

View File

@ -19,7 +19,7 @@ Item {
Connections {
target: rootItem
function onSettingsChanged() {
onSettingsChanged: {
qmlProfile.update()
}
}

View File

@ -32,10 +32,10 @@ Kirigami.ScrollablePage {
}
Connections {
target: Backend
function onLengthChanged() {
onLengthChanged: {
reload()
}
function onVolumeChanged() {
onVolumeChanged: {
reload()
}
}

View File

@ -318,7 +318,7 @@ Kirigami.Page {
Connections {
target: manager
function onRestartDownloadSignal() {
onRestartDownloadSignal: {
buttonBar.doDownload()
}
}

View File

@ -111,14 +111,14 @@ TemplatePage {
}
Connections {
target: manager
function onUploadFinish(success, text) {
onUploadFinish: {
if (success) {
pageStack.pop()
}
statusText.text = text
progress.value = 0
}
function onUploadProgress(percentage) {
onUploadProgress: {
progress.value = percentage
}
}

View File

@ -110,12 +110,10 @@ Kirigami.Page {
model: statsManager.var1List
currentIndex: statsManager.var1Index
onCurrentIndexChanged: {
if (currentIndex != statsManager.var1Index) {
statsManager.var1Changed(currentIndex)
}
}
}
}
ColumnLayout {
id: i2
Layout.column: wide ? 0 : 1
@ -129,12 +127,10 @@ Kirigami.Page {
model: statsManager.binner1List
currentIndex: statsManager.binner1Index
onCurrentIndexChanged: {
if (currentIndex != statsManager.binner1Index) {
statsManager.var1BinnerChanged(currentIndex)
}
}
}
}
ColumnLayout {
id: i3
Layout.column: wide ? 0 : 0
@ -149,12 +145,10 @@ Kirigami.Page {
currentIndex: statsManager.var2Index
Layout.fillWidth: false
onCurrentIndexChanged: {
if (currentIndex != statsManager.var2Index) {
statsManager.var2Changed(currentIndex)
}
}
}
}
ColumnLayout {
id: i4
Layout.column: wide ? 0 : 1
@ -169,12 +163,10 @@ Kirigami.Page {
currentIndex: statsManager.binner2Index
Layout.fillWidth: false
onCurrentIndexChanged: {
if (currentIndex != statsManager.binner2Index) {
statsManager.var2BinnerChanged(currentIndex)
}
}
}
}
ColumnLayout {
id: i5
Layout.column: wide ? 0 : 0
@ -189,12 +181,10 @@ Kirigami.Page {
currentIndex: statsManager.operation2Index
Layout.fillWidth: false
onCurrentIndexChanged: {
if (currentIndex != statsManager.operation2Index) {
statsManager.var2OperationChanged(currentIndex)
}
}
}
}
ColumnLayout {
id: i6
Layout.column: wide ? 0 : 0

View File

@ -1135,7 +1135,7 @@ bool QMLManager::checkDuration(struct dive *d, QString duration)
m = m6.captured(1).toInt();
}
d->dc.duration.seconds = d->duration.seconds = h * 3600 + m * 60 + s;
if (is_dc_manually_added_dive(&d->dc))
if (is_manually_added_dc(&d->dc))
free_samples(&d->dc);
else
appendTextToLog("Cannot change the duration on a dive that wasn't manually added");
@ -1153,7 +1153,7 @@ bool QMLManager::checkDepth(dive *d, QString depth)
// the depth <= 500m
if (0 <= depthValue && depthValue <= 500000) {
d->maxdepth.mm = depthValue;
if (is_dc_manually_added_dive(&d->dc)) {
if (is_manually_added_dc(&d->dc)) {
d->dc.maxdepth.mm = d->maxdepth.mm;
free_samples(&d->dc);
}
@ -1359,7 +1359,7 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt
if (diveChanged) {
if (d->maxdepth.mm == d->dc.maxdepth.mm &&
d->maxdepth.mm > 0 &&
is_dc_manually_added_dive(&d->dc) &&
is_manually_added_dc(&d->dc) &&
d->dc.samples == 0) {
// so we have depth > 0, a manually added dive and no samples
// let's create an actual profile so the desktop version can work it
@ -1736,7 +1736,7 @@ int QMLManager::addDive()
d.dc.duration.seconds = 40 * 60;
d.dc.maxdepth.mm = M_OR_FT(15, 45);
d.dc.meandepth.mm = M_OR_FT(13, 39); // this creates a resonable looking safety stop
make_manually_added_dive_dc(&d.dc);
make_manually_added_dc(&d.dc);
fake_dc(&d.dc);
fixup_dive(&d);

View File

@ -1,4 +1,5 @@
# Tool repo to crosscompile subsurface for iOS
Tool repo to crosscompile subsurface for iOS
--------------------------------------------
Dependencies:
@ -6,18 +7,20 @@ Dependencies:
- XCode with iOS SDK and Qt5.13 or later
- cmake
Follow [these instructions](/INSTALL.md#cross-building-subsurface-on-macosx-for-ios)
Follow the instruction in:
<repo>/INSTALL
and then continue here:
1. `cd <repo>/packaging/ios`
2. `export IOS_BUNDLE_PRODUCT_IDENTIFIER="<your apple id>.subsurface-divelog.subsurface-mobile"`
3. `./build.sh`
1) cd <repo>/packaging/ios
2) export IOS_BUNDLE_PRODUCT_IDENTIFIER="<your apple id>.subsurface-divelog.subsurface-mobile"
3) ./build.sh
note: this builds all dependencies and is only needed first time
it currently build for armv7 arm64 and x86_64 (simulator)
1. `cd <repo>/..`
2. Launch QtCreator and open `subsurface/packaging/ios/Subsurface-mobile.pro`
3. Build Subsurface-mobile in QtCreator - you can build for the simulator and for
1) cd <repo>/..
2) Launch QtCreator and open subsurface/packaging/ios/Subsurface-mobile.pro
3) Build Subsurface-mobile in QtCreator - you can build for the simulator and for
a device and even deploy to a connected device.
Everything up to here you can do without paying for an Apple Developer account.
@ -30,17 +33,17 @@ The easiest way to do that appears to be to open the Subsurface-mobile.xcodeproj
in the build directory that QtCreator used in Xcode and to create an archive there.
**WARNING:**
WARNING:
========
The version number used in the Subsurface-mobile app is created in step 3.
So whenever you pull the latest git or commit a change, you need to re-run the
`build.sh` script so that the `Info.plist` used by QtCreator (well, by Xcode under
build.sh script so that the Info.plist used by QtCreator (well, by Xcode under
the hood) gets updated. Otherwise you will continue to see the old version
number, even though the sources have been recompiled which can be very
confusing.
Do a simply version update by running:
```
build.sh -version
```
and then rebuilding in Qt Creator (or Xcode).
and then rebuilding in Qt Creator (or Xcode)

View File

@ -105,7 +105,7 @@ debuild -S -d
# create builds for the newer Ubuntu releases that Launchpad supports
#
rel=focal
others="jammy mantic noble"
others="jammy mantic"
for next in $others
do
sed -i "s/${rel}/${next}/g" debian/changelog

View File

@ -62,10 +62,10 @@ void DiveHandler::selfRemove()
void DiveHandler::changeGas()
{
ProfileWidget2 *view = qobject_cast<ProfileWidget2 *>(scene()->views().first());
QAction *action = qobject_cast<QAction *>(sender());
view->changeGas(parentIndex(), action->data().toInt());
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS);
plannerModel->gasChange(index.sibling(index.row() + 1, index.column()), action->data().toInt());
}
void DiveHandler::mouseMoveEvent(QGraphicsSceneMouseEvent *event)

View File

@ -333,7 +333,7 @@ void DiveHeartrateItem::createTextItem(int sec, int hr, bool last)
int flags = last ? Qt::AlignLeft | Qt::AlignBottom :
Qt::AlignRight | Qt::AlignBottom;
auto text = std::make_unique<DiveTextItem>(dpr, 0.7, flags, this);
text->set(QStringLiteral("%1").arg(hr), getColor(HR_TEXT));
text->set(QString("%1").arg(hr), getColor(HR_TEXT));
text->setPos(QPointF(hAxis.posAtValue(sec), vAxis.posAtValue(hr)));
texts.push_back(std::move(text));
}
@ -600,9 +600,7 @@ void DiveGasPressureItem::replot(const dive *d, int fromIn, int toIn, bool in_pl
}
}
bool showDescriptions = false;
for (int cyl = 0; cyl < pInfo.nr_cylinders; cyl++) {
showDescriptions = showDescriptions || same_gasmix_cylinder(get_cylinder(d, cyl), cyl, d, true) != -1;
if (act_segments[cyl].polygon.empty())
continue;
act_segments[cyl].cyl = cyl;
@ -620,47 +618,45 @@ void DiveGasPressureItem::replot(const dive *d, int fromIn, int toIn, bool in_pl
// Right now it's just strictly alternating when you have multiple gas
// pressures.
QFlags<Qt::AlignmentFlag> startAlignVar = Qt::AlignTop;
QFlags<Qt::AlignmentFlag> alignVar = Qt::AlignTop;
std::vector<QFlags<Qt::AlignmentFlag>> align(pInfo.nr_cylinders);
double labelHeight = DiveTextItem::fontHeight(dpr, 1.0);
for (const Segment &segment: segments) {
// Magic Y offset depending on whether we're aliging
// the top of the text or the bottom of the text to
// the pressure line.
double y_offset = -0.5 * dpr;
plotPressureValue(segment.first.pressure, segment.first.time, startAlignVar, y_offset);
double value_y_offset = -0.5 * dpr;
double label_y_offset = alignVar & Qt::AlignTop ? labelHeight : -labelHeight;
gasmix gas = get_cylinder(d, segment.cyl)->gasmix;
plotPressureValue(segment.first.pressure, segment.first.time, alignVar, value_y_offset);
plotGasValue(segment.first.pressure, segment.first.time, gas, alignVar, label_y_offset);
// For each cylinder, on right hand side of the curve, write cylinder pressure
double x_offset = plotPressureValue(segment.last.pressure, segment.last.time, Qt::AlignTop | Qt::AlignLeft, y_offset) + 2;
plotGasValue(segment.last.pressure, segment.last.time, get_cylinder(d, segment.cyl), Qt::AlignTop | Qt::AlignLeft, x_offset, y_offset, showDescriptions);
plotPressureValue(segment.last.pressure, segment.last.time, alignVar | Qt::AlignLeft, value_y_offset);
/* Alternate alignment as we see cylinder use.. */
startAlignVar ^= Qt::AlignTop | Qt::AlignBottom;
alignVar ^= Qt::AlignTop | Qt::AlignBottom;
}
}
double DiveGasPressureItem::plotPressureValue(double mbar, double sec, QFlags<Qt::AlignmentFlag> align, double y_offset)
void DiveGasPressureItem::plotPressureValue(double mbar, double sec, QFlags<Qt::AlignmentFlag> align, double pressure_offset)
{
const char *unit;
auto label = QStringLiteral("%1%2").arg(get_pressure_units(lrint(mbar), &unit)).arg(unit);
int pressure = get_pressure_units(lrint(mbar), &unit);
auto text = std::make_unique<DiveTextItem>(dpr, 1.0, align, this);
text->set(label, getColor(PRESSURE_TEXT));
text->setPos(hAxis.posAtValue(sec), vAxis.posAtValue(mbar) + y_offset);
text->set(QString("%1%2").arg(pressure).arg(unit), getColor(PRESSURE_TEXT));
text->setPos(hAxis.posAtValue(sec), vAxis.posAtValue(mbar) + pressure_offset);
texts.push_back(std::move(text));
return DiveTextItem::getLabelSize(dpr, 1.0, label).first;
}
void DiveGasPressureItem::plotGasValue(double mbar, double sec, const cylinder_t *cylinder, QFlags<Qt::AlignmentFlag> align, double x_offset, double y_offset, bool showDescription)
void DiveGasPressureItem::plotGasValue(double mbar, double sec, struct gasmix gasmix, QFlags<Qt::AlignmentFlag> align, double gasname_offset)
{
QString gas = get_gas_string(cylinder->gasmix);
QString label;
if (showDescription)
label = QStringLiteral("(%1) %2").arg(cylinder->type.description, gas);
else
label = gas;
QString gas = get_gas_string(gasmix);
auto text = std::make_unique<DiveTextItem>(dpr, 1.0, align, this);
text->set(label, getColor(PRESSURE_TEXT));
text->setPos(hAxis.posAtValue(sec) - x_offset, vAxis.posAtValue(mbar) + y_offset);
text->set(gas, getColor(PRESSURE_TEXT));
text->setPos(hAxis.posAtValue(sec), vAxis.posAtValue(mbar) + gasname_offset);
texts.push_back(std::move(text));
}

View File

@ -7,8 +7,6 @@
#include "divelineitem.h"
#include "core/equipment.h"
/* This is the Profile Item, it should be used for quite a lot of things
on the profile view. The usage should be pretty simple:
@ -107,8 +105,8 @@ public:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
private:
double plotPressureValue(double mbar, double sec, QFlags<Qt::AlignmentFlag> align, double y_offset);
void plotGasValue(double mbar, double sec, const cylinder_t *cylinder, QFlags<Qt::AlignmentFlag> align, double x_offset, double y_offset, bool showDescription);
void plotPressureValue(double mbar, double sec, QFlags<Qt::AlignmentFlag> align, double offset);
void plotGasValue(double mbar, double sec, struct gasmix gasmix, QFlags<Qt::AlignmentFlag> align, double offset);
struct PressureEntry {
double time = 0.0;
double pressure = 0.0;

View File

@ -8,7 +8,6 @@
#include "divetextitem.h"
#include "tankitem.h"
#include "core/device.h"
#include "core/divecomputer.h"
#include "core/event.h"
#include "core/pref.h"
#include "core/profile.h"
@ -580,9 +579,9 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
}
QString dcText = get_dc_nickname(currentdc);
if (is_dc_planner(currentdc))
if (dcText == "planned dive")
dcText = tr("Planned dive");
else if (is_dc_manually_added_dive(currentdc))
else if (dcText == "manually added dive")
dcText = tr("Manually added dive");
else if (dcText.isEmpty())
dcText = tr("Unknown dive computer");

View File

@ -404,7 +404,7 @@ void ProfileWidget2::clear()
#ifndef SUBSURFACE_MOBILE
clearPictures();
#endif
disconnectPlannerModel();
disconnectTemporaryConnections();
profileScene->clear();
handles.clear();
gases.clear();
@ -426,7 +426,7 @@ void ProfileWidget2::setProfileState()
if (currentState == PROFILE)
return;
disconnectPlannerModel();
disconnectTemporaryConnections();
currentState = PROFILE;
setBackgroundBrush(getColor(::BACKGROUND, profileScene->isGrayscale));
@ -444,6 +444,16 @@ void ProfileWidget2::setProfileState()
}
#ifndef SUBSURFACE_MOBILE
void ProfileWidget2::connectPlannerModel()
{
connect(plannerModel, &DivePlannerPointsModel::dataChanged, this, &ProfileWidget2::replot);
connect(plannerModel, &DivePlannerPointsModel::cylinderModelEdited, this, &ProfileWidget2::replot);
connect(plannerModel, &DivePlannerPointsModel::modelReset, this, &ProfileWidget2::pointsReset);
connect(plannerModel, &DivePlannerPointsModel::rowsInserted, this, &ProfileWidget2::pointInserted);
connect(plannerModel, &DivePlannerPointsModel::rowsRemoved, this, &ProfileWidget2::pointsRemoved);
connect(plannerModel, &DivePlannerPointsModel::rowsMoved, this, &ProfileWidget2::pointsMoved);
}
void ProfileWidget2::setEditState(const dive *d, int dc)
{
if (currentState == EDIT)
@ -452,6 +462,7 @@ void ProfileWidget2::setEditState(const dive *d, int dc)
setProfileState(d, dc);
mouseFollowerHorizontal->setVisible(true);
mouseFollowerVertical->setVisible(true);
disconnectTemporaryConnections();
connectPlannerModel();
@ -469,6 +480,7 @@ void ProfileWidget2::setPlanState(const dive *d, int dc)
setProfileState(d, dc);
mouseFollowerHorizontal->setVisible(true);
mouseFollowerVertical->setVisible(true);
disconnectTemporaryConnections();
connectPlannerModel();
@ -565,7 +577,7 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
for (int i = 0; i < d->cylinders.nr; i++) {
const cylinder_t *cylinder = get_cylinder(d, i);
QString label = printCylinderDescription(i, cylinder);
gasChange->addAction(label, [this, i, eventTime] { addGasSwitch(i, eventTime); });
gasChange->addAction(label, [this, i, eventTime] { changeGas(i, eventTime); });
}
} else if (d && d->cylinders.nr > 1) {
// if we have more than one gas, offer to switch to another one
@ -573,7 +585,7 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
for (int i = 0; i < d->cylinders.nr; i++) {
const cylinder_t *cylinder = get_cylinder(d, i);
QString label = printCylinderDescription(i, cylinder);
gasChange->addAction(label, [this, i, seconds] { addGasSwitch(i, seconds); });
gasChange->addAction(label, [this, i, seconds] { changeGas(i, seconds); });
}
}
m.addAction(tr("Add setpoint change"), [this, seconds]() { ProfileWidget2::addSetpointChange(seconds); });
@ -759,25 +771,16 @@ void ProfileWidget2::splitDive(int seconds)
Command::splitDives(mutable_dive(), duration_t{ seconds });
}
void ProfileWidget2::addGasSwitch(int tank, int seconds)
void ProfileWidget2::changeGas(int tank, int seconds)
{
if (!d || tank < 0 || tank >= d->cylinders.nr)
return;
Command::addGasSwitch(mutable_dive(), dc, seconds, tank);
}
#endif
void ProfileWidget2::changeGas(int index, int newCylinderId)
{
if ((currentState == PLAN || currentState == EDIT) && plannerModel) {
QModelIndex modelIndex = plannerModel->index(index, DivePlannerPointsModel::GAS);
plannerModel->gasChange(modelIndex.sibling(modelIndex.row() + 1, modelIndex.column()), newCylinderId);
if (currentState == EDIT)
emit stopEdited();
}
}
#ifndef SUBSURFACE_MOBILE
void ProfileWidget2::editName(DiveEventItem *item)
{
struct event *event = item->getEventMutable();
@ -797,19 +800,9 @@ void ProfileWidget2::editName(DiveEventItem *item)
Command::renameEvent(mutable_dive(), dc, event, qPrintable(newName));
}
}
void ProfileWidget2::connectPlannerModel()
{
connect(plannerModel, &DivePlannerPointsModel::dataChanged, this, &ProfileWidget2::replot);
connect(plannerModel, &DivePlannerPointsModel::cylinderModelEdited, this, &ProfileWidget2::replot);
connect(plannerModel, &DivePlannerPointsModel::modelReset, this, &ProfileWidget2::pointsReset);
connect(plannerModel, &DivePlannerPointsModel::rowsInserted, this, &ProfileWidget2::pointInserted);
connect(plannerModel, &DivePlannerPointsModel::rowsRemoved, this, &ProfileWidget2::pointsRemoved);
connect(plannerModel, &DivePlannerPointsModel::rowsMoved, this, &ProfileWidget2::pointsMoved);
}
#endif
void ProfileWidget2::disconnectPlannerModel()
void ProfileWidget2::disconnectTemporaryConnections()
{
#ifndef SUBSURFACE_MOBILE
if (plannerModel) {

View File

@ -68,7 +68,6 @@ signals:
void stopAdded(); // only emitted in edit mode
void stopRemoved(int count); // only emitted in edit mode
void stopMoved(int count); // only emitted in edit mode
void stopEdited(); // only emitted in edit mode
public
slots: // Necessary to call from QAction's signals.
@ -112,11 +111,11 @@ private:
void replot();
void setZoom(int level);
void addGasSwitch(int tank, int seconds);
void changeGas(int index, int newCylinderId);
void changeGas(int tank, int seconds);
void setupSceneAndFlags();
void addItemsToScene();
void setupItemOnScene();
void disconnectTemporaryConnections();
struct plot_data *getEntryFromPos(QPointF pos);
void clearPictures();
void plotPicturesInternal(const struct dive *d, bool synchronous);
@ -185,7 +184,6 @@ private:
std::vector<std::unique_ptr<DiveHandler>> handles;
int handleIndex(const DiveHandler *h) const;
void disconnectPlannerModel();
#ifndef SUBSURFACE_MOBILE
void connectPlannerModel();
void repositionDiveHandlers();

View File

@ -499,7 +499,7 @@ void CylindersModel::add()
if (!d)
return;
int row = d->cylinders.nr;
cylinder_t cyl = create_new_manual_cylinder(d);
cylinder_t cyl = create_new_cylinder(d);
beginInsertRows(QModelIndex(), row, row);
add_cylinder(&d->cylinders, row, cyl);
++numRows;

View File

@ -69,7 +69,7 @@ void DivePlannerPointsModel::createSimpleDive(struct dive *dIn)
clear_dive(d);
d->id = dive_getUniqID();
d->when = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset() + 3600;
make_planner_dc(&d->dc);
d->dc.model = strdup("planned dive"); // don't translate! this is stored in the XML file
clear();
removeDeco();
@ -206,8 +206,20 @@ void DivePlannerPointsModel::setupCylinders()
return; // We have at least one cylinder
}
}
add_default_cylinder(d);
if (!empty_string(prefs.default_cylinder)) {
cylinder_t cyl = empty_cylinder;
fill_default_cylinder(d, &cyl);
cyl.start = cyl.type.workingpressure;
add_cylinder(&d->cylinders, 0, cyl);
} else {
cylinder_t cyl = empty_cylinder;
// roughly an AL80
cyl.type.description = copy_qstring(tr("unknown"));
cyl.type.size.mliter = 11100;
cyl.type.workingpressure.mbar = 207000;
add_cylinder(&d->cylinders, 0, cyl);
}
reset_cylinders(d, false);
cylinders.updateDive(d, dcNr);
}
@ -1276,7 +1288,7 @@ void DivePlannerPointsModel::computeVariationsDone(QString variations)
emit calculatedPlanNotes(QString(d->notes));
}
void DivePlannerPointsModel::createPlan(bool saveAsNew)
void DivePlannerPointsModel::createPlan(bool replanCopy)
{
// Ok, so, here the diveplan creates a dive
deco_state_cache cache;
@ -1338,7 +1350,7 @@ void DivePlannerPointsModel::createPlan(bool saveAsNew)
#endif // !SUBSURFACE_TESTING
} else {
copy_events_until(current_dive, d, dcNr, preserved_until.seconds);
if (saveAsNew) {
if (replanCopy) {
// we were planning an old dive and save as a new dive
d->id = dive_getUniqID(); // Things will break horribly if we create dives with the same id.
#if !defined(SUBSURFACE_TESTING)

View File

@ -23,12 +23,12 @@ public:
GAS,
CCSETPOINT,
DIVEMODE,
COLUMNS,
COLUMNS
};
enum Mode {
NOTHING,
PLAN,
EDIT,
ADD
};
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

View File

@ -389,6 +389,10 @@ QVariant DiveTripModelBase::diveData(const struct dive *d, int column, int role)
return d->rating;
case DIVE_ROLE:
return QVariant::fromValue(const_cast<dive *>(d)); // Not nice: casting away a const
case DIVE_IDX:
return get_divenr(d);
case SELECTED_ROLE:
return d->selected;
case CURRENT_ROLE:
return d == current_dive;
}

View File

@ -53,6 +53,8 @@ public:
IS_TRIP_ROLE,
DIVE_ROLE,
TRIP_ROLE,
DIVE_IDX,
SELECTED_ROLE,
CURRENT_ROLE,
TRIP_HAS_CURRENT_ROLE, // Returns true if this is a trip and it contains the current dive
LAST_ROLE

View File

@ -13,15 +13,17 @@ QVariant TankInfoModel::data(const QModelIndex &index, int role) const
return defaultModelFont();
if (role == Qt::DisplayRole || role == Qt::EditRole) {
const struct tank_info &info = tank_info_table.infos[index.row()];
volume_t size = {0};
pressure_t pressure = {0};
extract_tank_info(&info, &size, &pressure);
int ml = info.ml;
double bar = (info.psi) ? psi_to_bar(info.psi) : info.bar;
if (info.cuft && info.psi)
ml = lrint(cuft_to_l(info.cuft) * 1000 / bar_to_atm(bar));
switch (index.column()) {
case BAR:
return pressure.mbar;
return bar * 1000;
case ML:
return size.mliter;
return ml;
case DESCRIPTION:
return info.name;
}

View File

@ -577,9 +577,9 @@ if [ "$QUICK" != "1" ] && [ "$BUILD_DESKTOP$BUILD_MOBILE" != "" ] && ( [[ $QT_VE
# since we are currently building QtLocation from source, we don't have a way to easily install
# the private headers... so this is a bit of a hack to get those for googlemaps...
# regardless of whether we do a fat build or not, let's do the 'native' build here
$QMAKE "INCLUDEPATH=$INSTALL_ROOT/../qtlocation/build/include/QtLocation/6.3.0" "CONFIG+=release" QMAKE_APPLE_DEVICE_ARCHS="$(uname -m)" ../googlemaps.pro
$QMAKE "INCLUDEPATH=$INSTALL_ROOT/../qtlocation/build/include/QtLocation/6.3.0" QMAKE_APPLE_DEVICE_ARCHS="$(uname -m)" ../googlemaps.pro
else
$QMAKE "INCLUDEPATH=$INSTALL_ROOT/include" "CONFIG+=release" ../googlemaps.pro
$QMAKE "INCLUDEPATH=$INSTALL_ROOT/include" ../googlemaps.pro
fi
make -j4
if [ "$PLATFORM" = Darwin ] && [[ $QT_VERSION == 6* ]] && [[ $ARCHS == *" "* ]] ; then

View File

@ -819,12 +819,12 @@ static dc_descriptor_t *get_data_descriptor(int data_model, dc_family_t data_fam
* DC. dc_family_t is certainly known *only* if it is Aladin/Memomouse family
* otherwise it will be known after get_data_descriptor call.
*/
static dc_status_t prepare_data(int data_model, const char *serial, dc_family_t dc_fam, device_data_t *dev_data)
static dc_status_t prepare_data(int data_model, char *serial, dc_family_t dc_fam, device_data_t *dev_data)
{
dev_data->device = NULL;
dev_data->context = NULL;
if (!data_model) {
dev_data->model = copy_string(manual_dc_name);
dev_data->model = copy_string("manually added dive");
dev_data->descriptor = NULL;
return DC_STATUS_NODEVICE;
}
@ -952,7 +952,7 @@ extern "C" void smartrak_import(const char *file, struct divelog *log)
if (hdr_length > 0 && hdr_length < 20) // We have a profile but it's imported from datatrak
dc_fam = DC_FAMILY_UWATEC_ALADIN;
}
rc = prepare_data(dc_model, (char *)col[coln(DCNUMBER)]->bind_ptr, dc_fam, devdata);
rc = prepare_data(dc_model, copy_string((char *)col[coln(DCNUMBER)]->bind_ptr), dc_fam, devdata);
smtkdive->dc.model = copy_string(devdata->model);
if (rc == DC_STATUS_SUCCESS && mdb_table.get_len(coln(PROFILE))) {
prf_buffer = static_cast<unsigned char *>(mdb_ole_read_full(mdb, col[coln(PROFILE)], &prf_length));

View File

@ -43,7 +43,7 @@ int main(int argc, char **argv)
QStringList arguments = QCoreApplication::arguments();
// set a default logfile name for libdivecomputer so we always get a logfile
logfile_name = "subsurface-downloader.log";
logfile_name = strdup("subsurface-downloader.log");
const char *default_directory = system_default_directory();
subsurface_mkdir(default_directory);

View File

@ -52,12 +52,9 @@ void setupPlan(struct diveplan *dp)
struct gasmix ean36 = {{360}, {0}};
struct gasmix oxygen = {{1000}, {0}};
pressure_t po2 = {1600};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cyl0->gasmix = bottomgas;
cyl0->type.size.mliter = 36000;
cyl0->type.workingpressure.mbar = 232000;
@ -86,12 +83,9 @@ void setupPlanVpmb45m30mTx(struct diveplan *dp)
struct gasmix ean50 = {{500}, {0}};
struct gasmix oxygen = {{1000}, {0}};
pressure_t po2 = {1600};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cyl0->gasmix = bottomgas;
cyl0->type.size.mliter = 24000;
cyl0->type.workingpressure.mbar = 232000;
@ -120,12 +114,9 @@ void setupPlanVpmb60m10mTx(struct diveplan *dp)
struct gasmix tx50_15 = {{500}, {150}};
struct gasmix oxygen = {{1000}, {0}};
pressure_t po2 = {1600};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cyl0->gasmix = bottomgas;
cyl0->type.size.mliter = 24000;
cyl0->type.workingpressure.mbar = 232000;
@ -172,11 +163,8 @@ void setupPlanVpmb60m30minEan50(struct diveplan *dp)
struct gasmix bottomgas = {{210}, {0}};
struct gasmix ean50 = {{500}, {0}};
pressure_t po2 = {1600};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cyl0->gasmix = bottomgas;
cyl0->type.size.mliter = 36000;
cyl0->type.workingpressure.mbar = 232000;
@ -201,11 +189,8 @@ void setupPlanVpmb60m30minTx(struct diveplan *dp)
struct gasmix bottomgas = {{180}, {450}};
struct gasmix ean50 = {{500}, {0}};
pressure_t po2 = {1600};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cyl0->gasmix = bottomgas;
cyl0->type.size.mliter = 36000;
cyl0->type.workingpressure.mbar = 232000;
@ -254,12 +239,9 @@ void setupPlanVpmb100m60min(struct diveplan *dp)
struct gasmix ean50 = {{500}, {0}};
struct gasmix oxygen = {{1000}, {0}};
pressure_t po2 = {1600};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cyl0->gasmix = bottomgas;
cyl0->type.size.mliter = 200000;
cyl0->type.workingpressure.mbar = 232000;
@ -287,12 +269,9 @@ void setupPlanVpmb100m10min(struct diveplan *dp)
struct gasmix ean50 = {{500}, {0}};
struct gasmix oxygen = {{1000}, {0}};
pressure_t po2 = {1600};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cyl0->gasmix = bottomgas;
cyl0->type.size.mliter = 60000;
cyl0->type.workingpressure.mbar = 232000;
@ -342,13 +321,10 @@ void setupPlanVpmb100mTo70m30min(struct diveplan *dp)
struct gasmix ean50 = {{500}, {0}};
struct gasmix oxygen = {{1000}, {0}};
pressure_t po2 = {1600};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl3 = get_or_create_cylinder(&dive, 3);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cylinder_t *cyl3 = get_or_create_cylinder(&dive, 3);
cyl0->gasmix = bottomgas;
cyl0->type.size.mliter = 36000;
cyl0->type.workingpressure.mbar = 232000;
@ -381,11 +357,8 @@ void setupPlanSeveralGases(struct diveplan *dp)
struct gasmix ean36 = {{360}, {0}};
struct gasmix tx11_50 = {{110}, {500}};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cyl0->gasmix = ean36;
cyl0->type.size.mliter = 36000;
cyl0->type.workingpressure.mbar = 232000;
@ -413,12 +386,9 @@ void setupPlanCcr(struct diveplan *dp)
struct gasmix diluent = {{200}, {210}};
struct gasmix ean53 = {{530}, {0}};
struct gasmix tx19_33 = {{190}, {330}};
// Note: we add the highest-index cylinder first, because
// pointers to cylinders are not stable when reallocating.
// For testing OK - don't do this in actual code!
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0);
cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1);
cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2);
cyl0->gasmix = diluent;
cyl0->depth = gas_mod(diluent, po2, &dive, M_OR_FT(3, 10));
cyl0->type.size.mliter = 3000;

View File

@ -18,8 +18,6 @@
void TestProfile::init()
{
setlocale(LC_ALL, "C");
// Set UTF8 text codec as in real applications
QTextCodec::setCodecForLocale(QTextCodec::codecForMib(106));
// first, setup the preferences