Compare commits
273 commits
38a0050ac3
...
3bd7be809a
Author | SHA1 | Date | |
---|---|---|---|
|
3bd7be809a | ||
|
b392052c37 | ||
|
c72a26afe1 | ||
|
f81cf77886 | ||
|
821f3fc551 | ||
|
b1493c3540 | ||
|
8a64d1f4b9 | ||
|
784eddc166 | ||
|
5613014124 | ||
|
b5e5e4c212 | ||
|
968599c7d9 | ||
|
6418d3acbc | ||
|
9873c86192 | ||
|
b806c5371f | ||
|
89b2b3bf70 | ||
|
716b350af2 | ||
|
478e444cd9 | ||
|
28cd093d37 | ||
|
40c22f0fe9 | ||
|
ffa49cfd34 | ||
|
435d5f5436 | ||
|
8e48a323e7 | ||
|
6537192e1d | ||
|
3d552b84d1 | ||
|
236203bf06 | ||
|
09f59211ed | ||
|
c8ef53c43f | ||
|
68f8ca5fd0 | ||
|
c866d2bead | ||
|
b92475d9c9 | ||
|
d9f8570728 | ||
|
13d1188c41 | ||
|
02638d7c3e | ||
|
a6a15f9d3a | ||
|
cb6766c1d4 | ||
|
a2f5be13e3 | ||
|
1a3bc9bf71 | ||
|
dd5def35f5 | ||
|
ae81b42fe2 | ||
|
f09601bc93 | ||
|
77b12bbccf | ||
|
110e64bc66 | ||
|
729cc16fc5 | ||
|
12ca172a9e | ||
|
696ba61eef | ||
|
6f91a73a05 | ||
|
db531bbd05 | ||
|
80abde2a61 | ||
|
1287880be0 | ||
|
22b232661a | ||
|
3e006e678a | ||
|
0745c50e58 | ||
|
74c8bd34a0 | ||
|
8704a8b6f9 | ||
|
6c8f158569 | ||
|
7d215deaa1 | ||
|
5700379560 | ||
|
a5effbe0a6 | ||
|
96fdaf660b | ||
|
515f593a56 | ||
|
f585726283 | ||
|
ee25e8a1db | ||
|
33bb39f1ca | ||
|
cc55c442a3 | ||
|
27a89b0232 | ||
|
5b46d8cc33 | ||
|
db516b6d4e | ||
|
38fe08e5e1 | ||
|
aa3a93a466 | ||
|
da6c753502 | ||
|
914cdb102b | ||
|
069f8a5d18 | ||
|
3c3856f9e8 | ||
|
14b9074f40 | ||
|
2d8e343221 | ||
|
7106c4d5f0 | ||
|
8b435c9d8f | ||
|
a6ce6e56d3 | ||
|
26c594382e | ||
|
0dc47882cb | ||
|
bdfd37c95b | ||
|
01705a6449 | ||
|
d295ca1d17 | ||
|
2d5094a48b | ||
|
92abfb4b90 | ||
|
1578c7be99 | ||
|
f78662acce | ||
|
a3340298b6 | ||
|
ef9ae5f6d6 | ||
|
e305546046 | ||
|
5c6ca2c1ce | ||
|
1dade48aa6 | ||
|
455bc8f40c | ||
|
6d08903917 | ||
|
2bbc95e8f1 | ||
|
c91884223f | ||
|
ad2ccc8888 | ||
|
1fc5a294a6 | ||
|
df568fbb5d | ||
|
152e6966c9 | ||
|
48b4308a7d | ||
|
a75c9c3872 | ||
|
a905a78178 | ||
|
94ed723015 | ||
|
67f38ce3ce | ||
|
80b5f6bfcd | ||
|
22a1120b30 | ||
|
9c726d8d6f | ||
|
4cb3db2548 | ||
|
b5a4e7eb0b | ||
|
650fda3221 | ||
|
498302dcc6 | ||
|
9bb2255ba8 | ||
|
777e7f32a5 | ||
|
fb3a157462 | ||
|
28814829e0 | ||
|
6e349793d1 | ||
|
76d672210d | ||
|
f1f082d86a | ||
|
731052c776 | ||
|
718523e01d | ||
|
0aa4efb3d9 | ||
|
c812dd140b | ||
|
6e29c00f35 | ||
|
e90251b0cf | ||
|
286d8fe21c | ||
|
d36fd79527 | ||
|
df1affc25b | ||
|
79bf79ad7f | ||
|
576d3a3bc6 | ||
|
60c7b503cf | ||
|
72bb38601d | ||
|
1b593dc56c | ||
|
3aab33ba4c | ||
|
4d7291d4a1 | ||
|
8ec1f008ab | ||
|
a2903b31a7 | ||
|
d81ca005ab | ||
|
4a165980e7 | ||
|
bdd5527005 | ||
|
bf84d66df2 | ||
|
3660241993 | ||
|
176f544106 | ||
|
b34116e2e2 | ||
|
f3b8e3c4aa | ||
|
271df8962b | ||
|
cce5f5950c | ||
|
e9a57ac5f5 | ||
|
4afefb1b9b | ||
|
124362caa5 | ||
|
541abf7ae4 | ||
|
d594cc72f0 | ||
|
ccdd92aeb7 | ||
|
82fc9de40b | ||
|
5805b14734 | ||
|
ec92cff92c | ||
|
6c7942ac1c | ||
|
6252d22adf | ||
|
d05e289507 | ||
|
71f3189a31 | ||
|
0c7c96402c | ||
|
7452aa22c2 | ||
|
91968ac579 | ||
|
2bdcdab391 | ||
|
5af9d28291 | ||
|
7792f54a73 | ||
|
67d0f69516 | ||
|
b95ac3f79c | ||
|
f00c30ad4a | ||
|
a1e6df46d9 | ||
|
b9df26066e | ||
|
aa60e5d21d | ||
|
1e09ec77d7 | ||
|
0ef497c3c9 | ||
|
c9d4ce0c15 | ||
|
5c7cfb1057 | ||
|
71518fa77e | ||
|
eacad89531 | ||
|
1cebafb08f | ||
|
3ee41328f9 | ||
|
2fd226964c | ||
|
73f2605ab1 | ||
|
9e3e0a5a05 | ||
|
3cb04d230b | ||
|
2b3d2f1020 | ||
|
f18acf6fb9 | ||
|
640ecb345b | ||
|
28520da655 | ||
|
284582d2e8 | ||
|
e237f29fb2 | ||
|
c27314d603 | ||
|
af6201f89c | ||
|
27dbdd35c6 | ||
|
8ddc960fa0 | ||
|
f120fecccb | ||
|
bc761344d4 | ||
|
b9a2eff3c9 | ||
|
cc39f709ce | ||
|
bfb54aa581 | ||
|
eac11683a9 | ||
|
37be879e17 | ||
|
058485e374 | ||
|
65dcb98e41 | ||
|
d242198c99 | ||
|
6e352d5281 | ||
|
90d5bab4e9 | ||
|
858a0aecba | ||
|
512eada468 | ||
|
4ac2486a23 | ||
|
7d7766be9a | ||
|
2de6f69c19 | ||
|
76c52c87a3 | ||
|
6b835710bc | ||
|
e39dea3d68 | ||
|
411188728d | ||
|
ead58cd039 | ||
|
db4b972897 | ||
|
2df30a4144 | ||
|
4d183637d0 | ||
|
5960fb7340 | ||
|
b8c7b173c6 | ||
|
408b31b6ce | ||
|
b82fdd1d20 | ||
|
b56dd13add | ||
|
03b910ee7f | ||
|
9065bf8622 | ||
|
7d3977481a | ||
|
3916125786 | ||
|
177246b419 | ||
|
801b5d50b2 | ||
|
3f8b4604be | ||
|
3c1401785b | ||
|
1af00703b3 | ||
|
c5f96d877d | ||
|
19148d30e7 | ||
|
48f7828d10 | ||
|
aaab5157d4 | ||
|
e537904965 | ||
|
3395c61bc8 | ||
|
7c2b580bfa | ||
|
e29a0c1b29 | ||
|
01306224ff | ||
|
0915c1ce43 | ||
|
b74703b61d | ||
|
fe0bb905f3 | ||
|
6cda13a9fe | ||
|
1daa4f0584 | ||
|
84219641de | ||
|
edc0c69943 | ||
|
a244e71256 | ||
|
7b79bc954b | ||
|
a6815661d5 | ||
|
52fb77da69 | ||
|
018884dfde | ||
|
6d8a904981 | ||
|
f3a36b62ac | ||
|
5554acb2c5 | ||
|
ad33d498c7 | ||
|
9e7b98024e | ||
|
58b3583b3b | ||
|
16e19b550b | ||
|
ad3be20c9f | ||
|
bf92407a4a | ||
|
b28d6cf0fc | ||
|
cdc87618da | ||
|
21f68387ae | ||
|
729356e0b1 | ||
|
742193a5e3 | ||
|
b24f37fb4f | ||
|
0b817e468a | ||
|
e3f6496f59 | ||
|
092035d883 | ||
|
628e2fe933 |
|
@ -10,7 +10,7 @@ ColumnLimit: 0
|
|||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
ForEachMacros: [ 'for_each_dc', 'for_each_relevant_dc', 'for_each_dive', 'for_each_line' ]
|
||||
ForEachMacros: [ 'for_each_line' ]
|
||||
IndentFunctionDeclarationAfterType: false #personal taste, good for long methods
|
||||
IndentWidth: 8
|
||||
MaxEmptyLinesToKeep: 2
|
||||
|
|
19
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
# Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.)
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/android-mobile"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directories:
|
||||
- "/scripts/docker/android-build-container"
|
||||
- "/scripts/docker/mxe-build-container"
|
||||
schedule:
|
||||
interval: "weekly"
|
2
.github/workflows/android-dockerimage.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
android-build-container:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERSION: ${{ '5.15.2' }} # the version numbers here is based on the Qt version, the third digit is the rev of the docker image
|
||||
VERSION: ${{ '5.15.3' }} # the version numbers here is based on the Qt version, the third digit is the rev of the docker image
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
2
.github/workflows/android.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
KEYSTORE_FILE: ${{ github.workspace }}/../subsurface.keystore
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://subsurface/android-build:5.15.2
|
||||
image: docker://subsurface/android-build:5.15.3
|
||||
|
||||
steps:
|
||||
- name: checkout sources
|
||||
|
|
2
.github/workflows/codeql-analysis.yml
vendored
|
@ -46,7 +46,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
|
||||
qtquickcontrols2-5-dev xvfb libbluetooth-dev libmtp-dev libraw-dev
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
|
2
.github/workflows/coverity-scan.yml
vendored
|
@ -25,7 +25,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 libbluetooth-dev libmtp-dev
|
||||
qtquickcontrols2-5-dev libbluetooth-dev libmtp-dev libraw-dev
|
||||
|
||||
- name: checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
|
3
.github/workflows/linux-debian-generic.yml
vendored
|
@ -30,7 +30,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 \
|
||||
qtquickcontrols2-5-dev xvfb libbluetooth-dev libmtp-dev libraw-dev \
|
||||
mdbtools-dev
|
||||
|
||||
git config --global user.email "ci@subsurface-divelog.org"
|
||||
|
@ -95,6 +95,5 @@ jobs:
|
|||
echo "--------------------------------------------------------------"
|
||||
echo "building smtk2ssrf"
|
||||
|
||||
# build smtk2ssrf (needs the artefacts generated by the subsurface build
|
||||
cd ..
|
||||
bash -e -x subsurface/scripts/smtk2ssrf-build.sh -y
|
||||
|
|
2
.github/workflows/linux-fedora-35-qt6.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
|||
qt6-qtlocation-devel qt6-qtsvg-devel \
|
||||
qt6-qttools-devel redhat-rpm-config \
|
||||
libxkbcommon-devel qt6-qt5compat-devel \
|
||||
bluez-libs-devel libgit2-devel libzip-devel libmtp-devel \
|
||||
bluez-libs-devel libgit2-devel libzip-devel libmtp-devel LibRaw-devel \
|
||||
xorg-x11-server-Xvfb
|
||||
|
||||
- name: checkout sources
|
||||
|
|
2
.github/workflows/linux-snap.yml
vendored
|
@ -55,7 +55,7 @@ jobs:
|
|||
echo "key=$( git merge-base origin/master $GITHUB_SHA )" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: CCache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ccache-${{ runner.os }}-${{ steps.setup-ccache.outputs.key }}
|
||||
restore-keys: |
|
||||
|
|
|
@ -35,7 +35,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 \
|
||||
qtquickcontrols2-5-dev xvfb libbluetooth-dev libmtp-dev libraw-dev \
|
||||
mdbtools-dev curl
|
||||
|
||||
git config --global user.email "ci@subsurface-divelog.org"
|
||||
|
|
4
.github/workflows/mac.yml
vendored
|
@ -24,7 +24,7 @@ jobs:
|
|||
submodules: recursive
|
||||
|
||||
- name: setup Homebrew
|
||||
run: brew install hidapi libxslt libjpg libmtp create-dmg confuse automake
|
||||
run: brew install hidapi libxslt libjpg libmtp libraw create-dmg confuse automake
|
||||
|
||||
- name: checkout Qt resources
|
||||
uses: actions/checkout@v4
|
||||
|
@ -45,7 +45,7 @@ jobs:
|
|||
CANONICALVERSION: ${{ steps.version_number.outputs.version }}
|
||||
run: |
|
||||
cd ${GITHUB_WORKSPACE}/..
|
||||
export QT_ROOT=${GITHUB_WORKSPACE}/qt-mac/Qt5.15.13
|
||||
export QT_ROOT=${GITHUB_WORKSPACE}/qt-mac/Qt5.15.15
|
||||
export QT_QPA_PLATFORM_PLUGIN_PATH=$QT_ROOT/plugins
|
||||
export PATH=$QT_ROOT/bin:$PATH
|
||||
export CMAKE_PREFIX_PATH=$QT_ROOT/lib/cmake
|
||||
|
|
6
.github/workflows/snap_usns.yml
vendored
|
@ -14,12 +14,12 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install Python dependencies
|
||||
uses: insightsengineering/pip-action@v2.0.0
|
||||
uses: insightsengineering/pip-action@v2.0.1
|
||||
with:
|
||||
requirements: .github/workflows/scripts/requirements.txt
|
||||
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
sudo snap install review-tools --edge
|
||||
|
||||
- name: Set up Launchpad credentials
|
||||
uses: DamianReeves/write-file-action@v1.2
|
||||
uses: DamianReeves/write-file-action@v1.3
|
||||
with:
|
||||
path: lp_credentials
|
||||
contents: ${{ secrets.LAUNCHPAD_CREDENTIALS }}
|
||||
|
|
|
@ -12,8 +12,8 @@ jobs:
|
|||
windows-mxe:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERSION: ${{ '3.1.0' }} # 'official' images should have a dot-zero version
|
||||
mxe_sha: 'c0bfefc57a00fdf6cb5278263e21a478e47b0bf5'
|
||||
VERSION: ${{ '3.2.0' }} # 'official' images should have a dot-zero version
|
||||
mxe_sha: '974808c2ecb02866764d236fe533ae57ba342e7a'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
2
.github/workflows/windows.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://subsurface/mxe-build:3.1.0
|
||||
image: docker://subsurface/mxe-build:3.2.0
|
||||
|
||||
steps:
|
||||
- name: checkout sources
|
||||
|
|
3
.gitignore
vendored
|
@ -17,6 +17,9 @@ Documentation/docbook-xsl.css
|
|||
Documentation/user-manual*.html
|
||||
Documentation/user-manual*.pdf
|
||||
Documentation/user-manual*.text
|
||||
Documentation/mobile-manual*.html
|
||||
Documentation/mobile-manual*.pdf
|
||||
Documentation/mobile-manual*.text
|
||||
Documentation/mobile-images/mobile-images
|
||||
packaging/windows/subsurface.nsi
|
||||
packaging/macos/Info.plist
|
||||
|
|
|
@ -8,6 +8,7 @@ desktop: fix gas switches in UDDF exports
|
|||
core: allow of up to 6 O2 sensors (and corresponding voting logic)
|
||||
desktop: add divemode as a possible dive list column
|
||||
profile-widget: Now zomed in profiles can be panned with horizontal scroll.
|
||||
media: support raw files if libraw is installed
|
||||
desktop: hide only events with the same severity when 'Hide similar events' is used
|
||||
equipment: mark gas mixes reported by the dive computer as 'inactive' as 'not used'
|
||||
equipment: include unused cylinders in merged dive if the preference is enabled
|
||||
|
|
|
@ -52,6 +52,7 @@ option(NO_USERMANUAL "don't include a viewer for the user manual" OFF)
|
|||
#Options regarding enabling parts of subsurface
|
||||
option(BTSUPPORT "enable support for QtBluetooth" ON)
|
||||
option(FTDISUPPORT "enable support for libftdi based serial" OFF)
|
||||
option(LIBRAW_SUPPORT "enable support for LibRaw images" ON)
|
||||
|
||||
# Options regarding What should we build on subsurface
|
||||
option(MAKE_TESTS "Make the tests" ON)
|
||||
|
@ -166,6 +167,7 @@ if(NOT ANDROID)
|
|||
endif()
|
||||
pkg_config_library(LIBUSB libusb-1.0 QUIET)
|
||||
pkg_config_library(LIBMTP libmtp QUIET)
|
||||
pkg_config_library(LIBRAW libraw )
|
||||
endif()
|
||||
|
||||
include_directories(.
|
||||
|
@ -295,6 +297,7 @@ elseif (SUBSURFACE_TARGET_EXECUTABLE MATCHES "DownloaderExecutable")
|
|||
set(SUBSURFACE_TARGET subsurface-downloader)
|
||||
endif()
|
||||
set(BTSUPPORT ON)
|
||||
set(LIBRAW_SUPPORT OFF)
|
||||
add_definitions(-DSUBSURFACE_DOWNLOADER)
|
||||
message(STATUS "building the embedded Subsurface-downloader app")
|
||||
endif()
|
||||
|
@ -358,6 +361,14 @@ if(BTSUPPORT)
|
|||
add_definitions(-DBLE_SUPPORT)
|
||||
endif()
|
||||
|
||||
if (LIBRAW_SUPPORT)
|
||||
if(LIBRAW_FOUND)
|
||||
add_definitions(-DLIBRAW_SUPPORT)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "building without built-in libraw support")
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
# when building for Android, the toolchain file requires all cmake modules
|
||||
# to be inside the CMAKE_FIND_ROOT_PATH - which prevents cmake from finding
|
||||
|
|
172
CODINGSTYLE.md
|
@ -14,7 +14,7 @@ At the end of this file are some ideas for your `.emacs` file (if that's
|
|||
your editor of choice) as well as for QtCreator. If you have settings for
|
||||
other editors that implement this coding style, please add them here.
|
||||
|
||||
## Basic rules
|
||||
## Basic style rules
|
||||
|
||||
* all indentation is tabs (set to 8 char) with the exception of
|
||||
continuation lines that are aligned with tabs and then spaces
|
||||
|
@ -87,17 +87,17 @@ other editors that implement this coding style, please add them here.
|
|||
|
||||
* unfortunate inconsistency
|
||||
|
||||
- C code usually uses underscores to structure names
|
||||
- Core code usually uses underscores to structure names
|
||||
```
|
||||
variable_in_C
|
||||
variable_or_class_in_core
|
||||
```
|
||||
- In contrast, C++ code usually uses camelCase
|
||||
- In contrast, Qt / display layer code usually uses camelCase
|
||||
```
|
||||
variableInCPlusPlus
|
||||
variableInQt
|
||||
```
|
||||
for variable names and PascalCase
|
||||
```
|
||||
ClassInCPlusPlus
|
||||
ClassInQt
|
||||
```
|
||||
for names of classes and other types
|
||||
|
||||
|
@ -115,6 +115,20 @@ other editors that implement this coding style, please add them here.
|
|||
#define frobulate(x) (x)+frob
|
||||
```
|
||||
|
||||
Since C++ is strongly typed, avoid macros where possible.
|
||||
For constants use `constexpr`:
|
||||
```
|
||||
static constexpr int frob = 17;
|
||||
```
|
||||
and for functions use templated inline functions such as
|
||||
```
|
||||
template<typename T>
|
||||
static bool less_than(T x, T y)
|
||||
{
|
||||
return x < y;
|
||||
}
|
||||
```
|
||||
|
||||
* there is a strong preference for lower case file names; sometimes conventions
|
||||
or outside requirements make camelCase filenames the better (or only) choice,
|
||||
but absent such an outside reason all file names should be lower case
|
||||
|
@ -140,16 +154,40 @@ other editors that implement this coding style, please add them here.
|
|||
}
|
||||
```
|
||||
|
||||
## Separation of core and UI layer and historical remarks
|
||||
|
||||
Ideally, we strive for a separation of core functionality and UI layer.
|
||||
In practice however, the distinction is rather fuzzy and the code base is
|
||||
inconsistent. The current state is due to the fact that the project was
|
||||
originally written in C with the gtk library. Later, the UI layer was
|
||||
converted to Qt, whereas the core functionality was still C. Gradually
|
||||
more and more Qt and C++ creeped into the core layer. Recently we
|
||||
switched to full C++.
|
||||
|
||||
To keep the option of non-Qt frontends, we should strive to use as few Qt
|
||||
primitives in the core code as possible. However, some parts
|
||||
are deeply interwoven with Qt, such as for example the translation machinery.
|
||||
Moreover, some platform independent features, such as regexps or URL handling
|
||||
might be hard to replace.
|
||||
|
||||
## C++
|
||||
|
||||
Since the project was originally written in C, some of the creators and
|
||||
original contributors may feel overwhelmed by all too
|
||||
"modern" C++, so try to avoid "fancy" constructs such as template meta
|
||||
programming, unless they make the code distinctly simpler.
|
||||
|
||||
Also many of the (potential) contributors will not have an extensive
|
||||
background in C++, so strive for simplicity.
|
||||
|
||||
## Coding conventions
|
||||
|
||||
* variable declarations
|
||||
|
||||
In C code we really like them to be at the beginning of a code block,
|
||||
not interspersed in the middle.
|
||||
in C++ we are a bit less strict about this – but still, try not to go
|
||||
crazy. Notably, in C++ the lifetime of a variable often coincides with the
|
||||
In C++ the lifetime of a variable often coincides with the
|
||||
lifetime of a resource (e.g. file) and therefore the variable is defined
|
||||
at the place where the resource is needed.
|
||||
at the place where the resource is acquired. The resource is freed,
|
||||
when the variable goes out of scope.
|
||||
|
||||
* The `*`, `&` and `&&` declarators are grouped with the name, not the type
|
||||
(classical C-style) as in `char *string` instead of `char* string`. This
|
||||
|
@ -164,7 +202,7 @@ other editors that implement this coding style, please add them here.
|
|||
struct dive *next, **pprev;
|
||||
```
|
||||
|
||||
* In C++ code, we generally use explicit types in variable declarations for clarity.
|
||||
* We generally use explicit types in variable declarations for clarity.
|
||||
Use `auto` sparingly and only in cases where code readability improves.
|
||||
Two classical examples are:
|
||||
- Iterators, whose type names often are verbose:
|
||||
|
@ -173,26 +211,29 @@ other editors that implement this coding style, please add them here.
|
|||
```
|
||||
is not only distinctly shorter than
|
||||
```
|
||||
QMap<qint64, gpsTracker>::iterator it = m_trackers.find(when);
|
||||
std::map<qint64, gpsTracker>::iterator it = m_trackers.find(when);
|
||||
```
|
||||
it will also continue working if a different data structure is chosen.
|
||||
- If the type is given in the same line anyway. Thus,
|
||||
```
|
||||
auto service = qobject_cast<QLowEnergyService*>(sender());
|
||||
auto service = std::make_unique<QLowEnergyService*>(sender());
|
||||
```
|
||||
is easier to read than and conveys the same information as
|
||||
```
|
||||
QLowEnergyService *service = qobject_cast<QLowEnergyService*>(sender());
|
||||
std::unique_ptr<QLowEnergyService> service = std::make_unique<QLowEnergyService>(sender());
|
||||
```
|
||||
- If the variable is a container that is only assigned to a local variable to
|
||||
be able to use it in a range-based `for` loop
|
||||
|
||||
* containers
|
||||
|
||||
The standard library (STL) containers are robust, but their usage may
|
||||
appear verbose. Therefore, we have a few convenience functions in the
|
||||
`core/ranges.h` header.
|
||||
For example, to loop with an index variable, use
|
||||
```
|
||||
const auto serviceUuids = device.serviceUuids();
|
||||
for (QBluetoothUuid id: serviceUuids) {
|
||||
for (auto [idx, v]: container) {
|
||||
...
|
||||
}
|
||||
```
|
||||
The variable has also to be const to avoid that Qt containers will do a
|
||||
deep copy when the range bases `for` loop will call the `begin()` method
|
||||
internally.
|
||||
|
||||
* text strings
|
||||
|
||||
|
@ -230,9 +271,8 @@ other editors that implement this coding style, please add them here.
|
|||
```
|
||||
|
||||
The `gettextFromC` class in the above example was created as a catch-all
|
||||
context for translations accessed in C code. But it can also be used
|
||||
from C++ helper functions. To use it from C, include the `"core/gettext.h"`
|
||||
header and invoke the `translate()` macro:
|
||||
context for translations accessed in core code. To use it from C, include
|
||||
the `"core/gettext.h"` header and invoke the `translate()` macro:
|
||||
```
|
||||
#include "core/gettext.h"
|
||||
|
||||
|
@ -280,18 +320,85 @@ other editors that implement this coding style, please add them here.
|
|||
|
||||
* string manipulation
|
||||
|
||||
* user interface
|
||||
- user interface
|
||||
|
||||
In UI part of the code use of `QString` methods is preferred, see this pretty
|
||||
good guide in [`QString` documentation][1]
|
||||
|
||||
* core components
|
||||
- core components
|
||||
|
||||
In the core part of the code, C-string should be used.
|
||||
C-string manipulation is not always straightforward specifically when
|
||||
it comes to memory allocation, a set of helper functions has been developed
|
||||
to help with this. Documentation and usage examples can be found in
|
||||
[core/membuffer.h][2]
|
||||
In the core part of the code, std::string should be used.
|
||||
|
||||
* memory management in core
|
||||
|
||||
In core code, objects are typically stored in containers, such as `std::vector<>` or
|
||||
as subobjects of classes.
|
||||
|
||||
If an object has to be allocated on the heap, the owner keeps an `std::unique_ptr`.
|
||||
To transfer ownership, use `std::move()`.
|
||||
|
||||
* initialization and life time
|
||||
|
||||
By using subobjects, the life time of objects is well defined.
|
||||
Consider a class A1 with the two subobjects B and C:
|
||||
```
|
||||
class A1 {
|
||||
struct B;
|
||||
struct C;
|
||||
};
|
||||
```
|
||||
furthermode, consider a class A2 derived from A1 with the subobjects D and E:
|
||||
```
|
||||
class A2 : public A1 {
|
||||
struct D;
|
||||
struct E;
|
||||
};
|
||||
```
|
||||
When creating an object of type A2, the constructors are run in the following order:
|
||||
- B
|
||||
- C
|
||||
- A1
|
||||
- D
|
||||
- E
|
||||
- A2
|
||||
The destructors run in opposite order.
|
||||
This means that C can *always* access B, but not vice-versa and so on.
|
||||
|
||||
Subobjects should be initialized using initializer lists, so that they are initoalized
|
||||
only once.
|
||||
|
||||
* pointers and references
|
||||
|
||||
The difference between pointers and references can be confusing to C programmers,
|
||||
as internally they are realized by the same mechanism. However, conceptually they
|
||||
are different: a reference is a placeholder for a variable.
|
||||
|
||||
In particular this means two things:
|
||||
- A reference cannot be 'reseated'. It stands for a different variable and only
|
||||
that variable. There is no pointer arithmetic with references.
|
||||
- A reference cannot be null. In fact any reasonable compiler will compile
|
||||
```
|
||||
void f(int &f) {
|
||||
return &f == nullptr ? 1 : 2;
|
||||
}
|
||||
```
|
||||
as
|
||||
```
|
||||
f(int&):
|
||||
mov eax, 2
|
||||
ret
|
||||
```
|
||||
|
||||
Thus, functions should in general take references, not pointers. A pointer argument is
|
||||
basically only used if the argument is optional.
|
||||
|
||||
* output parameters
|
||||
|
||||
If a function returns multiple values, generally don't return them in output parameters,
|
||||
but return a structure of multiple values. This can be used in structured bindings:
|
||||
```
|
||||
[val, i] = get_result();
|
||||
```
|
||||
|
||||
## Sample Settings
|
||||
|
||||
|
@ -401,7 +508,6 @@ close to our coding standards.
|
|||
filetype plugin indent on
|
||||
filetype detect
|
||||
set cindent tabstop=8 shiftwidth=8 cinoptions=l1,:0,(0,g0
|
||||
" TODO: extern "C" gets indented
|
||||
|
||||
" And some sane defaults, optional, but quite nice
|
||||
set nocompatible
|
||||
|
|
Before Width: | Height: | Size: 22 KiB |
BIN
Documentation/images/CCR_b1.png
Normal file
After Width: | Height: | Size: 172 KiB |
Before Width: | Height: | Size: 30 KiB |
BIN
Documentation/images/CCR_b2.png
Normal file
After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 32 KiB |
BIN
Documentation/images/CCR_b3.png
Normal file
After Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 71 KiB |
BIN
Documentation/images/PlannerWindow1.png
Normal file
After Width: | Height: | Size: 350 KiB |
Before Width: | Height: | Size: 81 KiB |
BIN
Documentation/images/Planner_CCR.png
Normal file
After Width: | Height: | Size: 321 KiB |
Before Width: | Height: | Size: 81 KiB |
BIN
Documentation/images/Planner_OC_deco.png
Normal file
After Width: | Height: | Size: 346 KiB |
BIN
Documentation/images/Planner_OC_gas_for_CCR.png
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 69 KiB |
BIN
Documentation/images/Planner_OC_rec1.png
Normal file
After Width: | Height: | Size: 227 KiB |
Before Width: | Height: | Size: 74 KiB |
BIN
Documentation/images/Planner_OC_rec2.png
Normal file
After Width: | Height: | Size: 235 KiB |
BIN
Documentation/images/Planner_issues.png
Normal file
After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 86 KiB |
BIN
Documentation/images/Planner_pSCR.png
Normal file
After Width: | Height: | Size: 341 KiB |
Before Width: | Height: | Size: 31 KiB |
BIN
Documentation/images/planner1.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
Documentation/images/planner2.png
Normal file
After Width: | Height: | Size: 18 KiB |
|
@ -3825,24 +3825,18 @@ user interface. It is explicitly used under the following conditions:
|
|||
|
||||
=== The _Subsurface_ dive planner screen
|
||||
|
||||
Like the _Subsurface_ dive log, the planner screen is divided into several sections (see image below). The *setup*
|
||||
parameters for a dive are entered into the sections on the left hand and bottom side of the screen.
|
||||
They are: Available Gases, Rates, Planning, Gas Options and Notes.
|
||||
|
||||
At the top right hand is a green *design panel* on which the profile of the dive can be
|
||||
manipulated directly by dragging and clicking as explained below. This feature makes the
|
||||
_Subsurface_ dive planner unique in ease of use.
|
||||
|
||||
At the bottom right is a text panel with a heading of _Dive Plan Details_. This is where the details of
|
||||
the dive plan are provided in a way that can easily be copied to other software. This is also where
|
||||
any warning messages about the dive plan are printed.
|
||||
|
||||
image::images/PlannerWindow1.jpg["FIGURE: Dive planner startup window",align="center"]
|
||||
Like the _Subsurface_ dive log, the planner screen is divided into several sections (see image below):
|
||||
- At the top left of the screen are the *dive parameters* that will be stored with the planned dive, like dive time, dive mode, and water type.
|
||||
- Below this are tables that allow the user to configure the *cylinders* used on the dive, and the *depth / time and breathing gas* used for the individual parts of the planned dive.
|
||||
- At the bottom of the screen are the *parameters used by the planner* to plan the dive, like ascent / descent rates, planning parameters, and gas options.
|
||||
- At the top right hand is a green *design panel* on which the profile of the dive can be manipulated directly by dragging and clicking as explained below. This feature makes the _Subsurface_ dive planner unique in ease of use.
|
||||
- On the bottom right is a field for the *dive plan details* - this is where the planner will show the dive plan in text form, which will also persisted to the dive notes when the planned dive is saved. This field is designe to be easily copied to other software. It also shows any warning messages about the dive plan.
|
||||
|
||||
image::images/PlannerWindow1.png["FIGURE: Dive planner startup window",align="center"]
|
||||
|
||||
=== Open circuit dives
|
||||
|
||||
- Towards the center bottom of the planner (circled in blue in the image above) is a dropdown list with three options. Select the appropriate one of these:
|
||||
- In the top left of the planner (circled in blue in the image above) is a dropdown list with three options. Select the appropriate one of these:
|
||||
** Open Circuit (the default)
|
||||
** CCR
|
||||
** pSCR
|
||||
|
@ -3882,11 +3876,14 @@ image::images/PlannerWindow1.jpg["FIGURE: Dive planner startup window",align="ce
|
|||
* The most efficient way to create a dive profile is to enter the appropriate values into the table
|
||||
marked _Dive planner points_. The first line of the table represents the duration and the final
|
||||
depth of the descent from the surface. Subsequent segments describe the bottom phase of the dive.
|
||||
The _CC setpoint_ column is only relevant for closed circuit divers.
|
||||
The ascent is usually not specified because this is what the planner is supposed to calculate.
|
||||
Add additional segments to the profile by selecting the "+" icon at the top right hand of the
|
||||
table. Segments entered into the _Dive planner points_ table automatically appear in the *Dive
|
||||
Profile* diagram.
|
||||
* If the _Dive mode_ of the planned dive is changed after gases and segments have been added, it can happen that the _Use_ of a gasmix or the _Used gas_ selected for a segment are not appropriate for the selected _Dive mode_. In this case _Subsurface_ will highlight the problematic selection with a red background to help the user identify and correct the issue. The dive plan will not be calculated until all issues are resolved.
|
||||
|
||||
image::images/Planner_issues.png["FIGURE: Dive planner: Issue display",align="center"]
|
||||
|
||||
|
||||
==== Recreational dives
|
||||
|
||||
|
@ -3963,7 +3960,7 @@ the nitrogen load incurred during previous dives.
|
|||
Below is an image of a dive plan for a recreational dive at 30 meters with gradient factors of 100. Because the no-deco limit (NDL) is 22
|
||||
minutes, there remains a significant amount of air in the cylinder at the end of the dive.
|
||||
|
||||
image::images/Planner_OC_rec1.jpg["FIGURE: A recreational dive plan: setup",align="center"]
|
||||
image::images/Planner_OC_rec1.png["FIGURE: A recreational dive plan: setup",align="center"]
|
||||
|
||||
The dive profile in the planner shows the maximum dive time within no-deco limits using the
|
||||
Bühlmann ZH-L16 algorithm and the gas and depth settings specified as described above. The _Subsurface_ planner
|
||||
|
@ -3976,7 +3973,7 @@ it means that recreational dive limits are exceeded and either the dive duration
|
|||
Below is the same dive plan as above, but with a safety stop and reduced gradient factors for
|
||||
a larger safety margin.
|
||||
|
||||
image::images/Planner_OC_rec2.jpg["FIGURE: A recreational dive plan: gradient factors setup",align="center"]
|
||||
image::images/Planner_OC_rec2.png["FIGURE: A recreational dive plan: gradient factors setup",align="center"]
|
||||
|
||||
==== Non-recreational open circuit dives, including decompression
|
||||
|
||||
|
@ -4093,12 +4090,16 @@ _Type_ select the appropriate cylinder size by using the dropdown list that appe
|
|||
double-clicking a cell in this column. By default, a large number of sizes are listed,
|
||||
and a new cylinder size can be created by typing this into the text box. The cylinder size, start pressure
|
||||
and default switch depths are initialized automatically. Specify the gas composition
|
||||
(e.g. helium and oxygen content). A non-zero value in the "CC setpoint" column of the table of dive planner points
|
||||
indicates a valid setpoint for oxygen partial pressure and that the segment
|
||||
is dived using a closed circuit rebreather (CCR). If the last manually entered
|
||||
segment is a CCR segment, the decompression phase is computed assuming the diver
|
||||
(e.g. helium and oxygen content) and - in the case of planning a CCR dive, the intended use as diluent or OC bailout gas.
|
||||
When planning CCR dives, a segment is calculated as a closed circuit segment if the gas that is used is a _diluent_, and as an open circuit segment if the gas is an _OC-gas_. If the user enables the _Allow open circuit gas to be used as bailout_ option in the _Tech setup_ preferences, then an additional column will be shown allowing the user to select the _Dive mode_ for each segment dived on an _OC-gas_.
|
||||
|
||||
image::images/Planner_OC_gas_for_CCR.png["FIGURE: Planning a dive: OC as CCR",align="center"]
|
||||
|
||||
For CCR dives, an additional column is shown in the _Dive planner points_ table, allowing the user to specify the _Setpoint_ for each closed circuit segment.
|
||||
If the last manually entered
|
||||
segment is a closed circuit segment, the decompression phase is computed assuming the diver
|
||||
uses a CCR with the specified set-point. If the last segment (however
|
||||
short) is on open circuit (OC, indicated by a zero set-point) the
|
||||
short) is on open circuit the
|
||||
decompression is computed in OC mode and the planner only considers gas
|
||||
changes in OC mode.
|
||||
|
||||
|
@ -4112,16 +4113,19 @@ you wish to use this gas during the very start of the dive (the other gas is not
|
|||
Upon pressing Enter on the keyboard, that segment is moved to the top of that table and the plan is adjusted
|
||||
automatically to take into account this new segment of the dive plan (image B below).
|
||||
|
||||
image::images/planner1.jpg["FIGURE: Planning a dive: segments",align="center"]
|
||||
*A:*
|
||||
image::images/planner1.png["FIGURE: Planning a dive: segments 1/2",align="center"]
|
||||
*B:*
|
||||
image::images/planner2.png["FIGURE: Planning a dive: segments 2/2",align="center"]
|
||||
|
||||
Below is an example of a dive plan to 55m using Tx20/30 and the Bühlmann algorithm,
|
||||
followed by an ascent using EAN50 and using the settings as described above.
|
||||
|
||||
image::images/Planner_OC_deco.jpg["FIGURE: Planning a dive: setup",align="center"]
|
||||
image::images/Planner_OC_deco.png["FIGURE: Planning a dive: setup",align="center"]
|
||||
|
||||
Once the above steps have been completed, save by clicking the _Save_ button
|
||||
towards the top middle of the planner. The saved dive plan will appear
|
||||
in the *Dive List* panel of _Subsurface_.
|
||||
in the _Dive List_ panel of _Subsurface_.
|
||||
|
||||
*The dive plan details*
|
||||
|
||||
|
@ -4238,7 +4242,7 @@ are specified for pSCR dives. Below is a dive plan for a pSCR dive. The dive is
|
|||
to that of the CCR dive below, but note the longer ascent duration due to the lower oxygen
|
||||
in the loop due to the oxygen drop across the mouthpiece of the pSCR equipment.
|
||||
|
||||
image::images/Planner_pSCR.jpg["FIGURE: Planning a pSCR dive: setup",align="center"]
|
||||
image::images/Planner_pSCR.png["FIGURE: Planning a pSCR dive: setup",align="center"]
|
||||
|
||||
==== Planning for pSCR bailout
|
||||
|
||||
|
@ -4283,7 +4287,7 @@ columns, as in the cave example, above. See the example of bailout for a CCR div
|
|||
=== Planning CCR dives
|
||||
|
||||
To plan a dive using a closed circuit rebreather, select the _CCR_ option in the dropdown
|
||||
list, circled in blue in the image below.
|
||||
list.
|
||||
|
||||
*Available gases*: In the _Available gases_ table, enter the cylinder information for the
|
||||
diluent cylinder and for any bail-out cylinders. Do NOT enter the information for the oxygen
|
||||
|
@ -4292,12 +4296,7 @@ cylinder since it is implied when the _CCR_ dropdown selection is made.
|
|||
*Entering setpoints*: Specify a default setpoint in the Preferences tab, by selecting _File -> Preferences -> Tech setup_ from
|
||||
the main menu. All user-entered segments in the _Dive planner points_ table
|
||||
use the default setpoint value. Then, different setpoints can be specified for dive segments
|
||||
in the _Dive planner points_ table. A zero setpoint
|
||||
means the diver bails out to open circuit mode for that segment. Decompression is always calculated
|
||||
using the setpoint of the last manually entered segment. So, to plan a bail out ascent for a
|
||||
CCR dive, add a one-minute dive segment to the end with a setpoint value of 0. The decompression
|
||||
algorithm does not switch deco-gases automatically while in CCR mode (i.e. when a positive setpoint is specified) but
|
||||
this is calculated for bail out ascents.
|
||||
in the _Dive planner points_ table.
|
||||
|
||||
If you want the setpoint to change during the planned ascent at a specified depth, you can do this
|
||||
using a "fake" cylinder that you add to the gas list: Give that cylinder a name of "SP 1.4" (or use
|
||||
|
@ -4307,36 +4306,30 @@ there on.
|
|||
|
||||
The dive profile for a CCR dive may look something like the image below.
|
||||
|
||||
image::images/Planner_CCR.jpg["FIGURE: Planning a CCR dive: setup",align="center"]
|
||||
image::images/Planner_CCR.png["FIGURE: Planning a CCR dive: setup",align="center"]
|
||||
|
||||
Note that, in the _Dive plan details_, the gas consumption for a CCR segment is not calculated,
|
||||
so gas consumptions of 0 liters are the norm.
|
||||
|
||||
==== Planning for CCR bailout
|
||||
|
||||
[icon="images/CCR_b1.jpg"]
|
||||
[NOTE]
|
||||
image::images/CCR_b1.png["FIGURE: Planning a CCR dive: closed circuit deco",align="center"]
|
||||
|
||||
It is often necessary to plan for a worst-case bailout event in order to ensure sufficient bailout gas to reach the
|
||||
surface, taking into account decompression. This is done by 1) checking the _Bailout_ checkbox of the dive planner
|
||||
(bailout will be calculated starting at the last segment of the dive specified in the _Dive planner points_ table);
|
||||
2) defining a 1-minute segment at the end of the bottom part
|
||||
of the dive, as in the image on the left where a CCR dive to 40m for 21 minutes is planned;
|
||||
3) changing to an OC-gas during any segment in the _Dive planner points_ table.
|
||||
surface, taking into account decompression. This is done by checking the _Rebreather: Bailout / Deco on OC_ checkbox of the dive planner
|
||||
(bailout will be calculated starting at the last segment of the dive specified in the _Dive planner points_ table):
|
||||
|
||||
image::images/CCR_b2.png["FIGURE: Planning a CCR dive: open circuit bailout",align="center"]
|
||||
|
||||
[icon="images/CCR_b2.jpg"]
|
||||
[NOTE]
|
||||
In the _Dive planner points
|
||||
table_, change the _Dive mode_ of this 1-minute segment to _OC_. This signifies bailout. In this case there is bailout to
|
||||
the existing diluent cylinder (assuming this cylinder has sufficient gas). The appropriate pO~2~ and cylinder pressure
|
||||
graphs are shown in the dive profile, as in the image on the left. Note that the setpoint drops to zero after bailout, since
|
||||
this value does not apply to breathed bailout gas.
|
||||
In the _Available gases_
|
||||
table, change the _Use_ of the gas used for the last segment to _OC-gas_. This signifies bailout.
|
||||
The appropriate pO~2~ and cylinder pressure
|
||||
graphs are shown in the dive profile:
|
||||
|
||||
image::images/CCR_b3.png["FIGURE: Planning a CCR dive: open circuit bailout by cylinder selection",align="center"]
|
||||
|
||||
[icon="images/CCR_b3.jpg"]
|
||||
[NOTE]
|
||||
In order to plan for bailout to an external bailout cylinder, change the _Used gas_ for the 1-minute segment to the
|
||||
appropriate cylinder, as in the example on the left. Note that the cylinder change as well as the bailout are indicated with
|
||||
appropriate cylinder, as in the example above. Note that the cylinder change as well as the bailout are indicated with
|
||||
overlapping icons.
|
||||
|
||||
The volumes of gases required for bailout can be found at the bottom of the *Dive plan details* panel.
|
||||
|
@ -4347,7 +4340,7 @@ The volumes of gases required for bailout can be found at the bottom of the *Div
|
|||
Normally, when a dive plan has been saved, it is accessible from the *Dive List*, like any
|
||||
other dive log. Within the *Dive List* there is no way to change a saved dive plan.
|
||||
To change a dive plan, select it on the *Dive List*. Then, in the main menu,
|
||||
select _Log -> Re-plan dive_. This will open the selected dive plan within the dive planner,
|
||||
select _Log -> Edit dive in planner_. This will open the selected dive plan within the dive planner,
|
||||
allowing changes to be made and saved as usual.
|
||||
|
||||
In addition, there is the option "Save new". This keeps the original
|
||||
|
|
13
INSTALL.md
|
@ -156,7 +156,7 @@ sudo dnf install autoconf automake bluez-libs-devel cmake gcc-c++ git \
|
|||
qt5-qtbase-devel qt5-qtconnectivity-devel qt5-qtdeclarative-devel \
|
||||
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
|
||||
bluez-libs-devel libgit2-devel libzip-devel libmtp-devel libraw-devel
|
||||
```
|
||||
|
||||
|
||||
|
@ -169,8 +169,7 @@ sudo zypper install git gcc-c++ make autoconf automake libtool cmake libzip-deve
|
|||
libqt5-qtbase-devel libQt5WebKit5-devel libqt5-qtsvg-devel \
|
||||
libqt5-qtscript-devel libqt5-qtdeclarative-devel \
|
||||
libqt5-qtconnectivity-devel libqt5-qtlocation-devel libcurl-devel \
|
||||
bluez-devel libgit2-devel libmtp-devel
|
||||
```
|
||||
bluez-devel libgit2-devel libmtp-devel libraw-devel
|
||||
|
||||
On Debian Bookworm this seems to work
|
||||
|
||||
|
@ -183,7 +182,7 @@ sudo apt install \
|
|||
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
|
||||
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
|
||||
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \
|
||||
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev
|
||||
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev libraw-dev
|
||||
```
|
||||
|
||||
In order to build and run mobile-on-desktop, you also need
|
||||
|
@ -207,7 +206,7 @@ sudo apt install \
|
|||
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
|
||||
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
|
||||
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \
|
||||
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev
|
||||
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev libraw-dev
|
||||
```
|
||||
|
||||
In order to build and run mobile-on-desktop, you also need
|
||||
|
@ -231,7 +230,7 @@ sudo apt install \
|
|||
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
|
||||
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
|
||||
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \
|
||||
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev
|
||||
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev libraw-dev
|
||||
```
|
||||
|
||||
In order to build and run mobile-on-desktop, you also need
|
||||
|
@ -260,7 +259,7 @@ sudo apt install \
|
|||
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
|
||||
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
|
||||
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \
|
||||
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev
|
||||
qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev libraw-dev
|
||||
```
|
||||
|
||||
Note that you'll need to increase the swap space as the default of 100MB
|
||||
|
|
|
@ -45,28 +45,28 @@ SOURCES += subsurface-mobile-main.cpp \
|
|||
core/fulltext.cpp \
|
||||
core/subsurfacestartup.cpp \
|
||||
core/subsurface-string.cpp \
|
||||
core/pref.c \
|
||||
core/pref.cpp \
|
||||
core/profile.cpp \
|
||||
core/device.cpp \
|
||||
core/dive.cpp \
|
||||
core/divecomputer.c \
|
||||
core/divecomputer.cpp \
|
||||
core/divefilter.cpp \
|
||||
core/event.c \
|
||||
core/event.cpp \
|
||||
core/eventtype.cpp \
|
||||
core/filterconstraint.cpp \
|
||||
core/filterpreset.cpp \
|
||||
core/divelist.c \
|
||||
core/filterpresettable.cpp \
|
||||
core/divelist.cpp \
|
||||
core/divelog.cpp \
|
||||
core/gas-model.c \
|
||||
core/gaspressures.c \
|
||||
core/gas-model.cpp \
|
||||
core/gaspressures.cpp \
|
||||
core/git-access.cpp \
|
||||
core/globals.cpp \
|
||||
core/liquivision.cpp \
|
||||
core/load-git.cpp \
|
||||
core/parse-xml.cpp \
|
||||
core/parse.cpp \
|
||||
core/picture.c \
|
||||
core/pictureobj.cpp \
|
||||
core/picture.cpp \
|
||||
core/sample.cpp \
|
||||
core/import-suunto.cpp \
|
||||
core/import-shearwater.cpp \
|
||||
|
@ -75,37 +75,38 @@ SOURCES += subsurface-mobile-main.cpp \
|
|||
core/import-divinglog.cpp \
|
||||
core/import-csv.cpp \
|
||||
core/save-html.cpp \
|
||||
core/statistics.c \
|
||||
core/statistics.cpp \
|
||||
core/worldmap-save.cpp \
|
||||
core/libdivecomputer.cpp \
|
||||
core/version.c \
|
||||
core/version.cpp \
|
||||
core/save-git.cpp \
|
||||
core/datatrak.cpp \
|
||||
core/ostctools.c \
|
||||
core/ostctools.cpp \
|
||||
core/planner.cpp \
|
||||
core/save-xml.cpp \
|
||||
core/cochran.cpp \
|
||||
core/deco.cpp \
|
||||
core/divesite.c \
|
||||
core/equipment.c \
|
||||
core/gas.c \
|
||||
core/divesite.cpp \
|
||||
core/equipment.cpp \
|
||||
core/gas.cpp \
|
||||
core/membuffer.cpp \
|
||||
core/selection.cpp \
|
||||
core/sha1.c \
|
||||
core/sha1.cpp \
|
||||
core/string-format.cpp \
|
||||
core/strtod.c \
|
||||
core/strtod.cpp \
|
||||
core/tag.cpp \
|
||||
core/taxonomy.c \
|
||||
core/taxonomy.cpp \
|
||||
core/time.cpp \
|
||||
core/trip.c \
|
||||
core/units.c \
|
||||
core/uemis.c \
|
||||
core/trip.cpp \
|
||||
core/triptable.cpp \
|
||||
core/units.cpp \
|
||||
core/uemis.cpp \
|
||||
core/btdiscovery.cpp \
|
||||
core/connectionlistmodel.cpp \
|
||||
core/qt-ble.cpp \
|
||||
core/uploadDiveShare.cpp \
|
||||
core/uploadDiveLogsDE.cpp \
|
||||
core/save-profiledata.c \
|
||||
core/save-profiledata.cpp \
|
||||
core/xmlparams.cpp \
|
||||
core/settings/qPref.cpp \
|
||||
core/settings/qPrefCloudStorage.cpp \
|
||||
|
@ -207,7 +208,6 @@ HEADERS += \
|
|||
core/extradata.h \
|
||||
core/git-access.h \
|
||||
core/globals.h \
|
||||
core/owning_ptrs.h \
|
||||
core/pref.h \
|
||||
core/profile.h \
|
||||
core/qthelper.h \
|
||||
|
@ -217,9 +217,9 @@ HEADERS += \
|
|||
core/units.h \
|
||||
core/version.h \
|
||||
core/picture.h \
|
||||
core/pictureobj.h \
|
||||
core/planner.h \
|
||||
core/divesite.h \
|
||||
core/divesitetable.h \
|
||||
core/checkcloudconnection.h \
|
||||
core/cochran.h \
|
||||
core/color.h \
|
||||
|
@ -229,6 +229,7 @@ HEADERS += \
|
|||
core/divefilter.h \
|
||||
core/filterconstraint.h \
|
||||
core/filterpreset.h \
|
||||
core/filterpresettable.h \
|
||||
core/divelist.h \
|
||||
core/divelog.h \
|
||||
core/divelogexportlogic.h \
|
||||
|
@ -249,6 +250,8 @@ HEADERS += \
|
|||
core/subsurfacestartup.h \
|
||||
core/subsurfacesysinfo.h \
|
||||
core/taxonomy.h \
|
||||
core/trip.h \
|
||||
core/triptable.h \
|
||||
core/uemis.h \
|
||||
core/webservice.h \
|
||||
core/windowtitleupdate.h \
|
||||
|
|
|
@ -11,7 +11,7 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||
classpath 'com.android.tools.build:gradle:3.5.4'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,8 +28,8 @@ apply plugin: 'com.android.application'
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'com.github.mik3y:usb-serial-for-android:v3.4.3'
|
||||
implementation 'com.android.support:support-v4:25.3.1'
|
||||
implementation 'com.github.mik3y:usb-serial-for-android:v3.8.0'
|
||||
implementation 'com.android.support:support-v4:28.0.0'
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "core/divesite.h"
|
||||
#include "core/picture.h"
|
||||
#include "core/pref.h"
|
||||
#include "core/range.h"
|
||||
#include "core/sample.h"
|
||||
#include "core/selection.h"
|
||||
#include "core/taxonomy.h"
|
||||
|
@ -40,12 +41,12 @@ static constexpr int profileScale = 4;
|
|||
static constexpr int profileWidth = 800 * profileScale;
|
||||
static constexpr int profileHeight = 600 * profileScale;
|
||||
|
||||
static void exportProfile(ProfileScene *profile, const struct dive *dive, const QString &filename)
|
||||
static void exportProfile(ProfileScene &profile, const struct dive &dive, const QString &filename)
|
||||
{
|
||||
QImage image = QImage(QSize(profileWidth, profileHeight), QImage::Format_RGB32);
|
||||
QPainter paint;
|
||||
paint.begin(&image);
|
||||
profile->draw(&paint, QRect(0, 0, profileWidth, profileHeight), dive, 0, nullptr, false);
|
||||
profile.draw(&paint, QRect(0, 0, profileWidth, profileHeight), &dive, 0, nullptr, false);
|
||||
image.save(filename);
|
||||
}
|
||||
|
||||
|
@ -56,17 +57,15 @@ static std::unique_ptr<ProfileScene> getPrintProfile()
|
|||
|
||||
void exportProfile(QString filename, bool selected_only, ExportCallback &cb)
|
||||
{
|
||||
struct dive *dive;
|
||||
int i;
|
||||
int count = 0;
|
||||
if (!filename.endsWith(".png", Qt::CaseInsensitive))
|
||||
filename = filename.append(".png");
|
||||
QFileInfo fi(filename);
|
||||
|
||||
int todo = selected_only ? amount_selected : divelog.dives->nr;
|
||||
int todo = selected_only ? amount_selected : static_cast<int>(divelog.dives.size());
|
||||
int done = 0;
|
||||
auto profile = getPrintProfile();
|
||||
for_each_dive (i, dive) {
|
||||
for (auto &dive: divelog.dives) {
|
||||
if (cb.canceled())
|
||||
return;
|
||||
if (selected_only && !dive->selected)
|
||||
|
@ -74,7 +73,7 @@ void exportProfile(QString filename, bool selected_only, ExportCallback &cb)
|
|||
cb.setProgress(done++ * 1000 / todo);
|
||||
QString fn = count ? fi.path() + QDir::separator() + fi.completeBaseName().append(QString("-%1.").arg(count)) + fi.suffix()
|
||||
: filename;
|
||||
exportProfile(profile.get(), dive, fn);
|
||||
exportProfile(*profile, *dive, fn);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
@ -83,14 +82,12 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
|
|||
{
|
||||
FILE *f;
|
||||
QDir texdir = QFileInfo(filename).dir();
|
||||
struct dive *dive;
|
||||
const struct units *units = get_units();
|
||||
const char *unit;
|
||||
const char *ssrf;
|
||||
int i;
|
||||
bool need_pagebreak = false;
|
||||
|
||||
struct membufferpp buf;
|
||||
membuffer buf;
|
||||
|
||||
if (plain) {
|
||||
ssrf = "";
|
||||
|
@ -132,34 +129,29 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
|
|||
|
||||
put_format(&buf, "\n%%%%%%%%%% Begin Dive Data: %%%%%%%%%%\n");
|
||||
|
||||
int todo = selected_only ? amount_selected : divelog.dives->nr;
|
||||
int todo = selected_only ? amount_selected : static_cast<int>(divelog.dives.size());
|
||||
int done = 0;
|
||||
auto profile = getPrintProfile();
|
||||
for_each_dive (i, dive) {
|
||||
for (auto &dive: divelog.dives) {
|
||||
if (cb.canceled())
|
||||
return;
|
||||
if (selected_only && !dive->selected)
|
||||
continue;
|
||||
cb.setProgress(done++ * 1000 / todo);
|
||||
exportProfile(profile.get(), dive, texdir.filePath(QString("profile%1.png").arg(dive->number)));
|
||||
exportProfile(*profile, *dive, texdir.filePath(QString("profile%1.png").arg(dive->number)));
|
||||
struct tm tm;
|
||||
utc_mkdate(dive->when, &tm);
|
||||
|
||||
const char *country = NULL;
|
||||
std::string country;
|
||||
dive_site *site = dive->dive_site;
|
||||
if (site)
|
||||
country = taxonomy_get_country(&site->taxonomy);
|
||||
pressure_t delta_p = {.mbar = 0};
|
||||
country = taxonomy_get_country(site->taxonomy);
|
||||
pressure_t delta_p;
|
||||
|
||||
QString star = "*";
|
||||
QString viz = star.repeated(dive->visibility);
|
||||
QString rating = star.repeated(dive->rating);
|
||||
|
||||
int i;
|
||||
int qty_cyl;
|
||||
int qty_weight;
|
||||
double total_weight;
|
||||
|
||||
if (need_pagebreak) {
|
||||
if (plain)
|
||||
put_format(&buf, "\\vfill\\eject\n");
|
||||
|
@ -174,13 +166,13 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
|
|||
put_format(&buf, "\\def\\%shour{%02u}\n", ssrf, tm.tm_hour);
|
||||
put_format(&buf, "\\def\\%sminute{%02u}\n", ssrf, tm.tm_min);
|
||||
put_format(&buf, "\\def\\%snumber{%d}\n", ssrf, dive->number);
|
||||
put_format(&buf, "\\def\\%splace{%s}\n", ssrf, site ? site->name : "");
|
||||
put_format(&buf, "\\def\\%splace{%s}\n", ssrf, site ? site->name.c_str() : "");
|
||||
put_format(&buf, "\\def\\%sspot{}\n", ssrf);
|
||||
put_format(&buf, "\\def\\%ssitename{%s}\n", ssrf, site ? site->name : "");
|
||||
put_format(&buf, "\\def\\%ssitename{%s}\n", ssrf, site ? site->name.c_str() : "");
|
||||
site ? put_format(&buf, "\\def\\%sgpslat{%f}\n", ssrf, site->location.lat.udeg / 1000000.0) : put_format(&buf, "\\def\\%sgpslat{}\n", ssrf);
|
||||
site ? put_format(&buf, "\\def\\%sgpslon{%f}\n", ssrf, site->location.lon.udeg / 1000000.0) : put_format(&buf, "\\def\\gpslon{}\n");
|
||||
put_format(&buf, "\\def\\%scomputer{%s}\n", ssrf, dive->dc.model);
|
||||
put_format(&buf, "\\def\\%scountry{%s}\n", ssrf, country ?: "");
|
||||
put_format(&buf, "\\def\\%scomputer{%s}\n", ssrf, dive->dcs[0].model.c_str());
|
||||
put_format(&buf, "\\def\\%scountry{%s}\n", ssrf, country.c_str());
|
||||
put_format(&buf, "\\def\\%stime{%u:%02u}\n", ssrf, FRACTION_TUPLE(dive->duration.seconds, 60));
|
||||
|
||||
put_format(&buf, "\n%% Dive Profile Details:\n");
|
||||
|
@ -191,29 +183,28 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
|
|||
dive->maxdepth.mm ? put_format(&buf, "\\def\\%smaximumdepth{%.1f\\%sdepthunit}\n", ssrf, get_depth_units(dive->maxdepth.mm, NULL, &unit), ssrf) : put_format(&buf, "\\def\\%smaximumdepth{}\n", ssrf);
|
||||
dive->meandepth.mm ? put_format(&buf, "\\def\\%smeandepth{%.1f\\%sdepthunit}\n", ssrf, get_depth_units(dive->meandepth.mm, NULL, &unit), ssrf) : put_format(&buf, "\\def\\%smeandepth{}\n", ssrf);
|
||||
|
||||
std::string tags = taglist_get_tagstring(dive->tag_list);
|
||||
std::string tags = taglist_get_tagstring(dive->tags);
|
||||
put_format(&buf, "\\def\\%stype{%s}\n", ssrf, tags.c_str());
|
||||
put_format(&buf, "\\def\\%sviz{%s}\n", ssrf, qPrintable(viz));
|
||||
put_format(&buf, "\\def\\%srating{%s}\n", ssrf, qPrintable(rating));
|
||||
put_format(&buf, "\\def\\%splot{\\includegraphics[width=9cm,height=4cm]{profile%d}}\n", ssrf, dive->number);
|
||||
put_format(&buf, "\\def\\%sprofilename{profile%d}\n", ssrf, dive->number);
|
||||
put_format(&buf, "\\def\\%scomment{%s}\n", ssrf, dive->notes ? dive->notes : "");
|
||||
put_format(&buf, "\\def\\%sbuddy{%s}\n", ssrf, dive->buddy ? dive->buddy : "");
|
||||
put_format(&buf, "\\def\\%sdivemaster{%s}\n", ssrf, dive->diveguide ? dive->diveguide : "");
|
||||
put_format(&buf, "\\def\\%ssuit{%s}\n", ssrf, dive->suit ? dive->suit : "");
|
||||
put_format(&buf, "\\def\\%scomment{%s}\n", ssrf, dive->notes.c_str());
|
||||
put_format(&buf, "\\def\\%sbuddy{%s}\n", ssrf, dive->buddy.c_str());
|
||||
put_format(&buf, "\\def\\%sdivemaster{%s}\n", ssrf, dive->diveguide.c_str());
|
||||
put_format(&buf, "\\def\\%ssuit{%s}\n", ssrf, dive->suit.c_str());
|
||||
|
||||
// Print cylinder data
|
||||
put_format(&buf, "\n%% Gas use information:\n");
|
||||
qty_cyl = 0;
|
||||
for (i = 0; i < dive->cylinders.nr; i++){
|
||||
const cylinder_t &cyl = *get_cylinder(dive, i);
|
||||
if (is_cylinder_used(dive, i) || (prefs.include_unused_tanks && cyl.type.description)){
|
||||
put_format(&buf, "\\def\\%scyl%cdescription{%s}\n", ssrf, 'a' + i, cyl.type.description);
|
||||
put_format(&buf, "\\def\\%scyl%cgasname{%s}\n", ssrf, 'a' + i, gasname(cyl.gasmix));
|
||||
int qty_cyl = 0;
|
||||
for (auto [i, cyl]: enumerated_range(dive->cylinders)) {
|
||||
if (dive->is_cylinder_used(i) || (prefs.include_unused_tanks && !cyl.type.description.empty())){
|
||||
put_format(&buf, "\\def\\%scyl%cdescription{%s}\n", ssrf, 'a' + i, cyl.type.description.c_str());
|
||||
put_format(&buf, "\\def\\%scyl%cgasname{%s}\n", ssrf, 'a' + i, cyl.gasmix.name().c_str());
|
||||
put_format(&buf, "\\def\\%scyl%cmixO2{%.1f\\%%}\n", ssrf, 'a' + i, get_o2(cyl.gasmix)/10.0);
|
||||
put_format(&buf, "\\def\\%scyl%cmixHe{%.1f\\%%}\n", ssrf, 'a' + i, get_he(cyl.gasmix)/10.0);
|
||||
put_format(&buf, "\\def\\%scyl%cmixN2{%.1f\\%%}\n", ssrf, 'a' + i, (100.0 - (get_o2(cyl.gasmix)/10.0) - (get_he(cyl.gasmix)/10.0)));
|
||||
delta_p.mbar += cyl.start.mbar - cyl.end.mbar;
|
||||
delta_p += cyl.start - cyl.end;
|
||||
put_format(&buf, "\\def\\%scyl%cstartpress{%.1f\\%spressureunit}\n", ssrf, 'a' + i, get_pressure_units(cyl.start.mbar, &unit)/1.0, ssrf);
|
||||
put_format(&buf, "\\def\\%scyl%cendpress{%.1f\\%spressureunit}\n", ssrf, 'a' + i, get_pressure_units(cyl.end.mbar, &unit)/1.0, ssrf);
|
||||
qty_cyl += 1;
|
||||
|
@ -235,11 +226,10 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
|
|||
|
||||
//Code block prints all weights listed in dive.
|
||||
put_format(&buf, "\n%% Weighting information:\n");
|
||||
qty_weight = 0;
|
||||
total_weight = 0;
|
||||
for (i = 0; i < dive->weightsystems.nr; i++) {
|
||||
weightsystem_t w = dive->weightsystems.weightsystems[i];
|
||||
put_format(&buf, "\\def\\%sweight%ctype{%s}\n", ssrf, 'a' + i, w.description);
|
||||
int qty_weight = 0;
|
||||
double total_weight = 0;
|
||||
for (auto [i, w]: enumerated_range(dive->weightsystems)) {
|
||||
put_format(&buf, "\\def\\%sweight%ctype{%s}\n", ssrf, 'a' + i, w.description.c_str());
|
||||
put_format(&buf, "\\def\\%sweight%camt{%.3f\\%sweightunit}\n", ssrf, 'a' + i, get_weight_units(w.weight.grams, NULL, &unit), ssrf);
|
||||
qty_weight += 1;
|
||||
total_weight += get_weight_units(w.weight.grams, NULL, &unit);
|
||||
|
@ -251,7 +241,7 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
|
|||
// Legacy fields
|
||||
put_format(&buf, "\\def\\%sspot{}\n", ssrf);
|
||||
put_format(&buf, "\\def\\%sentrance{}\n", ssrf);
|
||||
put_format(&buf, "\\def\\%splace{%s}\n", ssrf, site ? site->name : "");
|
||||
put_format(&buf, "\\def\\%splace{%s}\n", ssrf, site ? site->name.c_str() : "");
|
||||
dive->maxdepth.mm ? put_format(&buf, "\\def\\%sdepth{%.1f\\%sdepthunit}\n", ssrf, get_depth_units(dive->maxdepth.mm, NULL, &unit), ssrf) : put_format(&buf, "\\def\\%sdepth{}\n", ssrf);
|
||||
|
||||
put_format(&buf, "\\%spage\n", ssrf);
|
||||
|
@ -275,26 +265,22 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
|
|||
void export_depths(const char *filename, bool selected_only)
|
||||
{
|
||||
FILE *f;
|
||||
struct dive *dive;
|
||||
depth_t depth;
|
||||
int i;
|
||||
const char *unit = NULL;
|
||||
|
||||
struct membufferpp buf;
|
||||
membuffer buf;
|
||||
|
||||
for_each_dive (i, dive) {
|
||||
for (auto &dive: divelog.dives) {
|
||||
if (selected_only && !dive->selected)
|
||||
continue;
|
||||
|
||||
FOR_EACH_PICTURE (dive) {
|
||||
int n = dive->dc.samples;
|
||||
struct sample *s = dive->dc.sample;
|
||||
depth.mm = 0;
|
||||
while (--n >= 0 && (int32_t)s->time.seconds <= picture->offset.seconds) {
|
||||
depth.mm = s->depth.mm;
|
||||
s++;
|
||||
for (auto &picture: dive->pictures) {
|
||||
depth_t depth;
|
||||
for (auto &s: dive->dcs[0].samples) {
|
||||
if ((int32_t)s.time.seconds > picture.offset.seconds)
|
||||
break;
|
||||
depth = s.depth;
|
||||
}
|
||||
put_format(&buf, "%s\t%.1f", picture->filename, get_depth_units(depth.mm, NULL, &unit));
|
||||
put_format(&buf, "%s\t%.1f", picture.filename.c_str(), get_depth_units(depth.mm, NULL, &unit));
|
||||
put_format(&buf, "%s\n", unit);
|
||||
}
|
||||
}
|
||||
|
@ -318,28 +304,23 @@ std::vector<const dive_site *> getDiveSitesToExport(bool selectedOnly)
|
|||
if (selectedOnly && DiveFilter::instance()->diveSiteMode()) {
|
||||
// Special case in dive site mode: export all selected dive sites,
|
||||
// not the dive sites of selected dives.
|
||||
QVector<dive_site *> sites = DiveFilter::instance()->filteredDiveSites();
|
||||
res.reserve(sites.size());
|
||||
for (const dive_site *ds: sites)
|
||||
for (auto ds: DiveFilter::instance()->filteredDiveSites())
|
||||
res.push_back(ds);
|
||||
return res;
|
||||
}
|
||||
|
||||
res.reserve(divelog.sites->nr);
|
||||
for (int i = 0; i < divelog.sites->nr; i++) {
|
||||
struct dive_site *ds = get_dive_site(i, divelog.sites);
|
||||
if (dive_site_is_empty(ds))
|
||||
res.reserve(divelog.sites.size());
|
||||
for (const auto &ds: divelog.sites) {
|
||||
if (ds->is_empty())
|
||||
continue;
|
||||
if (selectedOnly && !is_dive_site_selected(ds))
|
||||
if (selectedOnly && !ds->is_selected())
|
||||
continue;
|
||||
res.push_back(ds);
|
||||
res.push_back(ds.get());
|
||||
}
|
||||
#else
|
||||
/* walk the dive site list */
|
||||
int i;
|
||||
const struct dive_site *ds;
|
||||
for_each_dive_site (i, ds, divelog.sites)
|
||||
res.push_back(get_dive_site(i, divelog.sites));
|
||||
for (const auto &ds: divelog.sites)
|
||||
res.push_back(ds.get());
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "qt-models/diveimportedmodel.h"
|
||||
|
||||
void cliDownloader(const char *vendor, const char *product, const char *device)
|
||||
void cliDownloader(const std::string &vendor, const std::string &product, const std::string &device)
|
||||
{
|
||||
DiveImportedModel diveImportedModel;
|
||||
DiveImportedModel::connect(&diveImportedModel, &DiveImportedModel::downloadFinished, [] {
|
||||
|
@ -10,11 +10,11 @@ void cliDownloader(const char *vendor, const char *product, const char *device)
|
|||
});
|
||||
|
||||
auto data = diveImportedModel.thread.data();
|
||||
data->setVendor(vendor);
|
||||
data->setProduct(product);
|
||||
data->setVendor(QString::fromStdString(vendor));
|
||||
data->setProduct(QString::fromStdString(product));
|
||||
data->setBluetoothMode(false);
|
||||
if (data->vendor() == "Uemis") {
|
||||
QString devname(device);
|
||||
QString devname = QString::fromStdString(device);
|
||||
int colon = devname.indexOf(QStringLiteral(":\\ (UEMISSDA)"));
|
||||
if (colon >= 0) {
|
||||
devname.truncate(colon + 2);
|
||||
|
@ -22,7 +22,7 @@ void cliDownloader(const char *vendor, const char *product, const char *device)
|
|||
}
|
||||
data->setDevName(devname);
|
||||
} else {
|
||||
data->setDevName(device);
|
||||
data->setDevName(QString::fromStdString(device));
|
||||
}
|
||||
|
||||
// some assumptions - should all be configurable
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
MACRO(pkg_config_library LIBNAME pcfile option)
|
||||
pkg_check_modules(${LIBNAME} ${option} ${pcfile})
|
||||
MACRO(pkg_config_library LIBNAME pcfile )
|
||||
pkg_check_modules(${LIBNAME} ${ARGN} ${pcfile})
|
||||
include_directories(${${LIBNAME}_INCLUDE_DIRS})
|
||||
link_directories(${${LIBNAME}_LIBRARY_DIRS})
|
||||
add_definitions(${${LIBNAME}_CFLAGS_OTHER})
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
namespace Command {
|
||||
|
||||
// Dive-list related commands
|
||||
void addDive(dive *d, bool autogroup, bool newNumber)
|
||||
void addDive(std::unique_ptr<dive> d, bool autogroup, bool newNumber)
|
||||
{
|
||||
execute(new AddDive(d, autogroup, newNumber));
|
||||
execute(new AddDive(std::move(d), autogroup, newNumber));
|
||||
}
|
||||
|
||||
void importDives(struct divelog *log, int flags, const QString &source)
|
||||
|
@ -134,9 +134,9 @@ void addDiveSite(const QString &name)
|
|||
execute(new AddDiveSite(name));
|
||||
}
|
||||
|
||||
void importDiveSites(struct dive_site_table *sites, const QString &source)
|
||||
void importDiveSites(dive_site_table sites, const QString &source)
|
||||
{
|
||||
execute(new ImportDiveSites(sites, source));
|
||||
execute(new ImportDiveSites(std::move(sites), source));
|
||||
}
|
||||
|
||||
void mergeDiveSites(dive_site *ds, const QVector<dive_site *> &sites)
|
||||
|
@ -267,9 +267,9 @@ int editDiveGuide(const QStringList &newList, bool currentDiveOnly)
|
|||
return execute_edit(new EditDiveGuide(newList, currentDiveOnly));
|
||||
}
|
||||
|
||||
void pasteDives(const dive *d, dive_components what)
|
||||
void pasteDives(const dive_paste_data &data)
|
||||
{
|
||||
execute(new PasteDives(d, what));
|
||||
execute(new PasteDives(data));
|
||||
}
|
||||
|
||||
void replanDive(dive *d)
|
||||
|
@ -294,7 +294,7 @@ int removeWeight(int index, bool currentDiveOnly)
|
|||
|
||||
int editWeight(int index, weightsystem_t ws, bool currentDiveOnly)
|
||||
{
|
||||
return execute_edit(new EditWeight(index, ws, currentDiveOnly));
|
||||
return execute_edit(new EditWeight(index, std::move(ws), currentDiveOnly));
|
||||
}
|
||||
|
||||
int addCylinder(bool currentDiveOnly)
|
||||
|
@ -309,7 +309,7 @@ int removeCylinder(int index, bool currentDiveOnly)
|
|||
|
||||
int editCylinder(int index, cylinder_t cyl, EditCylinderType type, bool currentDiveOnly)
|
||||
{
|
||||
return execute_edit(new EditCylinder(index, cyl, type, currentDiveOnly));
|
||||
return execute_edit(new EditCylinder(index, std::move(cyl), type, currentDiveOnly));
|
||||
}
|
||||
|
||||
void editSensors(int toCylinder, int fromCylinder, int dcNr)
|
||||
|
@ -352,14 +352,14 @@ void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO
|
|||
execute(new AddEventSetpointChange(d, dcNr, seconds, pO2));
|
||||
}
|
||||
|
||||
void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name)
|
||||
void renameEvent(struct dive *d, int dcNr, int idx, const std::string name)
|
||||
{
|
||||
execute(new RenameEvent(d, dcNr, ev, name));
|
||||
execute(new RenameEvent(d, dcNr, idx, std::move(name)));
|
||||
}
|
||||
|
||||
void removeEvent(struct dive *d, int dcNr, struct event *ev)
|
||||
void removeEvent(struct dive *d, int dcNr, int idx)
|
||||
{
|
||||
execute(new RemoveEvent(d, dcNr, ev));
|
||||
execute(new RemoveEvent(d, dcNr, idx));
|
||||
}
|
||||
|
||||
void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include "core/divelog.h"
|
||||
#include "core/equipment.h"
|
||||
#include "core/pictureobj.h"
|
||||
#include "core/picture.h"
|
||||
#include "core/taxonomy.h"
|
||||
#include <QVector>
|
||||
#include <QAction>
|
||||
|
@ -13,6 +13,7 @@
|
|||
struct divecomputer;
|
||||
struct divelog;
|
||||
struct dive_components;
|
||||
struct dive_paste_data;
|
||||
struct dive_site;
|
||||
struct dive_trip;
|
||||
struct event;
|
||||
|
@ -40,7 +41,7 @@ bool placingCommand(); // Currently executing a new command -> might not have
|
|||
// distance are added to a trip. dive d is consumed (the structure is reset)!
|
||||
// If newNumber is true, the dive is assigned a new number, depending on the
|
||||
// insertion position.
|
||||
void addDive(dive *d, bool autogroup, bool newNumber);
|
||||
void addDive(std::unique_ptr<dive> d, bool autogroup, bool newNumber);
|
||||
void importDives(struct divelog *log, int flags, const QString &source); // The tables are consumed!
|
||||
void deleteDive(const QVector<struct dive*> &divesToDelete);
|
||||
void shiftTime(const std::vector<dive *> &changedDives, int amount);
|
||||
|
@ -68,7 +69,7 @@ void editDiveSiteCountry(dive_site *ds, const QString &value);
|
|||
void editDiveSiteLocation(dive_site *ds, location_t value);
|
||||
void editDiveSiteTaxonomy(dive_site *ds, taxonomy_data &value); // value is consumed (i.e. will be erased after call)!
|
||||
void addDiveSite(const QString &name);
|
||||
void importDiveSites(struct dive_site_table *sites, const QString &source);
|
||||
void importDiveSites(dive_site_table sites, const QString &source); // takes ownership of dive site table
|
||||
void mergeDiveSites(dive_site *ds, const QVector<dive_site *> &sites);
|
||||
void purgeUnusedDiveSites();
|
||||
|
||||
|
@ -95,7 +96,7 @@ int editDiveSiteNew(const QString &newName, bool currentDiveOnly);
|
|||
int editTags(const QStringList &newList, bool currentDiveOnly);
|
||||
int editBuddies(const QStringList &newList, bool currentDiveOnly);
|
||||
int editDiveGuide(const QStringList &newList, bool currentDiveOnly);
|
||||
void pasteDives(const dive *d, dive_components what);
|
||||
void pasteDives(const dive_paste_data &data);
|
||||
enum class EditProfileType {
|
||||
ADD,
|
||||
REMOVE,
|
||||
|
@ -132,8 +133,8 @@ void editTripNotes(dive_trip *trip, const QString &s);
|
|||
void addEventBookmark(struct dive *d, int dcNr, int seconds);
|
||||
void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode);
|
||||
void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2);
|
||||
void renameEvent(struct dive *d, int dcNr, struct event *ev, const char *name);
|
||||
void removeEvent(struct dive *d, int dcNr, struct event *ev);
|
||||
void renameEvent(struct dive *d, int dcNr, int idx, std::string name);
|
||||
void removeEvent(struct dive *d, int dcNr, int idx);
|
||||
void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank);
|
||||
|
||||
// 7) Picture (media) commands
|
||||
|
@ -144,7 +145,7 @@ struct PictureListForDeletion {
|
|||
};
|
||||
struct PictureListForAddition {
|
||||
dive *d;
|
||||
std::vector<PictureObj> pics;
|
||||
std::vector<picture> pics;
|
||||
};
|
||||
void setPictureOffset(dive *d, const QString &filename, offset_t offset);
|
||||
void removePictures(const std::vector<PictureListForDeletion> &pictures);
|
||||
|
|
|
@ -66,7 +66,7 @@ QString diveNumberOrDate(struct dive *d)
|
|||
QString getListOfDives(const std::vector<struct dive*> &dives)
|
||||
{
|
||||
QString listOfDives;
|
||||
if ((int)dives.size() == divelog.dives->nr)
|
||||
if (dives.size() == divelog.dives.size())
|
||||
return Base::tr("all dives");
|
||||
int i = 0;
|
||||
for (dive *d: dives) {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "core/divesite.h"
|
||||
#include "core/trip.h"
|
||||
#include "core/dive.h"
|
||||
#include "core/owning_ptrs.h"
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include <QCoreApplication> // For Q_DECLARE_TR_FUNCTIONS
|
||||
|
@ -106,9 +105,8 @@
|
|||
// 1) Dive 2 was deleted with the "add dive 2" command, because that was the owner.
|
||||
// 2) Dive 1 was not deleted, because it is owned by the backend.
|
||||
//
|
||||
// To take ownership of dives/trips, the OnwingDivePtr and OwningTripPtr types are used. These
|
||||
// are simply derived from std::unique_ptr and therefore use well-established semantics.
|
||||
// Expressed in C-terms: std::unique_ptr<T> is exactly the same as T* with the following
|
||||
// To take ownership of dives/trips, std::unique_ptr<>s are used.
|
||||
// Expressed in C-terms: std::unique_ptr<T> is the same as T* with the following
|
||||
// twists:
|
||||
// 1) default-initialized to NULL.
|
||||
// 2) if it goes out of scope (local scope or containing object destroyed), it does:
|
||||
|
@ -122,8 +120,8 @@
|
|||
// move-semantics and Qt's containers are incompatible, owing to COW semantics.
|
||||
//
|
||||
// Usage:
|
||||
// OwningDivePtr dPtr; // Initialize to null-state: not owning any dive.
|
||||
// OwningDivePtr dPtr(dive); // Take ownership of dive (which is of type struct dive *).
|
||||
// std::unique_ptr<dive> dPtr; // Initialize to null-state: not owning any dive.
|
||||
// std::unique_ptr<dive> dPtr(dive); // Take ownership of dive (which is of type struct dive *).
|
||||
// // If dPtr goes out of scope, the dive will be freed with free_dive().
|
||||
// struct dive *d = dPtr.release(); // Give up ownership of dive. dPtr is reset to null.
|
||||
// struct dive *d = d.get(); // Get pointer dive, but don't release ownership.
|
||||
|
@ -131,10 +129,10 @@
|
|||
// dPtr.reset(); // Delete currently owned dive and reset to null.
|
||||
// dPtr2 = dPtr1; // Fails to compile.
|
||||
// dPtr2 = std::move(dPtr1); // dPtr2 takes ownership, dPtr1 is reset to null.
|
||||
// OwningDivePtr fun();
|
||||
// std::unique_ptr<dive> fun();
|
||||
// dPtr1 = fun(); // Compiles. Simply put: the compiler knows that the result of fun() will
|
||||
// // be trashed and therefore can be moved-from.
|
||||
// std::vector<OwningDivePtr> v: // Define an empty vector of owning pointers.
|
||||
// std::vector<std::unique_ptr<dive>> v: // Define an empty vector of owning pointers.
|
||||
// v.emplace_back(dive); // Take ownership of dive and add at end of vector
|
||||
// // If the vector goes out of scope, all dives will be freed with free_dive().
|
||||
// v.clear(v); // Reset the vector to zero length. If the elements weren't release()d,
|
||||
|
@ -151,6 +149,12 @@ QVector<T> stdToQt(const std::vector<T> &v)
|
|||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> qtToStd(const QVector<T> &v)
|
||||
{
|
||||
return std::vector<T>(v.begin(), v.end());
|
||||
}
|
||||
|
||||
// We put everything in a namespace, so that we can shorten names without polluting the global namespace
|
||||
namespace Command {
|
||||
|
||||
|
|
|
@ -13,20 +13,19 @@ EditDeviceNickname::EditDeviceNickname(const struct divecomputer *dc, const QStr
|
|||
if (index == -1)
|
||||
return;
|
||||
|
||||
setText(Command::Base::tr("Set nickname of device %1 (serial %2) to %3").arg(dc->model, dc->serial, nicknameIn));
|
||||
setText(Command::Base::tr("Set nickname of device %1 (serial %2) to %3").arg(dc->model.c_str(), dc->serial.c_str(), nicknameIn));
|
||||
}
|
||||
|
||||
bool EditDeviceNickname::workToBeDone()
|
||||
{
|
||||
return get_device(divelog.devices, index) != nullptr;
|
||||
return index >= 0;
|
||||
}
|
||||
|
||||
void EditDeviceNickname::redo()
|
||||
{
|
||||
device *dev = get_device_mutable(divelog.devices, index);
|
||||
if (!dev)
|
||||
if (index < 0 || static_cast<size_t>(index) >= divelog.devices.size())
|
||||
return;
|
||||
std::swap(dev->nickName, nickname);
|
||||
std::swap(divelog.devices[index].nickName, nickname);
|
||||
emit diveListNotifier.deviceEdited();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include "command_divelist.h"
|
||||
#include "core/divefilter.h"
|
||||
#include "core/divelist.h"
|
||||
#include "core/divelog.h"
|
||||
#include "core/qthelper.h"
|
||||
#include "core/selection.h"
|
||||
#include "core/subsurface-qt/divelistnotifier.h"
|
||||
#include "qt-models/filtermodels.h"
|
||||
#include "core/divefilter.h"
|
||||
|
||||
#include <array>
|
||||
#include "qt-models/divelocationmodel.h"
|
||||
|
||||
namespace Command {
|
||||
|
||||
// Helper function that takes care to unselect trips that are removed from the backend
|
||||
static void remove_trip_from_backend(dive_trip *trip)
|
||||
static std::unique_ptr<dive_trip> remove_trip_from_backend(dive_trip *trip)
|
||||
{
|
||||
if (trip->selected)
|
||||
deselect_trip(trip);
|
||||
remove_trip(trip, divelog.trips); // Remove trip from backend
|
||||
auto [t, idx] = divelog.trips.pull(trip);
|
||||
return std::move(t);
|
||||
}
|
||||
|
||||
// This helper function removes a dive, takes ownership of the dive and adds it to a DiveToAdd structure.
|
||||
// If the trip the dive belongs to becomes empty, it is removed and added to the tripsToAdd vector.
|
||||
// It is crucial that dives are added in reverse order of deletion, so that the indices are correctly
|
||||
// set and that the trips are added before they are used!
|
||||
DiveToAdd DiveListBase::removeDive(struct dive *d, std::vector<OwningTripPtr> &tripsToAdd)
|
||||
DiveToAdd DiveListBase::removeDive(struct dive *d, std::vector<std::unique_ptr<dive_trip>> &tripsToAdd)
|
||||
{
|
||||
// If the dive was the current dive, reset the current dive. The calling
|
||||
// command is responsible of finding a new dive.
|
||||
|
@ -39,18 +39,19 @@ DiveToAdd DiveListBase::removeDive(struct dive *d, std::vector<OwningTripPtr> &t
|
|||
if (d->dive_site)
|
||||
diveSiteCountChanged(d->dive_site);
|
||||
res.site = unregister_dive_from_dive_site(d);
|
||||
if (res.trip && res.trip->dives.nr == 0) {
|
||||
remove_trip_from_backend(res.trip); // Remove trip from backend
|
||||
tripsToAdd.emplace_back(res.trip); // Take ownership of trip
|
||||
if (res.trip && res.trip->dives.empty()) {
|
||||
divelog.trips.sort(); // Removal of dives has changed order of trips! (TODO: remove this)
|
||||
auto trip = remove_trip_from_backend(res.trip); // Remove trip from backend
|
||||
tripsToAdd.push_back(std::move(trip)); // Take ownership of trip
|
||||
}
|
||||
|
||||
int idx = get_divenr(d);
|
||||
if (idx < 0)
|
||||
size_t idx = divelog.dives.get_idx(d);
|
||||
if (idx == std::string::npos)
|
||||
qWarning("Deletion of unknown dive!");
|
||||
|
||||
DiveFilter::instance()->diveRemoved(d);
|
||||
|
||||
res.dive.reset(unregister_dive(idx)); // Remove dive from backend
|
||||
res.dive = divelog.dives.unregister_dive(idx); // Remove dive from backend
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -67,23 +68,12 @@ void DiveListBase::diveSiteCountChanged(struct dive_site *ds)
|
|||
dive *DiveListBase::addDive(DiveToAdd &d)
|
||||
{
|
||||
if (d.trip)
|
||||
add_dive_to_trip(d.dive.get(), d.trip);
|
||||
d.trip->add_dive(d.dive.get());
|
||||
if (d.site) {
|
||||
add_dive_to_dive_site(d.dive.get(), d.site);
|
||||
d.site->add_dive(d.dive.get());
|
||||
diveSiteCountChanged(d.site);
|
||||
}
|
||||
dive *res = d.dive.release(); // Give up ownership of dive
|
||||
|
||||
// When we add dives, we start in hidden-by-filter status. Once all
|
||||
// dives have been added, their status will be updated.
|
||||
res->hidden_by_filter = true;
|
||||
|
||||
int idx = dive_table_get_insertion_index(divelog.dives, res);
|
||||
fulltext_register(res); // Register the dive's fulltext cache
|
||||
add_to_dive_table(divelog.dives, idx, res); // Return ownership to backend
|
||||
invalidate_dive_cache(res); // Ensure that dive is written in git_save()
|
||||
|
||||
return res;
|
||||
return divelog.dives.register_dive(std::move(d.dive)); // Transfer ownership to core and update fulltext index
|
||||
}
|
||||
|
||||
// Some signals are sent in batches per trip. To avoid writing the same loop
|
||||
|
@ -99,7 +89,7 @@ void processByTrip(std::vector<std::pair<dive_trip *, dive *>> &dives, Function
|
|||
// Sort lexicographically by trip then according to the dive_less_than() function.
|
||||
std::sort(dives.begin(), dives.end(),
|
||||
[](const std::pair<dive_trip *, dive *> &e1, const std::pair<dive_trip *, dive *> &e2)
|
||||
{ return e1.first == e2.first ? dive_less_than(e1.second, e2.second) : e1.first < e2.first; });
|
||||
{ return e1.first == e2.first ? dive_less_than(*e1.second, *e2.second) : e1.first < e2.first; });
|
||||
|
||||
// Then, process the dives in batches by trip
|
||||
size_t i, j; // Begin and end of batch
|
||||
|
@ -124,8 +114,8 @@ void processByTrip(std::vector<std::pair<dive_trip *, dive *>> &dives, Function
|
|||
DivesAndTripsToAdd DiveListBase::removeDives(DivesAndSitesToRemove &divesAndSitesToDelete)
|
||||
{
|
||||
std::vector<DiveToAdd> divesToAdd;
|
||||
std::vector<OwningTripPtr> tripsToAdd;
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd;
|
||||
std::vector<std::unique_ptr<dive_trip>> tripsToAdd;
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd;
|
||||
divesToAdd.reserve(divesAndSitesToDelete.dives.size());
|
||||
sitesToAdd.reserve(divesAndSitesToDelete.sites.size());
|
||||
|
||||
|
@ -135,16 +125,17 @@ DivesAndTripsToAdd DiveListBase::removeDives(DivesAndSitesToRemove &divesAndSite
|
|||
// Make sure that the dive list is sorted. The added dives will be sent in a signal
|
||||
// and the recipients assume that the dives are sorted the same way as they are
|
||||
// in the core list.
|
||||
std::sort(divesAndSitesToDelete.dives.begin(), divesAndSitesToDelete.dives.end(), dive_less_than);
|
||||
std::sort(divesAndSitesToDelete.dives.begin(), divesAndSitesToDelete.dives.end(),
|
||||
[](const dive *d1, const dive *d2) { return dive_less_than(*d1, *d2); });
|
||||
|
||||
for (dive *d: divesAndSitesToDelete.dives)
|
||||
divesToAdd.push_back(removeDive(d, tripsToAdd));
|
||||
divesAndSitesToDelete.dives.clear();
|
||||
|
||||
for (dive_site *ds: divesAndSitesToDelete.sites) {
|
||||
int idx = unregister_dive_site(ds);
|
||||
sitesToAdd.emplace_back(ds);
|
||||
emit diveListNotifier.diveSiteDeleted(ds, idx);
|
||||
auto res = divelog.sites.pull(ds);
|
||||
sitesToAdd.push_back(std::move(res.ptr));
|
||||
emit diveListNotifier.diveSiteDeleted(ds, res.idx);
|
||||
}
|
||||
divesAndSitesToDelete.sites.clear();
|
||||
|
||||
|
@ -159,7 +150,7 @@ DivesAndTripsToAdd DiveListBase::removeDives(DivesAndSitesToRemove &divesAndSite
|
|||
processByTrip(dives, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) {
|
||||
// Check if this trip is supposed to be deleted, by checking if it was marked as "add it".
|
||||
bool deleteTrip = trip &&
|
||||
std::find_if(tripsToAdd.begin(), tripsToAdd.end(), [trip](const OwningTripPtr &ptr)
|
||||
std::find_if(tripsToAdd.begin(), tripsToAdd.end(), [trip](const std::unique_ptr<dive_trip> &ptr)
|
||||
{ return ptr.get() == trip; }) != tripsToAdd.end();
|
||||
emit diveListNotifier.divesDeleted(trip, deleteTrip, divesInTrip);
|
||||
});
|
||||
|
@ -188,7 +179,7 @@ DivesAndSitesToRemove DiveListBase::addDives(DivesAndTripsToAdd &toAdd)
|
|||
// in the core list.
|
||||
std::sort(toAdd.dives.begin(), toAdd.dives.end(),
|
||||
[](const DiveToAdd &d, const DiveToAdd &d2)
|
||||
{ return dive_less_than(d.dive.get(), d2.dive.get()); });
|
||||
{ return dive_less_than(*d.dive, *d2.dive); });
|
||||
|
||||
// Now, add the dives
|
||||
// Note: the idiomatic STL-way would be std::transform, but let's use a loop since
|
||||
|
@ -209,17 +200,17 @@ DivesAndSitesToRemove DiveListBase::addDives(DivesAndTripsToAdd &toAdd)
|
|||
// Remember the pointers so that we can later check if a trip was newly added
|
||||
std::vector<dive_trip *> addedTrips;
|
||||
addedTrips.reserve(toAdd.trips.size());
|
||||
for (OwningTripPtr &trip: toAdd.trips) {
|
||||
addedTrips.push_back(trip.get());
|
||||
insert_trip(trip.release(), divelog.trips); // Return ownership to backend
|
||||
for (std::unique_ptr<dive_trip> &trip: toAdd.trips) {
|
||||
auto [t, idx] = divelog.trips.put(std::move(trip)); // Return ownership to backend
|
||||
addedTrips.push_back(t);
|
||||
}
|
||||
toAdd.trips.clear();
|
||||
|
||||
// Finally, add any necessary dive sites
|
||||
for (OwningDiveSitePtr &ds: toAdd.sites) {
|
||||
sites.push_back(ds.get());
|
||||
int idx = register_dive_site(ds.release()); // Return ownership to backend
|
||||
emit diveListNotifier.diveSiteAdded(sites.back(), idx);
|
||||
for (std::unique_ptr<dive_site> &ds: toAdd.sites) {
|
||||
auto res = divelog.sites.register_site(std::move(ds));
|
||||
sites.push_back(res.ptr);
|
||||
emit diveListNotifier.diveSiteAdded(sites.back(), res.idx);
|
||||
}
|
||||
toAdd.sites.clear();
|
||||
|
||||
|
@ -249,7 +240,7 @@ static void renumberDives(QVector<QPair<dive *, int>> &divesToRenumber)
|
|||
continue;
|
||||
std::swap(d->number, pair.second);
|
||||
dives.push_back(d);
|
||||
invalidate_dive_cache(d);
|
||||
d->invalidate_cache();
|
||||
}
|
||||
|
||||
// Send signals.
|
||||
|
@ -260,27 +251,25 @@ static void renumberDives(QVector<QPair<dive *, int>> &divesToRenumber)
|
|||
// passed-in structure. This means that calling the function twice on the same
|
||||
// object is a no-op concerning the dive. If the old trip was deleted from the
|
||||
// core, an owning pointer to the removed trip is returned, otherwise a null pointer.
|
||||
static OwningTripPtr moveDiveToTrip(DiveToTrip &diveToTrip)
|
||||
static std::unique_ptr<dive_trip> moveDiveToTrip(DiveToTrip &diveToTrip)
|
||||
{
|
||||
// Firstly, check if we move to the same trip and bail if this is a no-op.
|
||||
if (diveToTrip.trip == diveToTrip.dive->divetrip)
|
||||
return {};
|
||||
|
||||
// Remove from old trip
|
||||
OwningTripPtr res;
|
||||
std::unique_ptr<dive_trip> res;
|
||||
|
||||
// Remove dive from trip - if this is the last dive in the trip, remove the whole trip.
|
||||
dive_trip *trip = unregister_dive_from_trip(diveToTrip.dive);
|
||||
if (trip && trip->dives.nr == 0) {
|
||||
remove_trip_from_backend(trip); // Remove trip from backend
|
||||
res.reset(trip);
|
||||
}
|
||||
if (trip && trip->dives.empty())
|
||||
res = remove_trip_from_backend(trip); // Remove trip from backend
|
||||
|
||||
// Store old trip and get new trip we should associate this dive with
|
||||
std::swap(trip, diveToTrip.trip);
|
||||
if (trip)
|
||||
add_dive_to_trip(diveToTrip.dive, trip);
|
||||
invalidate_dive_cache(diveToTrip.dive); // Ensure that dive is written in git_save()
|
||||
trip->add_dive(diveToTrip.dive);
|
||||
diveToTrip.dive->invalidate_cache(); // Ensure that dive is written in git_save()
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -298,15 +287,14 @@ static void moveDivesBetweenTrips(DivesToTrip &dives)
|
|||
createdTrips.reserve(dives.tripsToAdd.size());
|
||||
|
||||
// First, bring back the trip(s)
|
||||
for (OwningTripPtr &trip: dives.tripsToAdd) {
|
||||
dive_trip *t = trip.release(); // Give up ownership
|
||||
for (std::unique_ptr<dive_trip> &trip: dives.tripsToAdd) {
|
||||
auto [t, idx] = divelog.trips.put(std::move(trip)); // Return ownership to backend
|
||||
createdTrips.push_back(t);
|
||||
insert_trip(t, divelog.trips); // Return ownership to backend
|
||||
}
|
||||
dives.tripsToAdd.clear();
|
||||
|
||||
for (DiveToTrip &dive: dives.divesToMove) {
|
||||
OwningTripPtr tripToAdd = moveDiveToTrip(dive);
|
||||
std::unique_ptr<dive_trip> tripToAdd = moveDiveToTrip(dive);
|
||||
// register trips that we'll have to readd
|
||||
if (tripToAdd)
|
||||
dives.tripsToAdd.push_back(std::move(tripToAdd));
|
||||
|
@ -350,7 +338,7 @@ static void moveDivesBetweenTrips(DivesToTrip &dives)
|
|||
std::find_if(divesMoved.begin() + j, divesMoved.end(), // Is this the last occurence of "from"?
|
||||
[from](const DiveMoved &entry) { return entry.from == from; }) == divesMoved.end() &&
|
||||
std::find_if(dives.tripsToAdd.begin(), dives.tripsToAdd.end(), // Is "from" in tripsToAdd?
|
||||
[from](const OwningTripPtr &trip) { return trip.get() == from; }) != dives.tripsToAdd.end();
|
||||
[from](const std::unique_ptr<dive_trip> &trip) { return trip.get() == from; }) != dives.tripsToAdd.end();
|
||||
// Check if the to-trip has to be created. For this purpose, we saved an array of trips to be created.
|
||||
bool createTo = false;
|
||||
if (to) {
|
||||
|
@ -399,43 +387,39 @@ void DiveListBase::redo()
|
|||
finishWork();
|
||||
}
|
||||
|
||||
AddDive::AddDive(dive *d, bool autogroup, bool newNumber)
|
||||
AddDive::AddDive(std::unique_ptr<dive> d, bool autogroup, bool newNumber)
|
||||
{
|
||||
setText(Command::Base::tr("add dive"));
|
||||
// By convention, d is a pointer to "displayed dive" or a temporary variable and can be overwritten.
|
||||
d->maxdepth.mm = 0;
|
||||
d->dc.maxdepth.mm = 0;
|
||||
fixup_dive(d);
|
||||
d->maxdepth = 0_m;
|
||||
d->dcs[0].maxdepth = 0_m;
|
||||
divelog.dives.fixup_dive(*d);
|
||||
|
||||
// this only matters if undoit were called before redoit
|
||||
currentDive = nullptr;
|
||||
|
||||
// Get an owning pointer to a moved dive.
|
||||
OwningDivePtr divePtr(move_dive(d));
|
||||
divePtr->selected = false; // If we clone a planned dive, it might have been selected.
|
||||
d->selected = false; // If we clone a planned dive, it might have been selected.
|
||||
// We have to clear the flag, as selections will be managed
|
||||
// on dive-addition.
|
||||
|
||||
// If we alloc a new-trip for autogrouping, get an owning pointer to it.
|
||||
OwningTripPtr allocTrip;
|
||||
dive_trip *trip = divePtr->divetrip;
|
||||
dive_site *site = divePtr->dive_site;
|
||||
std::unique_ptr<dive_trip> allocTrip;
|
||||
dive_trip *trip = d->divetrip;
|
||||
dive_site *site = d->dive_site;
|
||||
// We have to delete the pointers to trip and site, because this would prevent the core from adding to the
|
||||
// trip or site and we would get the count-of-dives in the trip or site wrong. Yes, that's all horribly subtle!
|
||||
divePtr->divetrip = nullptr;
|
||||
divePtr->dive_site = nullptr;
|
||||
d->divetrip = nullptr;
|
||||
d->dive_site = nullptr;
|
||||
if (!trip && autogroup) {
|
||||
bool alloc;
|
||||
trip = get_trip_for_new_dive(divePtr.get(), &alloc);
|
||||
if (alloc)
|
||||
allocTrip.reset(trip);
|
||||
auto [t, allocated] = get_trip_for_new_dive(divelog, d.get());
|
||||
trip = t;
|
||||
allocTrip = std::move(allocated);
|
||||
}
|
||||
|
||||
int idx = dive_table_get_insertion_index(divelog.dives, divePtr.get());
|
||||
int idx = divelog.dives.get_insertion_index(d.get());
|
||||
if (newNumber)
|
||||
divePtr->number = get_dive_nr_at_idx(idx);
|
||||
d->number = divelog.dives.get_dive_nr_at_idx(idx);
|
||||
|
||||
divesToAdd.dives.push_back({ std::move(divePtr), trip, site });
|
||||
divesToAdd.dives.push_back({ std::move(d), trip, site });
|
||||
if (allocTrip)
|
||||
divesToAdd.trips.push_back(std::move(allocTrip));
|
||||
}
|
||||
|
@ -452,7 +436,7 @@ void AddDive::redoit()
|
|||
currentDive = current_dive;
|
||||
|
||||
divesAndSitesToRemove = addDives(divesToAdd);
|
||||
sort_trip_table(divelog.trips); // Though unlikely, adding a dive may reorder trips
|
||||
divelog.trips.sort(); // Though unlikely, adding a dive may reorder trips
|
||||
|
||||
// Select the newly added dive
|
||||
setSelection(divesAndSitesToRemove.dives, divesAndSitesToRemove.dives[0], -1);
|
||||
|
@ -462,7 +446,7 @@ void AddDive::undoit()
|
|||
{
|
||||
// Simply remove the dive that was previously added...
|
||||
divesToAdd = removeDives(divesAndSitesToRemove);
|
||||
sort_trip_table(divelog.trips); // Though unlikely, removing a dive may reorder trips
|
||||
divelog.trips.sort(); // Though unlikely, removing a dive may reorder trips
|
||||
|
||||
// ...and restore the selection
|
||||
setSelection(selection, currentDive, -1);
|
||||
|
@ -470,33 +454,28 @@ void AddDive::undoit()
|
|||
|
||||
ImportDives::ImportDives(struct divelog *log, int flags, const QString &source)
|
||||
{
|
||||
setText(Command::Base::tr("import %n dive(s) from %1", "", log->dives->nr).arg(source));
|
||||
setText(Command::Base::tr("import %n dive(s) from %1", "", log->dives.size()).arg(source));
|
||||
|
||||
// this only matters if undoit were called before redoit
|
||||
currentDive = nullptr;
|
||||
|
||||
struct dive_table dives_to_add = empty_dive_table;
|
||||
struct dive_table dives_to_remove = empty_dive_table;
|
||||
struct trip_table trips_to_add = empty_trip_table;
|
||||
struct dive_site_table sites_to_add = empty_dive_site_table;
|
||||
process_imported_dives(log, flags,
|
||||
&dives_to_add, &dives_to_remove, &trips_to_add,
|
||||
&sites_to_add, &devicesToAddAndRemove);
|
||||
auto [dives_to_add, dives_to_remove, trips_to_add, sites_to_add, devices_to_add] =
|
||||
divelog.process_imported_dives(*log, flags);
|
||||
|
||||
// Add devices to devicesToAddAndRemove structure
|
||||
devicesToAddAndRemove = std::move(devices_to_add);
|
||||
|
||||
// Add trips to the divesToAdd.trips structure
|
||||
divesToAdd.trips.reserve(trips_to_add.nr);
|
||||
for (int i = 0; i < trips_to_add.nr; ++i)
|
||||
divesToAdd.trips.emplace_back(trips_to_add.trips[i]);
|
||||
divesToAdd.trips.reserve(trips_to_add.size());
|
||||
for (auto &trip: trips_to_add)
|
||||
divesToAdd.trips.push_back(std::move(trip));
|
||||
|
||||
// Add sites to the divesToAdd.sites structure
|
||||
divesToAdd.sites.reserve(sites_to_add.nr);
|
||||
for (int i = 0; i < sites_to_add.nr; ++i)
|
||||
divesToAdd.sites.emplace_back(sites_to_add.dive_sites[i]);
|
||||
divesToAdd.sites = std::move(sites_to_add);
|
||||
|
||||
// Add dives to the divesToAdd.dives structure
|
||||
divesToAdd.dives.reserve(dives_to_add.nr);
|
||||
for (int i = 0; i < dives_to_add.nr; ++i) {
|
||||
OwningDivePtr divePtr(dives_to_add.dives[i]);
|
||||
divesToAdd.dives.reserve(dives_to_add.size());
|
||||
for (auto &divePtr: dives_to_add) {
|
||||
divePtr->selected = false; // See above in AddDive::AddDive()
|
||||
dive_trip *trip = divePtr->divetrip;
|
||||
divePtr->divetrip = nullptr; // See above in AddDive::AddDive()
|
||||
|
@ -507,25 +486,18 @@ ImportDives::ImportDives(struct divelog *log, int flags, const QString &source)
|
|||
}
|
||||
|
||||
// Add dive to be deleted to the divesToRemove structure
|
||||
divesAndSitesToRemove.dives.reserve(dives_to_remove.nr);
|
||||
for (int i = 0; i < dives_to_remove.nr; ++i)
|
||||
divesAndSitesToRemove.dives.push_back(dives_to_remove.dives[i]);
|
||||
divesAndSitesToRemove.dives = std::move(dives_to_remove);
|
||||
|
||||
// When encountering filter presets with equal names, check whether they are
|
||||
// the same. If they are, ignore them.
|
||||
for (const filter_preset &preset: *log->filter_presets) {
|
||||
for (const filter_preset &preset: log->filter_presets) {
|
||||
std::string name = preset.name;
|
||||
auto it = std::find_if(divelog.filter_presets->begin(), divelog.filter_presets->end(),
|
||||
auto it = std::find_if(divelog.filter_presets.begin(), divelog.filter_presets.end(),
|
||||
[&name](const filter_preset &preset) { return preset.name == name; });
|
||||
if (it != divelog.filter_presets->end() && it->data == preset.data)
|
||||
if (it != divelog.filter_presets.end() && it->data == preset.data)
|
||||
continue;
|
||||
filterPresetsToAdd.emplace_back(preset.name, preset.data);
|
||||
}
|
||||
|
||||
free(dives_to_add.dives);
|
||||
free(dives_to_remove.dives);
|
||||
free(trips_to_add.trips);
|
||||
free(sites_to_add.dive_sites);
|
||||
}
|
||||
|
||||
bool ImportDives::workToBeDone()
|
||||
|
@ -551,12 +523,12 @@ void ImportDives::redoit()
|
|||
divesAndSitesToRemove = std::move(divesAndSitesToRemoveNew);
|
||||
|
||||
// Add devices
|
||||
for (const device &dev: devicesToAddAndRemove.devices)
|
||||
add_to_device_table(divelog.devices, &dev);
|
||||
for (const device &dev: devicesToAddAndRemove)
|
||||
add_to_device_table(divelog.devices, dev);
|
||||
|
||||
// Add new filter presets
|
||||
for (auto &it: filterPresetsToAdd) {
|
||||
filterPresetsToRemove.push_back(filter_preset_add(it.first, it.second));
|
||||
filterPresetsToRemove.push_back(divelog.filter_presets.add(it.first, it.second));
|
||||
emit diveListNotifier.filterPresetAdded(filterPresetsToRemove.back());
|
||||
}
|
||||
filterPresetsToAdd.clear();
|
||||
|
@ -579,15 +551,16 @@ void ImportDives::undoit()
|
|||
setSelection(selection, currentDive, -1);
|
||||
|
||||
// Remove devices
|
||||
for (const device &dev: devicesToAddAndRemove.devices)
|
||||
remove_device(divelog.devices, &dev);
|
||||
for (const device &dev: devicesToAddAndRemove)
|
||||
remove_device(divelog.devices, dev);
|
||||
|
||||
// Remove filter presets. Do this in reverse order.
|
||||
for (auto it = filterPresetsToRemove.rbegin(); it != filterPresetsToRemove.rend(); ++it) {
|
||||
int index = *it;
|
||||
std::string oldName = filter_preset_name(index);
|
||||
FilterData oldData = filter_preset_get(index);
|
||||
filter_preset_delete(index);
|
||||
const filter_preset &preset = divelog.filter_presets[index];
|
||||
std::string oldName = preset.name;
|
||||
FilterData oldData = preset.data;
|
||||
divelog.filter_presets.remove(index);
|
||||
emit diveListNotifier.filterPresetRemoved(index);
|
||||
filterPresetsToAdd.emplace_back(oldName, oldData);
|
||||
}
|
||||
|
@ -609,7 +582,7 @@ bool DeleteDive::workToBeDone()
|
|||
void DeleteDive::undoit()
|
||||
{
|
||||
divesToDelete = addDives(divesToAdd);
|
||||
sort_trip_table(divelog.trips); // Though unlikely, removing a dive may reorder trips
|
||||
divelog.trips.sort(); // Though unlikely, removing a dive may reorder trips
|
||||
|
||||
// Select all re-added dives and make the first one current
|
||||
dive *currentDive = !divesToDelete.dives.empty() ? divesToDelete.dives[0] : nullptr;
|
||||
|
@ -619,13 +592,13 @@ void DeleteDive::undoit()
|
|||
void DeleteDive::redoit()
|
||||
{
|
||||
divesToAdd = removeDives(divesToDelete);
|
||||
sort_trip_table(divelog.trips); // Though unlikely, adding a dive may reorder trips
|
||||
divelog.trips.sort(); // Though unlikely, adding a dive may reorder trips
|
||||
|
||||
// Deselect all dives and select dive that was close to the first deleted dive
|
||||
dive *newCurrent = nullptr;
|
||||
if (!divesToAdd.dives.empty()) {
|
||||
timestamp_t when = divesToAdd.dives[0].dive->when;
|
||||
newCurrent = find_next_visible_dive(when);
|
||||
newCurrent = divelog.dives.find_next_visible_dive(when);
|
||||
}
|
||||
select_single_dive(newCurrent);
|
||||
}
|
||||
|
@ -647,10 +620,10 @@ void ShiftTime::redoit()
|
|||
}
|
||||
|
||||
// Changing times may have unsorted the dive and trip tables
|
||||
sort_dive_table(divelog.dives);
|
||||
sort_trip_table(divelog.trips);
|
||||
divelog.dives.sort();
|
||||
divelog.trips.sort();
|
||||
for (dive_trip *trip: trips)
|
||||
sort_dive_table(&trip->dives); // Keep the trip-table in order
|
||||
trip->sort_dives();
|
||||
|
||||
// Send signals
|
||||
QVector<dive *> dives = stdToQt<dive *>(diveList);
|
||||
|
@ -716,7 +689,7 @@ bool TripBase::workToBeDone()
|
|||
void TripBase::redoit()
|
||||
{
|
||||
moveDivesBetweenTrips(divesToMove);
|
||||
sort_trip_table(divelog.trips); // Though unlikely, moving dives may reorder trips
|
||||
divelog.trips.sort(); // Though unlikely, moving dives may reorder trips
|
||||
|
||||
// Select the moved dives
|
||||
std::vector<dive *> dives;
|
||||
|
@ -754,11 +727,9 @@ RemoveAutogenTrips::RemoveAutogenTrips()
|
|||
{
|
||||
setText(Command::Base::tr("remove autogenerated trips"));
|
||||
// TODO: don't touch core-innards directly
|
||||
int i;
|
||||
struct dive *dive;
|
||||
for_each_dive(i, dive) {
|
||||
if (dive->divetrip && dive->divetrip->autogen)
|
||||
divesToMove.divesToMove.push_back( {dive, nullptr} );
|
||||
for (auto &d: divelog.dives) {
|
||||
if (d->divetrip && d->divetrip->autogen)
|
||||
divesToMove.divesToMove.push_back( {d.get(), nullptr} );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -776,25 +747,22 @@ CreateTrip::CreateTrip(const QVector<dive *> &divesToAddIn)
|
|||
if (divesToAddIn.isEmpty())
|
||||
return;
|
||||
|
||||
dive_trip *trip = create_trip_from_dive(divesToAddIn[0]);
|
||||
divesToMove.tripsToAdd.emplace_back(trip);
|
||||
auto trip = create_trip_from_dive(divesToAddIn[0]);
|
||||
for (dive *d: divesToAddIn)
|
||||
divesToMove.divesToMove.push_back( {d, trip} );
|
||||
divesToMove.divesToMove.push_back( { d, trip.get() });
|
||||
divesToMove.tripsToAdd.push_back(std::move(trip));
|
||||
}
|
||||
|
||||
AutogroupDives::AutogroupDives()
|
||||
{
|
||||
setText(Command::Base::tr("autogroup dives"));
|
||||
|
||||
dive_trip *trip;
|
||||
bool alloc;
|
||||
int from, to;
|
||||
for(int i = 0; (trip = get_dives_to_autogroup(divelog.dives, i, &from, &to, &alloc)) != NULL; i = to) {
|
||||
for (auto &entry: get_dives_to_autogroup(divelog.dives)) {
|
||||
// If this is an allocated trip, take ownership
|
||||
if (alloc)
|
||||
divesToMove.tripsToAdd.emplace_back(trip);
|
||||
for (int j = from; j < to; ++j)
|
||||
divesToMove.divesToMove.push_back( { get_dive(j), trip } );
|
||||
if (entry.created_trip)
|
||||
divesToMove.tripsToAdd.push_back(std::move(entry.created_trip));
|
||||
for (auto it = divelog.dives.begin() + entry.from; it != divelog.dives.begin() + entry.to; ++it)
|
||||
divesToMove.divesToMove.push_back( { it->get(), entry.trip } );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -802,18 +770,15 @@ MergeTrips::MergeTrips(dive_trip *trip1, dive_trip *trip2)
|
|||
{
|
||||
if (trip1 == trip2)
|
||||
return;
|
||||
dive_trip *newTrip = combine_trips(trip1, trip2);
|
||||
divesToMove.tripsToAdd.emplace_back(newTrip);
|
||||
for (int i = 0; i < trip1->dives.nr; ++i)
|
||||
divesToMove.divesToMove.push_back( { trip1->dives.dives[i], newTrip } );
|
||||
for (int i = 0; i < trip2->dives.nr; ++i)
|
||||
divesToMove.divesToMove.push_back( { trip2->dives.dives[i], newTrip } );
|
||||
std::unique_ptr<dive_trip> newTrip = combine_trips(trip1, trip2);
|
||||
for (dive *d: trip1->dives)
|
||||
divesToMove.divesToMove.push_back( { d, newTrip.get() } );
|
||||
for (dive *d: trip2->dives)
|
||||
divesToMove.divesToMove.push_back( { d, newTrip.get() } );
|
||||
divesToMove.tripsToAdd.push_back(std::move(newTrip));
|
||||
}
|
||||
|
||||
// std::array<dive *, 2> is the same as struct *dive[2], with the fundamental
|
||||
// difference that it can be returned from functions. Thus, this constructor
|
||||
// can be chained with the result of a function.
|
||||
SplitDivesBase::SplitDivesBase(dive *d, std::array<dive *, 2> newDives)
|
||||
SplitDivesBase::SplitDivesBase(dive *d, std::array<std::unique_ptr<dive>, 2> newDives)
|
||||
{
|
||||
// If either of the new dives is null, simply return. Empty arrays indicate that nothing is to be done.
|
||||
if (!newDives[0] || !newDives[1])
|
||||
|
@ -833,10 +798,10 @@ SplitDivesBase::SplitDivesBase(dive *d, std::array<dive *, 2> newDives)
|
|||
|
||||
diveToSplit.dives.push_back(d);
|
||||
splitDives.dives.resize(2);
|
||||
splitDives.dives[0].dive.reset(newDives[0]);
|
||||
splitDives.dives[0].dive = std::move(newDives[0]);
|
||||
splitDives.dives[0].trip = d->divetrip;
|
||||
splitDives.dives[0].site = d->dive_site;
|
||||
splitDives.dives[1].dive.reset(newDives[1]);
|
||||
splitDives.dives[1].dive = std::move(newDives[1]);
|
||||
splitDives.dives[1].trip = d->divetrip;
|
||||
splitDives.dives[1].site = d->dive_site;
|
||||
}
|
||||
|
@ -865,16 +830,13 @@ void SplitDivesBase::undoit()
|
|||
setSelection(diveToSplit.dives, diveToSplit.dives[0], -1);
|
||||
}
|
||||
|
||||
static std::array<dive *, 2> doSplitDives(const dive *d, duration_t time)
|
||||
static std::array<std::unique_ptr<dive>, 2> doSplitDives(const dive *d, duration_t time)
|
||||
{
|
||||
// Split the dive
|
||||
dive *new1, *new2;
|
||||
if (time.seconds < 0)
|
||||
split_dive(d, &new1, &new2);
|
||||
return divelog.dives.split_dive(*d);
|
||||
else
|
||||
split_dive_at_time(d, time, &new1, &new2);
|
||||
|
||||
return { new1, new2 };
|
||||
return divelog.dives.split_dive_at_time(*d, time);
|
||||
}
|
||||
|
||||
SplitDives::SplitDives(dive *d, duration_t time) : SplitDivesBase(d, doSplitDives(d, time))
|
||||
|
@ -882,25 +844,13 @@ SplitDives::SplitDives(dive *d, duration_t time) : SplitDivesBase(d, doSplitDive
|
|||
setText(Command::Base::tr("split dive"));
|
||||
}
|
||||
|
||||
static std::array<dive *, 2> splitDiveComputer(const dive *d, int dc_num)
|
||||
{
|
||||
// Refuse to do anything if the dive has only one dive computer.
|
||||
// Yes, this should have been checked by the UI, but let's just make sure.
|
||||
if (!d->dc.next)
|
||||
return { nullptr, nullptr};
|
||||
|
||||
dive *new1, *new2;
|
||||
split_divecomputer(d, dc_num, &new1, &new2);
|
||||
|
||||
return { new1, new2 };
|
||||
}
|
||||
|
||||
SplitDiveComputer::SplitDiveComputer(dive *d, int dc_num) : SplitDivesBase(d, splitDiveComputer(d, dc_num))
|
||||
SplitDiveComputer::SplitDiveComputer(dive *d, int dc_num) :
|
||||
SplitDivesBase(d, divelog.dives.split_divecomputer(*d, dc_num))
|
||||
{
|
||||
setText(Command::Base::tr("split dive computer"));
|
||||
}
|
||||
|
||||
DiveComputerBase::DiveComputerBase(dive *old_dive, dive *new_dive, int dc_nr_before, int dc_nr_after) :
|
||||
DiveComputerBase::DiveComputerBase(dive *old_dive, std::unique_ptr<dive> new_dive, int dc_nr_before, int dc_nr_after) :
|
||||
dc_nr_before(dc_nr_before),
|
||||
dc_nr_after(dc_nr_after)
|
||||
{
|
||||
|
@ -920,7 +870,7 @@ DiveComputerBase::DiveComputerBase(dive *old_dive, dive *new_dive, int dc_nr_bef
|
|||
new_dive->dive_site = nullptr;
|
||||
|
||||
diveToAdd.dives.resize(1);
|
||||
diveToAdd.dives[0].dive.reset(new_dive);
|
||||
diveToAdd.dives[0].dive = std::move(new_dive);
|
||||
diveToAdd.dives[0].trip = old_dive->divetrip;
|
||||
diveToAdd.dives[0].site = old_dive->dive_site;
|
||||
}
|
||||
|
@ -950,49 +900,30 @@ void DiveComputerBase::undoit()
|
|||
}
|
||||
|
||||
MoveDiveComputerToFront::MoveDiveComputerToFront(dive *d, int dc_num)
|
||||
: DiveComputerBase(d, make_first_dc(d, dc_num), dc_num, 0)
|
||||
: DiveComputerBase(d, clone_make_first_dc(*d, dc_num), dc_num, 0)
|
||||
{
|
||||
setText(Command::Base::tr("move dive computer to front"));
|
||||
}
|
||||
|
||||
DeleteDiveComputer::DeleteDiveComputer(dive *d, int dc_num)
|
||||
: DiveComputerBase(d, clone_delete_divecomputer(d, dc_num), dc_num, std::min((int)number_of_computers(d) - 1, dc_num))
|
||||
: DiveComputerBase(d, divelog.dives.clone_delete_divecomputer(*d, dc_num),
|
||||
dc_num, std::min(d->number_of_computers() - 1, dc_num))
|
||||
{
|
||||
setText(Command::Base::tr("delete dive computer"));
|
||||
}
|
||||
|
||||
MergeDives::MergeDives(const QVector <dive *> &dives)
|
||||
MergeDives::MergeDives(const QVector <dive *> &dives) : site(nullptr)
|
||||
{
|
||||
setText(Command::Base::tr("merge dive"));
|
||||
|
||||
// Just a safety check - if there's not two or more dives - do nothing
|
||||
// Just a safety check - if there's not two or more dives - do nothing.
|
||||
// The caller should have made sure that this doesn't happen.
|
||||
if (dives.count() < 2) {
|
||||
qWarning("Merging less than two dives");
|
||||
return;
|
||||
}
|
||||
|
||||
dive_trip *preferred_trip;
|
||||
dive_site *preferred_site;
|
||||
OwningDivePtr d(merge_dives(dives[0], dives[1], dives[1]->when - dives[0]->when, false, &preferred_trip, &preferred_site));
|
||||
|
||||
// Currently, the core code selects the dive -> this is not what we want, as
|
||||
// we manually manage the selection post-command.
|
||||
// TODO: Remove selection code from core.
|
||||
d->selected = false;
|
||||
|
||||
// Set the preferred dive trip, so that for subsequent merges the better trip can be selected
|
||||
d->divetrip = preferred_trip;
|
||||
for (int i = 2; i < dives.count(); ++i) {
|
||||
d.reset(merge_dives(d.get(), dives[i], dives[i]->when - d->when, false, &preferred_trip, &preferred_site));
|
||||
// Set the preferred dive trip and site, so that for subsequent merges the better trip and site can be selected
|
||||
d->divetrip = preferred_trip;
|
||||
d->dive_site = preferred_site;
|
||||
}
|
||||
|
||||
// We got our preferred trip and site, so now the references can be deleted from the newly generated dive
|
||||
d->divetrip = nullptr;
|
||||
d->dive_site = nullptr;
|
||||
auto [d, set_location] = divelog.dives.merge_dives(qtToStd(dives));
|
||||
|
||||
// The merged dive gets the number of the first dive with a non-zero number
|
||||
for (const dive *dive: dives) {
|
||||
|
@ -1005,9 +936,9 @@ MergeDives::MergeDives(const QVector <dive *> &dives)
|
|||
// We will only renumber the remaining dives if the joined dives are consecutive.
|
||||
// Otherwise all bets are off concerning what the user wanted and doing nothing seems
|
||||
// like the best option.
|
||||
int idx = get_divenr(dives[0]);
|
||||
int num = dives.count();
|
||||
if (idx < 0 || idx + num > divelog.dives->nr) {
|
||||
size_t idx = divelog.dives.get_idx(dives[0]);
|
||||
size_t num = dives.count();
|
||||
if (idx == std::string::npos) {
|
||||
// It was the callers responsibility to pass only known dives.
|
||||
// Something is seriously wrong - give up.
|
||||
qWarning("Merging unknown dives");
|
||||
|
@ -1015,7 +946,8 @@ MergeDives::MergeDives(const QVector <dive *> &dives)
|
|||
}
|
||||
// std::equal compares two ranges. The parameters are (begin_range1, end_range1, begin_range2).
|
||||
// Here, we can compare C-arrays, because QVector guarantees contiguous storage.
|
||||
if (std::equal(&dives[0], &dives[0] + num, &divelog.dives->dives[idx]) &&
|
||||
if (std::equal(&dives[0], &dives[0] + num, divelog.dives.begin() + idx, [](dive *d1,
|
||||
const std::unique_ptr<dive> &d2) { return d1 == d2.get(); }) &&
|
||||
dives[0]->number && dives.last()->number && dives[0]->number < dives.last()->number) {
|
||||
// We have a consecutive set of dives. Rename all following dives according to the
|
||||
// number of erased dives. This considers that there might be missing numbers.
|
||||
|
@ -1031,24 +963,33 @@ MergeDives::MergeDives(const QVector <dive *> &dives)
|
|||
// consecutive, and the difference will be 1, so the
|
||||
// above example is not supposed to be normal.
|
||||
int diff = dives.last()->number - dives[0]->number;
|
||||
divesToRenumber.reserve(divelog.dives->nr - idx - num);
|
||||
int previousnr = dives[0]->number;
|
||||
for (int i = idx + num; i < divelog.dives->nr; ++i) {
|
||||
int newnr = divelog.dives->dives[i]->number - diff;
|
||||
for (size_t i = idx + num; i < divelog.dives.size(); ++i) {
|
||||
int newnr = divelog.dives[i]->number - diff;
|
||||
|
||||
// Stop renumbering if stuff isn't in order (see also core/divelist.c)
|
||||
if (newnr <= previousnr)
|
||||
break;
|
||||
divesToRenumber.append(QPair<dive *,int>(divelog.dives->dives[i], newnr));
|
||||
divesToRenumber.append(QPair<dive *,int>(divelog.dives[i].get(), newnr));
|
||||
previousnr = newnr;
|
||||
}
|
||||
}
|
||||
|
||||
mergedDive.dives.resize(1);
|
||||
mergedDive.dives[0].dive = std::move(d);
|
||||
mergedDive.dives[0].trip = preferred_trip;
|
||||
mergedDive.dives[0].site = preferred_site;
|
||||
mergedDive.dives[0].trip = d->divetrip;
|
||||
mergedDive.dives[0].site = d->dive_site;
|
||||
divesToMerge.dives = std::vector<dive *>(dives.begin(), dives.end());
|
||||
|
||||
if (set_location.has_value() && d->dive_site) {
|
||||
location = *set_location;
|
||||
site = d->dive_site;
|
||||
}
|
||||
|
||||
// We got our preferred trip and site, so now the references can be deleted from the newly generated dive
|
||||
d->divetrip = nullptr;
|
||||
d->dive_site = nullptr;
|
||||
|
||||
mergedDive.dives[0].dive = std::move(d);
|
||||
}
|
||||
|
||||
bool MergeDives::workToBeDone()
|
||||
|
@ -1056,11 +997,20 @@ bool MergeDives::workToBeDone()
|
|||
return !mergedDive.dives.empty();
|
||||
}
|
||||
|
||||
void MergeDives::swapDivesite()
|
||||
{
|
||||
if (!site)
|
||||
return;
|
||||
std::swap(location, site->location);
|
||||
emit diveListNotifier.diveSiteChanged(site, LocationInformationModel::LOCATION); // Inform frontend of changed dive site.
|
||||
}
|
||||
|
||||
void MergeDives::redoit()
|
||||
{
|
||||
renumberDives(divesToRenumber);
|
||||
diveToUnmerge = addDives(mergedDive);
|
||||
unmergedDives = removeDives(divesToMerge);
|
||||
swapDivesite();
|
||||
|
||||
// Select merged dive and make it current
|
||||
setSelection(diveToUnmerge.dives, diveToUnmerge.dives[0], -1);
|
||||
|
@ -1071,6 +1021,7 @@ void MergeDives::undoit()
|
|||
divesToMerge = addDives(unmergedDives);
|
||||
mergedDive = removeDives(diveToUnmerge);
|
||||
renumberDives(divesToRenumber);
|
||||
swapDivesite();
|
||||
|
||||
// Select unmerged dives and make first one current
|
||||
setSelection(divesToMerge.dives, divesToMerge.dives[0], -1);
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Command {
|
|||
|
||||
// This helper structure describes a dive that we want to add.
|
||||
struct DiveToAdd {
|
||||
OwningDivePtr dive; // Dive to add
|
||||
std::unique_ptr<struct dive> dive; // Dive to add
|
||||
dive_trip *trip; // Trip the dive belongs to, may be null
|
||||
dive_site *site; // Site the dive is associated with, may be null
|
||||
};
|
||||
|
@ -23,8 +23,8 @@ struct DiveToAdd {
|
|||
// Multiple trips, dives and dive sites that have to be added for a command
|
||||
struct DivesAndTripsToAdd {
|
||||
std::vector<DiveToAdd> dives;
|
||||
std::vector<OwningTripPtr> trips;
|
||||
std::vector<OwningDiveSitePtr> sites;
|
||||
std::vector<std::unique_ptr<dive_trip>> trips;
|
||||
std::vector<std::unique_ptr<dive_site>> sites;
|
||||
};
|
||||
|
||||
// Dives and sites that have to be removed for a command
|
||||
|
@ -48,7 +48,7 @@ struct DiveToTrip
|
|||
struct DivesToTrip
|
||||
{
|
||||
std::vector<DiveToTrip> divesToMove; // If dive_trip is null, remove from trip
|
||||
std::vector<OwningTripPtr> tripsToAdd;
|
||||
std::vector<std::unique_ptr<dive_trip>> tripsToAdd;
|
||||
};
|
||||
|
||||
// All divelist commands derive from a common base class. It keeps track
|
||||
|
@ -58,7 +58,7 @@ struct DivesToTrip
|
|||
class DiveListBase : public Base {
|
||||
protected:
|
||||
// These are helper functions to add / remove dive from the C-core structures.
|
||||
DiveToAdd removeDive(struct dive *d, std::vector<OwningTripPtr> &tripsToAdd);
|
||||
DiveToAdd removeDive(struct dive *d, std::vector<std::unique_ptr<dive_trip>> &tripsToAdd);
|
||||
dive *addDive(DiveToAdd &d);
|
||||
DivesAndTripsToAdd removeDives(DivesAndSitesToRemove &divesAndSitesToDelete);
|
||||
DivesAndSitesToRemove addDives(DivesAndTripsToAdd &toAdd);
|
||||
|
@ -79,7 +79,7 @@ private:
|
|||
|
||||
class AddDive : public DiveListBase {
|
||||
public:
|
||||
AddDive(dive *dive, bool autogroup, bool newNumber);
|
||||
AddDive(std::unique_ptr<struct dive> dive, bool autogroup, bool newNumber);
|
||||
private:
|
||||
void undoit() override;
|
||||
void redoit() override;
|
||||
|
@ -108,10 +108,10 @@ private:
|
|||
// For redo and undo
|
||||
DivesAndTripsToAdd divesToAdd;
|
||||
DivesAndSitesToRemove divesAndSitesToRemove;
|
||||
struct device_table devicesToAddAndRemove;
|
||||
device_table devicesToAddAndRemove;
|
||||
|
||||
// For redo
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd;
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd;
|
||||
std::vector<std::pair<std::string,FilterData>>
|
||||
filterPresetsToAdd;
|
||||
|
||||
|
@ -133,7 +133,7 @@ private:
|
|||
// For redo
|
||||
DivesAndSitesToRemove divesToDelete;
|
||||
|
||||
std::vector<OwningTripPtr> tripsToAdd;
|
||||
std::vector<std::unique_ptr<dive_trip>> tripsToAdd;
|
||||
DivesAndTripsToAdd divesToAdd;
|
||||
};
|
||||
|
||||
|
@ -196,7 +196,7 @@ struct MergeTrips : public TripBase {
|
|||
|
||||
class SplitDivesBase : public DiveListBase {
|
||||
protected:
|
||||
SplitDivesBase(dive *old, std::array<dive *, 2> newDives);
|
||||
SplitDivesBase(dive *old, std::array<std::unique_ptr<dive>, 2> newDives);
|
||||
private:
|
||||
void undoit() override;
|
||||
void redoit() override;
|
||||
|
@ -237,7 +237,7 @@ class DiveComputerBase : public DiveListBase {
|
|||
protected:
|
||||
// old_dive must be a dive known to the core.
|
||||
// new_dive must be new dive whose ownership is taken.
|
||||
DiveComputerBase(dive *old_dive, dive *new_dive, int dc_nr_before, int dc_nr_after);
|
||||
DiveComputerBase(dive *old_dive, std::unique_ptr<dive> new_dive, int dc_nr_before, int dc_nr_after);
|
||||
private:
|
||||
void undoit() override;
|
||||
void redoit() override;
|
||||
|
@ -267,6 +267,7 @@ private:
|
|||
void undoit() override;
|
||||
void redoit() override;
|
||||
bool workToBeDone() override;
|
||||
void swapDivesite(); // Common code for undo and redo.
|
||||
|
||||
// For redo
|
||||
// Add one and remove a batch of dives
|
||||
|
@ -284,6 +285,8 @@ private:
|
|||
|
||||
// For undo and redo
|
||||
QVector<QPair<dive *, int>> divesToRenumber;
|
||||
dive_site *site;
|
||||
location_t location;
|
||||
};
|
||||
|
||||
} // namespace Command
|
||||
|
|
|
@ -15,25 +15,24 @@ namespace Command {
|
|||
|
||||
// Add a set of dive sites to the core. The dives that were associated with
|
||||
// that dive site will be restored to that dive site.
|
||||
static std::vector<dive_site *> addDiveSites(std::vector<OwningDiveSitePtr> &sites)
|
||||
static std::vector<dive_site *> addDiveSites(std::vector<std::unique_ptr<dive_site>> &sites)
|
||||
{
|
||||
std::vector<dive_site *> res;
|
||||
QVector<dive *> changedDives;
|
||||
res.reserve(sites.size());
|
||||
|
||||
for (OwningDiveSitePtr &ds: sites) {
|
||||
for (std::unique_ptr<dive_site> &ds: sites) {
|
||||
// Readd the dives that belonged to this site
|
||||
for (int i = 0; i < ds->dives.nr; ++i) {
|
||||
for (dive *d: ds->dives) {
|
||||
// TODO: send dive site changed signal
|
||||
struct dive *d = ds->dives.dives[i];
|
||||
d->dive_site = ds.get();
|
||||
changedDives.push_back(d);
|
||||
}
|
||||
|
||||
// Add dive site to core, but remember a non-owning pointer first.
|
||||
res.push_back(ds.get());
|
||||
int idx = register_dive_site(ds.release()); // Return ownership to backend.
|
||||
emit diveListNotifier.diveSiteAdded(res.back(), idx); // Inform frontend of new dive site.
|
||||
auto add_res = divelog.sites.put(std::move(ds)); // Return ownership to backend.
|
||||
res.push_back(add_res.ptr);
|
||||
emit diveListNotifier.diveSiteAdded(res.back(), add_res.idx); // Inform frontend of new dive site.
|
||||
}
|
||||
|
||||
emit diveListNotifier.divesChanged(changedDives, DiveField::DIVESITE);
|
||||
|
@ -47,24 +46,23 @@ static std::vector<dive_site *> addDiveSites(std::vector<OwningDiveSitePtr> &sit
|
|||
// Remove a set of dive sites. Get owning pointers to them. The dives are set to
|
||||
// being at no dive site, but the dive site will retain a list of dives, so
|
||||
// that the dives can be readded to the site on undo.
|
||||
static std::vector<OwningDiveSitePtr> removeDiveSites(std::vector<dive_site *> &sites)
|
||||
static std::vector<std::unique_ptr<dive_site>> removeDiveSites(std::vector<dive_site *> &sites)
|
||||
{
|
||||
std::vector<OwningDiveSitePtr> res;
|
||||
std::vector<std::unique_ptr<dive_site>> res;
|
||||
QVector<dive *> changedDives;
|
||||
res.reserve(sites.size());
|
||||
|
||||
for (dive_site *ds: sites) {
|
||||
// Reset the dive_site field of the affected dives
|
||||
for (int i = 0; i < ds->dives.nr; ++i) {
|
||||
struct dive *d = ds->dives.dives[i];
|
||||
for (dive *d: ds->dives) {
|
||||
d->dive_site = nullptr;
|
||||
changedDives.push_back(d);
|
||||
}
|
||||
|
||||
// Remove dive site from core and take ownership.
|
||||
int idx = unregister_dive_site(ds);
|
||||
res.emplace_back(ds);
|
||||
emit diveListNotifier.diveSiteDeleted(ds, idx); // Inform frontend of removed dive site.
|
||||
auto pull_res = divelog.sites.pull(ds);
|
||||
res.push_back(std::move(pull_res.ptr));
|
||||
emit diveListNotifier.diveSiteDeleted(ds, pull_res.idx); // Inform frontend of removed dive site.
|
||||
}
|
||||
|
||||
emit diveListNotifier.divesChanged(changedDives, DiveField::DIVESITE);
|
||||
|
@ -77,8 +75,8 @@ static std::vector<OwningDiveSitePtr> removeDiveSites(std::vector<dive_site *> &
|
|||
AddDiveSite::AddDiveSite(const QString &name)
|
||||
{
|
||||
setText(Command::Base::tr("add dive site"));
|
||||
sitesToAdd.emplace_back(alloc_dive_site());
|
||||
sitesToAdd.back()->name = copy_qstring(name);
|
||||
sitesToAdd.push_back(std::make_unique<dive_site>());
|
||||
sitesToAdd.back()->name = name.toStdString();
|
||||
}
|
||||
|
||||
bool AddDiveSite::workToBeDone()
|
||||
|
@ -96,25 +94,17 @@ void AddDiveSite::undo()
|
|||
sitesToAdd = removeDiveSites(sitesToRemove);
|
||||
}
|
||||
|
||||
ImportDiveSites::ImportDiveSites(struct dive_site_table *sites, const QString &source)
|
||||
ImportDiveSites::ImportDiveSites(dive_site_table sites, const QString &source)
|
||||
{
|
||||
setText(Command::Base::tr("import dive sites from %1").arg(source));
|
||||
|
||||
for (int i = 0; i < sites->nr; ++i) {
|
||||
struct dive_site *new_ds = sites->dive_sites[i];
|
||||
|
||||
// Don't import dive sites that already exist. Currently we only check for
|
||||
// the same name. We might want to be smarter here and merge dive site data, etc.
|
||||
struct dive_site *old_ds = get_same_dive_site(new_ds);
|
||||
if (old_ds) {
|
||||
free_dive_site(new_ds);
|
||||
for (auto &new_ds: sites) {
|
||||
// Don't import dive sites that already exist.
|
||||
// We might want to be smarter here and merge dive site data, etc.
|
||||
if (divelog.sites.get_same(*new_ds))
|
||||
continue;
|
||||
sitesToAdd.push_back(std::move(new_ds));
|
||||
}
|
||||
sitesToAdd.emplace_back(new_ds);
|
||||
}
|
||||
|
||||
// All site have been consumed
|
||||
sites->nr = 0;
|
||||
}
|
||||
|
||||
bool ImportDiveSites::workToBeDone()
|
||||
|
@ -155,10 +145,9 @@ void DeleteDiveSites::undo()
|
|||
PurgeUnusedDiveSites::PurgeUnusedDiveSites()
|
||||
{
|
||||
setText(Command::Base::tr("purge unused dive sites"));
|
||||
for (int i = 0; i < divelog.sites->nr; ++i) {
|
||||
dive_site *ds = divelog.sites->dive_sites[i];
|
||||
if (ds->dives.nr == 0)
|
||||
sitesToRemove.push_back(ds);
|
||||
for (const auto &ds: divelog.sites) {
|
||||
if (ds->dives.empty())
|
||||
sitesToRemove.push_back(ds.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,24 +166,15 @@ void PurgeUnusedDiveSites::undo()
|
|||
sitesToRemove = addDiveSites(sitesToAdd);
|
||||
}
|
||||
|
||||
// Helper function: swap C and Qt string
|
||||
static void swap(char *&c, QString &q)
|
||||
{
|
||||
QString s = c;
|
||||
free(c);
|
||||
c = copy_qstring(q);
|
||||
q = s;
|
||||
}
|
||||
|
||||
EditDiveSiteName::EditDiveSiteName(dive_site *dsIn, const QString &name) : ds(dsIn),
|
||||
value(name)
|
||||
value(name.toStdString())
|
||||
{
|
||||
setText(Command::Base::tr("Edit dive site name"));
|
||||
}
|
||||
|
||||
bool EditDiveSiteName::workToBeDone()
|
||||
{
|
||||
return value != QString(ds->name);
|
||||
return value != ds->name;
|
||||
}
|
||||
|
||||
void EditDiveSiteName::redo()
|
||||
|
@ -210,14 +190,14 @@ void EditDiveSiteName::undo()
|
|||
}
|
||||
|
||||
EditDiveSiteDescription::EditDiveSiteDescription(dive_site *dsIn, const QString &description) : ds(dsIn),
|
||||
value(description)
|
||||
value(description.toStdString())
|
||||
{
|
||||
setText(Command::Base::tr("Edit dive site description"));
|
||||
}
|
||||
|
||||
bool EditDiveSiteDescription::workToBeDone()
|
||||
{
|
||||
return value != QString(ds->description);
|
||||
return value != ds->description;
|
||||
}
|
||||
|
||||
void EditDiveSiteDescription::redo()
|
||||
|
@ -233,14 +213,14 @@ void EditDiveSiteDescription::undo()
|
|||
}
|
||||
|
||||
EditDiveSiteNotes::EditDiveSiteNotes(dive_site *dsIn, const QString ¬es) : ds(dsIn),
|
||||
value(notes)
|
||||
value(notes.toStdString())
|
||||
{
|
||||
setText(Command::Base::tr("Edit dive site notes"));
|
||||
}
|
||||
|
||||
bool EditDiveSiteNotes::workToBeDone()
|
||||
{
|
||||
return value != QString(ds->notes);
|
||||
return value != ds->notes;
|
||||
}
|
||||
|
||||
void EditDiveSiteNotes::redo()
|
||||
|
@ -256,20 +236,20 @@ void EditDiveSiteNotes::undo()
|
|||
}
|
||||
|
||||
EditDiveSiteCountry::EditDiveSiteCountry(dive_site *dsIn, const QString &country) : ds(dsIn),
|
||||
value(country)
|
||||
value(country.toStdString())
|
||||
{
|
||||
setText(Command::Base::tr("Edit dive site country"));
|
||||
}
|
||||
|
||||
bool EditDiveSiteCountry::workToBeDone()
|
||||
{
|
||||
return !same_string(qPrintable(value), taxonomy_get_country(&ds->taxonomy));
|
||||
return value == taxonomy_get_country(ds->taxonomy);
|
||||
}
|
||||
|
||||
void EditDiveSiteCountry::redo()
|
||||
{
|
||||
QString old = taxonomy_get_country(&ds->taxonomy);
|
||||
taxonomy_set_country(&ds->taxonomy, qPrintable(value), taxonomy_origin::GEOMANUAL);
|
||||
std::string old = taxonomy_get_country(ds->taxonomy);
|
||||
taxonomy_set_country(ds->taxonomy, value, taxonomy_origin::GEOMANUAL);
|
||||
value = old;
|
||||
emit diveListNotifier.diveSiteChanged(ds, LocationInformationModel::TAXONOMY); // Inform frontend of changed dive site.
|
||||
}
|
||||
|
@ -292,7 +272,7 @@ bool EditDiveSiteLocation::workToBeDone()
|
|||
bool old_ok = has_location(&ds->location);
|
||||
if (ok != old_ok)
|
||||
return true;
|
||||
return ok && !same_location(&value, &ds->location);
|
||||
return ok && value != ds->location;
|
||||
}
|
||||
|
||||
void EditDiveSiteLocation::redo()
|
||||
|
@ -310,14 +290,11 @@ void EditDiveSiteLocation::undo()
|
|||
EditDiveSiteTaxonomy::EditDiveSiteTaxonomy(dive_site *dsIn, taxonomy_data &taxonomy) : ds(dsIn),
|
||||
value(taxonomy)
|
||||
{
|
||||
// We did a dumb copy. Erase the source to remove double references to strings.
|
||||
memset(&taxonomy, 0, sizeof(taxonomy));
|
||||
setText(Command::Base::tr("Edit dive site taxonomy"));
|
||||
}
|
||||
|
||||
EditDiveSiteTaxonomy::~EditDiveSiteTaxonomy()
|
||||
{
|
||||
free_taxonomy(&value);
|
||||
}
|
||||
|
||||
bool EditDiveSiteTaxonomy::workToBeDone()
|
||||
|
@ -364,10 +341,10 @@ void MergeDiveSites::redo()
|
|||
// The dives of the above dive sites were reset to no dive sites.
|
||||
// Add them to the merged-into dive site. Thankfully, we remember
|
||||
// the dives in the sitesToAdd vector.
|
||||
for (const OwningDiveSitePtr &site: sitesToAdd) {
|
||||
for (int i = 0; i < site->dives.nr; ++i) {
|
||||
add_dive_to_dive_site(site->dives.dives[i], ds);
|
||||
divesChanged.push_back(site->dives.dives[i]);
|
||||
for (const std::unique_ptr<dive_site> &site: sitesToAdd) {
|
||||
for (dive *d: site->dives) {
|
||||
ds->add_dive(d);
|
||||
divesChanged.push_back(d);
|
||||
}
|
||||
}
|
||||
emit diveListNotifier.divesChanged(divesChanged, DiveField::DIVESITE);
|
||||
|
@ -380,10 +357,10 @@ void MergeDiveSites::undo()
|
|||
|
||||
// Before readding the dive sites, unregister the corresponding dives so that they can be
|
||||
// readded to their old dive sites.
|
||||
for (const OwningDiveSitePtr &site: sitesToAdd) {
|
||||
for (int i = 0; i < site->dives.nr; ++i) {
|
||||
unregister_dive_from_dive_site(site->dives.dives[i]);
|
||||
divesChanged.push_back(site->dives.dives[i]);
|
||||
for (const std::unique_ptr<dive_site> &site: sitesToAdd) {
|
||||
for (dive *d: site->dives) {
|
||||
unregister_dive_from_dive_site(d);
|
||||
divesChanged.push_back(d);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -405,9 +382,9 @@ ApplyGPSFixes::ApplyGPSFixes(const std::vector<DiveAndLocation> &fixes)
|
|||
siteLocations.push_back({ ds, dl.location });
|
||||
}
|
||||
} else {
|
||||
ds = create_dive_site(qPrintable(dl.name), divelog.sites);
|
||||
ds = divelog.sites.create(dl.name.toStdString());
|
||||
ds->location = dl.location;
|
||||
add_dive_to_dive_site(dl.d, ds);
|
||||
ds->add_dive(dl.d);
|
||||
dl.d->dive_site = nullptr; // This will be set on redo()
|
||||
sitesToAdd.emplace_back(ds);
|
||||
}
|
||||
|
|
|
@ -31,13 +31,13 @@ private:
|
|||
std::vector<dive_site *> sitesToRemove;
|
||||
|
||||
// For redo
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd;
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd;
|
||||
};
|
||||
|
||||
class ImportDiveSites : public Base {
|
||||
public:
|
||||
// Note: the dive site table is consumed after the call it will be empty.
|
||||
ImportDiveSites(struct dive_site_table *sites, const QString &source);
|
||||
// Note: Takes ownership of dive site table
|
||||
ImportDiveSites(dive_site_table sites, const QString &source);
|
||||
private:
|
||||
bool workToBeDone() override;
|
||||
void undo() override;
|
||||
|
@ -47,7 +47,7 @@ private:
|
|||
std::vector<dive_site *> sitesToRemove;
|
||||
|
||||
// For redo
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd;
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd;
|
||||
};
|
||||
|
||||
class DeleteDiveSites : public Base {
|
||||
|
@ -62,7 +62,7 @@ private:
|
|||
std::vector<dive_site *> sitesToRemove;
|
||||
|
||||
// For undo
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd;
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd;
|
||||
};
|
||||
|
||||
class PurgeUnusedDiveSites : public Base {
|
||||
|
@ -77,7 +77,7 @@ private:
|
|||
std::vector<dive_site *> sitesToRemove;
|
||||
|
||||
// For undo
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd;
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd;
|
||||
};
|
||||
|
||||
class EditDiveSiteName : public Base {
|
||||
|
@ -89,7 +89,7 @@ private:
|
|||
void redo() override;
|
||||
|
||||
dive_site *ds;
|
||||
QString value; // Value to be set
|
||||
std::string value; // Value to be set
|
||||
};
|
||||
|
||||
class EditDiveSiteDescription : public Base {
|
||||
|
@ -101,7 +101,7 @@ private:
|
|||
void redo() override;
|
||||
|
||||
dive_site *ds;
|
||||
QString value; // Value to be set
|
||||
std::string value; // Value to be set
|
||||
};
|
||||
|
||||
class EditDiveSiteNotes : public Base {
|
||||
|
@ -113,7 +113,7 @@ private:
|
|||
void redo() override;
|
||||
|
||||
dive_site *ds;
|
||||
QString value; // Value to be set
|
||||
std::string value; // Value to be set
|
||||
};
|
||||
|
||||
class EditDiveSiteCountry : public Base {
|
||||
|
@ -125,7 +125,7 @@ private:
|
|||
void redo() override;
|
||||
|
||||
dive_site *ds;
|
||||
QString value; // Value to be set
|
||||
std::string value; // Value to be set
|
||||
};
|
||||
|
||||
class EditDiveSiteLocation : public Base {
|
||||
|
@ -167,7 +167,7 @@ private:
|
|||
std::vector<dive_site *> sitesToRemove;
|
||||
|
||||
// For undo
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd;
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd;
|
||||
};
|
||||
|
||||
class ApplyGPSFixes : public Base {
|
||||
|
@ -183,7 +183,7 @@ private:
|
|||
std::vector<dive_site *> sitesToRemove;
|
||||
|
||||
// For redo
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd;
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd;
|
||||
|
||||
// For redo and undo
|
||||
struct SiteAndLocation {
|
||||
|
|
|
@ -88,7 +88,7 @@ private:
|
|||
|
||||
// Automatically generate getter and setter in the case for string assignments.
|
||||
// The third parameter is a pointer to a C-style string in the dive structure.
|
||||
template <DiveField::Flags ID, char *dive::*PTR>
|
||||
template <DiveField::Flags ID, std::string dive::*PTR>
|
||||
class EditStringSetter : public EditTemplate<QString, ID> {
|
||||
private:
|
||||
using EditTemplate<QString, ID>::EditTemplate;
|
||||
|
@ -208,7 +208,7 @@ public:
|
|||
// deriving from it and hooks into undo() and redo() to add / remove the dive site.
|
||||
class EditDiveSiteNew : public EditDiveSite {
|
||||
public:
|
||||
OwningDiveSitePtr diveSiteToAdd;
|
||||
std::unique_ptr<dive_site> diveSiteToAdd;
|
||||
struct dive_site *diveSiteToRemove;
|
||||
EditDiveSiteNew(const QString &newName, bool currentDiveOnly);
|
||||
void undo() override;
|
||||
|
@ -287,34 +287,35 @@ public:
|
|||
|
||||
// Fields we have to remember to undo paste
|
||||
struct PasteState {
|
||||
dive *d;
|
||||
dive_site *divesite;
|
||||
QString notes;
|
||||
QString diveguide;
|
||||
QString buddy;
|
||||
QString suit;
|
||||
int rating;
|
||||
int wavesize;
|
||||
int visibility;
|
||||
int current;
|
||||
int surge;
|
||||
int chill;
|
||||
tag_entry *tags;
|
||||
struct cylinder_table cylinders;
|
||||
struct weightsystem_table weightsystems;
|
||||
int number;
|
||||
timestamp_t when;
|
||||
dive &d;
|
||||
std::optional<dive_site *> divesite;
|
||||
std::optional<std::string> notes;
|
||||
std::optional<std::string> diveguide;
|
||||
std::optional<std::string> buddy;
|
||||
std::optional<std::string> suit;
|
||||
std::optional<int> rating;
|
||||
std::optional<int> wavesize;
|
||||
std::optional<int> visibility;
|
||||
std::optional<int> current;
|
||||
std::optional<int> surge;
|
||||
std::optional<int> chill;
|
||||
std::optional<tag_list> tags;
|
||||
std::optional<cylinder_table> cylinders;
|
||||
std::optional<weightsystem_table> weightsystems;
|
||||
std::optional<int> number;
|
||||
std::optional<timestamp_t> when;
|
||||
|
||||
PasteState(dive *d, const dive *data, dive_components what); // Read data from dive data for dive d
|
||||
PasteState(dive &d, const dive_paste_data &data, std::vector<dive_site *> &changed_dive_sites);
|
||||
~PasteState();
|
||||
void swap(dive_components what); // Exchange values here and in dive
|
||||
void swap(); // Exchange values here and in dive
|
||||
};
|
||||
|
||||
class PasteDives : public Base {
|
||||
dive_components what;
|
||||
dive_paste_data data;
|
||||
std::vector<PasteState> dives;
|
||||
std::vector<dive_site *> dive_sites_changed;
|
||||
public:
|
||||
PasteDives(const dive *d, dive_components what);
|
||||
PasteDives(const dive_paste_data &data);
|
||||
private:
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
@ -329,7 +330,7 @@ class ReplanDive : public Base {
|
|||
depth_t maxdepth, meandepth;
|
||||
struct cylinder_table cylinders;
|
||||
struct divecomputer dc;
|
||||
char *notes;
|
||||
std::string notes;
|
||||
pressure_t surface_pressure;
|
||||
duration_t duration;
|
||||
int salinity;
|
||||
|
@ -465,12 +466,12 @@ public:
|
|||
EditDive(dive *oldDive, dive *newDive, dive_site *createDs, dive_site *editDs, location_t dsLocation); // Takes ownership of newDive
|
||||
private:
|
||||
dive *oldDive; // Dive that is going to be overwritten
|
||||
OwningDivePtr newDive; // New data
|
||||
std::unique_ptr<dive> newDive; // New data
|
||||
dive_site *newDiveSite;
|
||||
int changedFields;
|
||||
|
||||
dive_site *siteToRemove;
|
||||
OwningDiveSitePtr siteToAdd;
|
||||
std::unique_ptr<dive_site> siteToAdd;
|
||||
|
||||
dive_site *siteToEdit;
|
||||
location_t dsLocation;
|
||||
|
|
|
@ -41,13 +41,12 @@ void EditTripBase::redo()
|
|||
// ***** Location *****
|
||||
void EditTripLocation::set(dive_trip *t, const QString &s) const
|
||||
{
|
||||
free(t->location);
|
||||
t->location = copy_qstring(s);
|
||||
t->location = s.toStdString();
|
||||
}
|
||||
|
||||
QString EditTripLocation::data(dive_trip *t) const
|
||||
{
|
||||
return QString(t->location);
|
||||
return QString::fromStdString(t->location);
|
||||
}
|
||||
|
||||
QString EditTripLocation::fieldName() const
|
||||
|
@ -63,13 +62,12 @@ TripField EditTripLocation::fieldId() const
|
|||
// ***** Notes *****
|
||||
void EditTripNotes::set(dive_trip *t, const QString &s) const
|
||||
{
|
||||
free(t->notes);
|
||||
t->notes = copy_qstring(s);
|
||||
t->notes = s.toStdString();
|
||||
}
|
||||
|
||||
QString EditTripNotes::data(dive_trip *t) const
|
||||
{
|
||||
return QString(t->notes);
|
||||
return QString::fromStdString(t->notes);
|
||||
}
|
||||
|
||||
QString EditTripNotes::fieldName() const
|
||||
|
|
|
@ -30,7 +30,7 @@ protected:
|
|||
void redo() override;
|
||||
|
||||
// Get and set functions to be overriden by sub-classes.
|
||||
virtual void set(struct dive_trip *t, const QString &) const = 0;
|
||||
virtual void set(dive_trip *t, const QString &) const = 0;
|
||||
virtual QString data(struct dive_trip *t) const = 0;
|
||||
virtual QString fieldName() const = 0; // Name of the field, used to create the undo menu-entry
|
||||
virtual TripField fieldId() const = 0;
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
#include "command_event.h"
|
||||
#include "core/dive.h"
|
||||
#include "core/event.h"
|
||||
#include "core/divelist.h"
|
||||
#include "core/divelog.h"
|
||||
#include "core/selection.h"
|
||||
#include "core/subsurface-qt/divelistnotifier.h"
|
||||
#include "core/libdivecomputer.h"
|
||||
|
@ -30,13 +31,13 @@ void EventBase::undo()
|
|||
|
||||
void EventBase::updateDive()
|
||||
{
|
||||
invalidate_dive_cache(d);
|
||||
d->invalidate_cache();
|
||||
emit diveListNotifier.eventsChanged(d);
|
||||
setSelection({ d }, d, dcNr);
|
||||
}
|
||||
|
||||
AddEventBase::AddEventBase(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr),
|
||||
eventToAdd(ev)
|
||||
AddEventBase::AddEventBase(struct dive *d, int dcNr, struct event ev) : EventBase(d, dcNr),
|
||||
ev(std::move(ev))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -47,33 +48,30 @@ bool AddEventBase::workToBeDone()
|
|||
|
||||
void AddEventBase::redoit()
|
||||
{
|
||||
struct divecomputer *dc = get_dive_dc(d, dcNr);
|
||||
eventToRemove = eventToAdd.get();
|
||||
add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend
|
||||
struct divecomputer *dc = d->get_dc(dcNr);
|
||||
idx = add_event_to_dc(dc, ev); // return ownership to backend
|
||||
}
|
||||
|
||||
void AddEventBase::undoit()
|
||||
{
|
||||
struct divecomputer *dc = get_dive_dc(d, dcNr);
|
||||
remove_event_from_dc(dc, eventToRemove);
|
||||
eventToAdd.reset(eventToRemove); // take ownership of event
|
||||
eventToRemove = nullptr;
|
||||
struct divecomputer *dc = d->get_dc(dcNr);
|
||||
ev = remove_event_from_dc(dc, idx);
|
||||
}
|
||||
|
||||
AddEventBookmark::AddEventBookmark(struct dive *d, int dcNr, int seconds) :
|
||||
AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark"))
|
||||
AddEventBase(d, dcNr, event(seconds, SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark"))
|
||||
{
|
||||
setText(Command::Base::tr("Add bookmark"));
|
||||
}
|
||||
|
||||
AddEventDivemodeSwitch::AddEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode) :
|
||||
AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_BOOKMARK, 0, divemode, QT_TRANSLATE_NOOP("gettextFromC", "modechange")))
|
||||
AddEventBase(d, dcNr, event(seconds, SAMPLE_EVENT_BOOKMARK, 0, divemode, QT_TRANSLATE_NOOP("gettextFromC", "modechange")))
|
||||
{
|
||||
setText(Command::Base::tr("Add dive mode switch to %1").arg(gettextFromC::tr(divemode_text_ui[divemode])));
|
||||
}
|
||||
|
||||
AddEventSetpointChange::AddEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2) :
|
||||
AddEventBase(d, dcNr, create_event(seconds, SAMPLE_EVENT_PO2, 0, pO2.mbar, QT_TRANSLATE_NOOP("gettextFromC", "SP change"))),
|
||||
AddEventBase(d, dcNr, event(seconds, SAMPLE_EVENT_PO2, 0, pO2.mbar, QT_TRANSLATE_NOOP("gettextFromC", "SP change"))),
|
||||
divemode(CCR)
|
||||
{
|
||||
setText(Command::Base::tr("Add set point change")); // TODO: format pO2 value in bar or psi.
|
||||
|
@ -82,20 +80,20 @@ AddEventSetpointChange::AddEventSetpointChange(struct dive *d, int dcNr, int sec
|
|||
void AddEventSetpointChange::undoit()
|
||||
{
|
||||
AddEventBase::undoit();
|
||||
std::swap(get_dive_dc(d, dcNr)->divemode, divemode);
|
||||
std::swap(d->get_dc(dcNr)->divemode, divemode);
|
||||
}
|
||||
|
||||
void AddEventSetpointChange::redoit()
|
||||
{
|
||||
AddEventBase::redoit();
|
||||
std::swap(get_dive_dc(d, dcNr)->divemode, divemode);
|
||||
std::swap(d->get_dc(dcNr)->divemode, divemode);
|
||||
}
|
||||
|
||||
RenameEvent::RenameEvent(struct dive *d, int dcNr, struct event *ev, const char *name) : EventBase(d, dcNr),
|
||||
eventToAdd(clone_event_rename(ev, name)),
|
||||
eventToRemove(ev)
|
||||
RenameEvent::RenameEvent(struct dive *d, int dcNr, int idx, const std::string nameIn) : EventBase(d, dcNr),
|
||||
idx(idx),
|
||||
name(std::move(nameIn))
|
||||
{
|
||||
setText(Command::Base::tr("Rename bookmark to %1").arg(name));
|
||||
setText(Command::Base::tr("Rename bookmark to %1").arg(name.c_str()));
|
||||
}
|
||||
|
||||
bool RenameEvent::workToBeDone()
|
||||
|
@ -105,45 +103,47 @@ bool RenameEvent::workToBeDone()
|
|||
|
||||
void RenameEvent::redoit()
|
||||
{
|
||||
struct divecomputer *dc = get_dive_dc(d, dcNr);
|
||||
swap_event(dc, eventToRemove, eventToAdd.get());
|
||||
event *tmp = eventToRemove;
|
||||
eventToRemove = eventToAdd.release();
|
||||
eventToAdd.reset(tmp);
|
||||
struct divecomputer *dc = d->get_dc(dcNr);
|
||||
event *ev = get_event(dc, idx);
|
||||
if (ev)
|
||||
std::swap(ev->name, name);
|
||||
}
|
||||
|
||||
void RenameEvent::undoit()
|
||||
{
|
||||
// Undo and redo do the same thing - they simply swap events
|
||||
// Undo and redo do the same thing - they simply swap names
|
||||
redoit();
|
||||
}
|
||||
|
||||
RemoveEvent::RemoveEvent(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr),
|
||||
eventToRemove(ev),
|
||||
cylinder(ev->type == SAMPLE_EVENT_GASCHANGE2 || ev->type == SAMPLE_EVENT_GASCHANGE ?
|
||||
ev->gas.index : -1)
|
||||
RemoveEvent::RemoveEvent(struct dive *d, int dcNr, int idxIn) : EventBase(d, dcNr),
|
||||
idx(idxIn), cylinder(-1)
|
||||
{
|
||||
setText(Command::Base::tr("Remove %1 event").arg(ev->name));
|
||||
struct divecomputer *dc = d->get_dc(dcNr);
|
||||
event *ev = get_event(dc, idx);
|
||||
if (!ev) {
|
||||
idx = -1;
|
||||
return;
|
||||
}
|
||||
if (ev->type == SAMPLE_EVENT_GASCHANGE2 || ev->type == SAMPLE_EVENT_GASCHANGE)
|
||||
cylinder = ev->gas.index;
|
||||
setText(Command::Base::tr("Remove %1 event").arg(ev->name.c_str()));
|
||||
}
|
||||
|
||||
bool RemoveEvent::workToBeDone()
|
||||
{
|
||||
return true;
|
||||
return idx >= 0;
|
||||
}
|
||||
|
||||
void RemoveEvent::redoit()
|
||||
{
|
||||
struct divecomputer *dc = get_dive_dc(d, dcNr);
|
||||
remove_event_from_dc(dc, eventToRemove);
|
||||
eventToAdd.reset(eventToRemove); // take ownership of event
|
||||
eventToRemove = nullptr;
|
||||
struct divecomputer *dc = d->get_dc(dcNr);
|
||||
ev = remove_event_from_dc(dc, idx);
|
||||
}
|
||||
|
||||
void RemoveEvent::undoit()
|
||||
{
|
||||
struct divecomputer *dc = get_dive_dc(d, dcNr);
|
||||
eventToRemove = eventToAdd.get();
|
||||
add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend
|
||||
struct divecomputer *dc = d->get_dc(dcNr);
|
||||
idx = add_event_to_dc(dc, std::move(ev));
|
||||
}
|
||||
|
||||
void RemoveEvent::post() const
|
||||
|
@ -151,7 +151,7 @@ void RemoveEvent::post() const
|
|||
if (cylinder < 0)
|
||||
return;
|
||||
|
||||
fixup_dive(d);
|
||||
divelog.dives.fixup_dive(*d);
|
||||
emit diveListNotifier.cylinderEdited(d, cylinder);
|
||||
|
||||
// TODO: This is silly we send a DURATION change event so that the statistics are recalculated.
|
||||
|
@ -164,19 +164,19 @@ AddGasSwitch::AddGasSwitch(struct dive *d, int dcNr, int seconds, int tank) : Ev
|
|||
// If there is a gas change at this time stamp, remove it before adding the new one.
|
||||
// There shouldn't be more than one gas change per time stamp. Just in case we'll
|
||||
// support that anyway.
|
||||
struct divecomputer *dc = get_dive_dc(d, dcNr);
|
||||
struct event *gasChangeEvent = dc->events;
|
||||
while ((gasChangeEvent = get_next_event_mutable(gasChangeEvent, "gaschange")) != NULL) {
|
||||
if (gasChangeEvent->time.seconds == seconds) {
|
||||
eventsToRemove.push_back(gasChangeEvent);
|
||||
int idx = gasChangeEvent->gas.index;
|
||||
if (std::find(cylinders.begin(), cylinders.end(), idx) == cylinders.end())
|
||||
cylinders.push_back(idx); // cylinders might have changed their status
|
||||
}
|
||||
gasChangeEvent = gasChangeEvent->next;
|
||||
struct divecomputer *dc = d->get_dc(dcNr);
|
||||
|
||||
// Note that we remove events in reverse order so that the indexes don't change
|
||||
// meaning while removing. This should be an extremely rare case anyway.
|
||||
for (int idx = static_cast<int>(dc->events.size()) - 1; idx > 0; --idx) {
|
||||
const event &ev = dc->events[idx];
|
||||
if (ev.time.seconds == seconds && ev.name == "gaschange")
|
||||
eventsToRemove.push_back(idx);
|
||||
if (std::find(cylinders.begin(), cylinders.end(), ev.gas.index) == cylinders.end())
|
||||
cylinders.push_back(ev.gas.index); // cylinders might have changed their status
|
||||
}
|
||||
|
||||
eventsToAdd.emplace_back(create_gas_switch_event(d, dc, seconds, tank));
|
||||
eventsToAdd.push_back(create_gas_switch_event(d, dc, seconds, tank));
|
||||
}
|
||||
|
||||
bool AddGasSwitch::workToBeDone()
|
||||
|
@ -186,25 +186,26 @@ bool AddGasSwitch::workToBeDone()
|
|||
|
||||
void AddGasSwitch::redoit()
|
||||
{
|
||||
std::vector<OwningEventPtr> newEventsToAdd;
|
||||
std::vector<event *> newEventsToRemove;
|
||||
std::vector<event> newEventsToAdd;
|
||||
std::vector<int> newEventsToRemove;
|
||||
newEventsToAdd.reserve(eventsToRemove.size());
|
||||
newEventsToRemove.reserve(eventsToAdd.size());
|
||||
struct divecomputer *dc = get_dive_dc(d, dcNr);
|
||||
struct divecomputer *dc = d->get_dc(dcNr);
|
||||
|
||||
for (int idx: eventsToRemove)
|
||||
newEventsToAdd.push_back(remove_event_from_dc(dc, idx));
|
||||
|
||||
for (auto &ev: eventsToAdd)
|
||||
newEventsToRemove.push_back(add_event_to_dc(dc, std::move(ev)));
|
||||
|
||||
// Make sure that events are removed in reverse order
|
||||
std::sort(newEventsToRemove.begin(), newEventsToRemove.end(), std::greater<int>());
|
||||
|
||||
for (event *ev: eventsToRemove) {
|
||||
remove_event_from_dc(dc, ev);
|
||||
newEventsToAdd.emplace_back(ev); // take ownership of event
|
||||
}
|
||||
for (OwningEventPtr &ev: eventsToAdd) {
|
||||
newEventsToRemove.push_back(ev.get());
|
||||
add_event_to_dc(dc, ev.release()); // return ownership to backend
|
||||
}
|
||||
eventsToAdd = std::move(newEventsToAdd);
|
||||
eventsToRemove = std::move(newEventsToRemove);
|
||||
|
||||
// this means we potentially have a new tank that is being used and needs to be shown
|
||||
fixup_dive(d);
|
||||
divelog.dives.fixup_dive(*d);
|
||||
|
||||
for (int idx: cylinders)
|
||||
emit diveListNotifier.cylinderEdited(d, idx);
|
||||
|
|
|
@ -6,15 +6,12 @@
|
|||
|
||||
#include "command_base.h"
|
||||
#include "core/divemode.h"
|
||||
#include "core/event.h"
|
||||
|
||||
// We put everything in a namespace, so that we can shorten names without polluting the global namespace
|
||||
namespace Command {
|
||||
|
||||
// Events are a strange thing: they contain there own description which means
|
||||
// that on changing the description a new object must be allocated. Moreover,
|
||||
// it means that these objects can't be collected in a table.
|
||||
// Therefore, the undo commands work on events as they do with dives: using
|
||||
// owning pointers. See comments in command_base.h
|
||||
// Pointers to events are not stable, so we always store indexes.
|
||||
|
||||
class EventBase : public Base {
|
||||
protected:
|
||||
|
@ -25,8 +22,7 @@ protected:
|
|||
virtual void undoit() = 0;
|
||||
|
||||
// Note: we store dive and the divecomputer-number instead of a pointer to the divecomputer.
|
||||
// Since one divecomputer is integrated into the dive structure, pointers to divecomputers
|
||||
// are probably not stable.
|
||||
// Pointers to divecomputers are not stable.
|
||||
struct dive *d;
|
||||
int dcNr;
|
||||
private:
|
||||
|
@ -35,15 +31,15 @@ private:
|
|||
|
||||
class AddEventBase : public EventBase {
|
||||
public:
|
||||
AddEventBase(struct dive *d, int dcNr, struct event *ev); // Takes ownership of event!
|
||||
AddEventBase(struct dive *d, int dcNr, struct event ev); // Takes ownership of event!
|
||||
protected:
|
||||
void undoit() override;
|
||||
void redoit() override;
|
||||
private:
|
||||
bool workToBeDone() override;
|
||||
|
||||
OwningEventPtr eventToAdd; // for redo
|
||||
event *eventToRemove; // for undo
|
||||
struct event ev; // for redo
|
||||
int idx; // for undo
|
||||
};
|
||||
|
||||
class AddEventBookmark : public AddEventBase {
|
||||
|
@ -67,27 +63,27 @@ private:
|
|||
|
||||
class RenameEvent : public EventBase {
|
||||
public:
|
||||
RenameEvent(struct dive *d, int dcNr, struct event *ev, const char *name);
|
||||
RenameEvent(struct dive *d, int dcNr, int idx, const std::string name);
|
||||
private:
|
||||
bool workToBeDone() override;
|
||||
void undoit() override;
|
||||
void redoit() override;
|
||||
|
||||
OwningEventPtr eventToAdd; // for undo and redo
|
||||
event *eventToRemove; // for undo and redo
|
||||
int idx; // for undo and redo
|
||||
std::string name; // for undo and redo
|
||||
};
|
||||
|
||||
class RemoveEvent : public EventBase {
|
||||
public:
|
||||
RemoveEvent(struct dive *d, int dcNr, struct event *ev);
|
||||
RemoveEvent(struct dive *d, int dcNr, int idx);
|
||||
private:
|
||||
bool workToBeDone() override;
|
||||
void undoit() override;
|
||||
void redoit() override;
|
||||
void post() const; // Called to fix up dives should a gas-change have happened.
|
||||
|
||||
OwningEventPtr eventToAdd; // for undo
|
||||
event *eventToRemove; // for redo
|
||||
event ev; // for undo
|
||||
int idx; // for redo
|
||||
int cylinder; // affected cylinder (if removing gas switch). <0: not a gas switch.
|
||||
};
|
||||
|
||||
|
@ -100,8 +96,8 @@ private:
|
|||
void redoit() override;
|
||||
|
||||
std::vector<int> cylinders; // cylinders that are modified
|
||||
std::vector<OwningEventPtr> eventsToAdd;
|
||||
std::vector<event *> eventsToRemove;
|
||||
std::vector<event> eventsToAdd;
|
||||
std::vector<int> eventsToRemove;
|
||||
};
|
||||
|
||||
} // namespace Command
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include "command_filter.h"
|
||||
#include "core/divelog.h"
|
||||
#include "core/filterpreset.h"
|
||||
#include "core/filterpresettable.h"
|
||||
#include "core/subsurface-qt/divelistnotifier.h"
|
||||
|
||||
namespace Command {
|
||||
|
||||
static int createFilterPreset(const std::string &name, const FilterData &data)
|
||||
{
|
||||
int index = filter_preset_add(name, data);
|
||||
int index = divelog.filter_presets.add(name, data);
|
||||
emit diveListNotifier.filterPresetAdded(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
static std::pair<std::string, FilterData> removeFilterPreset(int index)
|
||||
{
|
||||
std::string oldName = filter_preset_name(index);
|
||||
FilterData oldData = filter_preset_get(index);
|
||||
filter_preset_delete(index);
|
||||
const filter_preset &preset = divelog.filter_presets[index];
|
||||
std::string oldName = preset.name;
|
||||
FilterData oldData = preset.data;
|
||||
divelog.filter_presets.remove(index);
|
||||
emit diveListNotifier.filterPresetRemoved(index);
|
||||
return { oldName, oldData };
|
||||
}
|
||||
|
@ -46,7 +49,8 @@ void CreateFilterPreset::undo()
|
|||
|
||||
RemoveFilterPreset::RemoveFilterPreset(int indexIn) : index(indexIn)
|
||||
{
|
||||
setText(Command::Base::tr("Delete filter preset %1").arg(QString(filter_preset_name(index).c_str())));
|
||||
const std::string &name = divelog.filter_presets[index].name;
|
||||
setText(Command::Base::tr("Delete filter preset %1").arg(QString::fromStdString(name)));
|
||||
}
|
||||
|
||||
bool RemoveFilterPreset::workToBeDone()
|
||||
|
@ -68,7 +72,8 @@ void RemoveFilterPreset::undo()
|
|||
EditFilterPreset::EditFilterPreset(int indexIn, const FilterData &dataIn) :
|
||||
index(indexIn), data(dataIn)
|
||||
{
|
||||
setText(Command::Base::tr("Edit filter preset %1").arg(QString(filter_preset_name(index).c_str())));
|
||||
const std::string &name = divelog.filter_presets[index].name;
|
||||
setText(Command::Base::tr("Edit filter preset %1").arg(QString::fromStdString(name)));
|
||||
}
|
||||
|
||||
bool EditFilterPreset::workToBeDone()
|
||||
|
@ -78,9 +83,8 @@ bool EditFilterPreset::workToBeDone()
|
|||
|
||||
void EditFilterPreset::redo()
|
||||
{
|
||||
FilterData oldData = filter_preset_get(index);
|
||||
filter_preset_set(index, data);
|
||||
data = std::move(oldData);
|
||||
filter_preset &preset = divelog.filter_presets[index];
|
||||
std::swap(data, preset.data);
|
||||
}
|
||||
|
||||
void EditFilterPreset::undo()
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
|
||||
#include "command_pictures.h"
|
||||
#include "core/errorhelper.h"
|
||||
#include "core/range.h"
|
||||
#include "core/subsurface-qt/divelistnotifier.h"
|
||||
#include "qt-models/divelocationmodel.h"
|
||||
|
||||
namespace Command {
|
||||
|
||||
static picture *dive_get_picture(const dive *d, const QString &fn)
|
||||
static picture *dive_get_picture(dive *d, const QString &fn)
|
||||
{
|
||||
int idx = get_picture_idx(&d->pictures, qPrintable(fn));
|
||||
return idx < 0 ? nullptr : &d->pictures.pictures[idx];
|
||||
int idx = get_picture_idx(d->pictures, fn.toStdString());
|
||||
return idx < 0 ? nullptr : &d->pictures[idx];
|
||||
}
|
||||
|
||||
SetPictureOffset::SetPictureOffset(dive *dIn, const QString &filenameIn, offset_t offsetIn) :
|
||||
|
@ -33,9 +34,9 @@ void SetPictureOffset::redo()
|
|||
|
||||
// Instead of trying to be smart, let's simply resort the picture table.
|
||||
// If someone complains about speed, do our usual "smart" thing.
|
||||
sort_picture_table(&d->pictures);
|
||||
std::sort(d->pictures.begin(), d->pictures.end());
|
||||
emit diveListNotifier.pictureOffsetChanged(d, filename, newOffset);
|
||||
invalidate_dive_cache(d);
|
||||
d->invalidate_cache();
|
||||
}
|
||||
|
||||
// Undo and redo do the same thing
|
||||
|
@ -55,10 +56,9 @@ static PictureListForDeletion filterPictureListForDeletion(const PictureListForD
|
|||
PictureListForDeletion res;
|
||||
res.d = p.d;
|
||||
res.filenames.reserve(p.filenames.size());
|
||||
for (int i = 0; i < p.d->pictures.nr; ++i) {
|
||||
std::string fn = p.d->pictures.pictures[i].filename;
|
||||
if (std::find(p.filenames.begin(), p.filenames.end(), fn) != p.filenames.end())
|
||||
res.filenames.push_back(fn);
|
||||
for (auto &pic: p.d->pictures) {
|
||||
if (range_contains(p.filenames, pic.filename))
|
||||
res.filenames.push_back(pic.filename);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -72,18 +72,18 @@ static std::vector<PictureListForAddition> removePictures(std::vector<PictureLis
|
|||
PictureListForAddition toAdd;
|
||||
toAdd.d = list.d;
|
||||
for (const std::string &fn: list.filenames) {
|
||||
int idx = get_picture_idx(&list.d->pictures, fn.c_str());
|
||||
int idx = get_picture_idx(list.d->pictures, fn);
|
||||
if (idx < 0) {
|
||||
report_info("removePictures(): picture disappeared!");
|
||||
continue; // Huh? We made sure that this can't happen by filtering out non-existent pictures.
|
||||
}
|
||||
filenames.push_back(QString::fromStdString(fn));
|
||||
toAdd.pics.emplace_back(list.d->pictures.pictures[idx]);
|
||||
remove_from_picture_table(&list.d->pictures, idx);
|
||||
toAdd.pics.emplace_back(list.d->pictures[idx]);
|
||||
list.d->pictures.erase(list.d->pictures.begin() + idx);
|
||||
}
|
||||
if (!toAdd.pics.empty())
|
||||
res.push_back(toAdd);
|
||||
invalidate_dive_cache(list.d);
|
||||
list.d->invalidate_cache();
|
||||
emit diveListNotifier.picturesRemoved(list.d, std::move(filenames));
|
||||
}
|
||||
picturesToRemove.clear();
|
||||
|
@ -98,22 +98,22 @@ static std::vector<PictureListForDeletion> addPictures(std::vector<PictureListFo
|
|||
// happen, as we checked that before.
|
||||
std::vector<PictureListForDeletion> res;
|
||||
for (const PictureListForAddition &list: picturesToAdd) {
|
||||
QVector<PictureObj> picsForSignal;
|
||||
QVector<picture> picsForSignal;
|
||||
PictureListForDeletion toRemove;
|
||||
toRemove.d = list.d;
|
||||
for (const PictureObj &pic: list.pics) {
|
||||
int idx = get_picture_idx(&list.d->pictures, pic.filename.c_str()); // This should *not* already exist!
|
||||
for (const picture &pic: list.pics) {
|
||||
int idx = get_picture_idx(list.d->pictures, pic.filename); // This should *not* already exist!
|
||||
if (idx >= 0) {
|
||||
report_info("addPictures(): picture disappeared!");
|
||||
continue; // Huh? We made sure that this can't happen by filtering out existing pictures.
|
||||
}
|
||||
picsForSignal.push_back(pic);
|
||||
add_picture(&list.d->pictures, pic.toCore());
|
||||
add_picture(list.d->pictures, pic);
|
||||
toRemove.filenames.push_back(pic.filename);
|
||||
}
|
||||
if (!toRemove.filenames.empty())
|
||||
res.push_back(toRemove);
|
||||
invalidate_dive_cache(list.d);
|
||||
list.d->invalidate_cache();
|
||||
emit diveListNotifier.picturesAdded(list.d, std::move(picsForSignal));
|
||||
}
|
||||
picturesToAdd.clear();
|
||||
|
@ -164,17 +164,16 @@ AddPictures::AddPictures(const std::vector<PictureListForAddition> &pictures) :
|
|||
std::sort(p.pics.begin(), p.pics.end());
|
||||
|
||||
// Find a picture with a location
|
||||
auto it = std::find_if(p.pics.begin(), p.pics.end(), [](const PictureObj &p) { return has_location(&p.location); });
|
||||
auto it = std::find_if(p.pics.begin(), p.pics.end(), [](const picture &p) { return has_location(&p.location); });
|
||||
if (it != p.pics.end()) {
|
||||
// There is a dive with a location, we might want to modify the dive accordingly.
|
||||
struct dive_site *ds = p.d->dive_site;
|
||||
if (!ds) {
|
||||
// This dive doesn't yet have a dive site -> add a new dive site.
|
||||
QString name = Command::Base::tr("unnamed dive site");
|
||||
dive_site *ds = alloc_dive_site_with_gps(qPrintable(name), &it->location);
|
||||
sitesToAdd.emplace_back(ds);
|
||||
sitesToSet.push_back({ p.d, ds });
|
||||
} else if (!dive_site_has_gps_location(ds)) {
|
||||
sitesToAdd.push_back(std::make_unique<dive_site>(qPrintable(name), it->location));
|
||||
sitesToSet.push_back({ p.d, sitesToAdd.back().get() });
|
||||
} else if (!ds->has_gps_location()) {
|
||||
// This dive has a dive site, but without coordinates. Let's add them.
|
||||
sitesToEdit.push_back({ ds, it->location });
|
||||
}
|
||||
|
@ -201,7 +200,7 @@ void AddPictures::swapDiveSites()
|
|||
unregister_dive_from_dive_site(entry.d); // the dive-site pointer in the dive is now NULL
|
||||
std::swap(ds, entry.ds);
|
||||
if (ds)
|
||||
add_dive_to_dive_site(entry.d, ds);
|
||||
ds->add_dive(entry.d);
|
||||
emit diveListNotifier.divesChanged(QVector<dive *>{ entry.d }, DiveField::DIVESITE);
|
||||
}
|
||||
|
||||
|
@ -218,9 +217,9 @@ void AddPictures::undo()
|
|||
|
||||
// Remove dive sites
|
||||
for (dive_site *siteToRemove: sitesToRemove) {
|
||||
int idx = unregister_dive_site(siteToRemove);
|
||||
sitesToAdd.emplace_back(siteToRemove);
|
||||
emit diveListNotifier.diveSiteDeleted(siteToRemove, idx); // Inform frontend of removed dive site.
|
||||
auto res = divelog.sites.pull(siteToRemove);
|
||||
sitesToAdd.push_back(std::move(res.ptr));
|
||||
emit diveListNotifier.diveSiteDeleted(siteToRemove, res.idx); // Inform frontend of removed dive site.
|
||||
}
|
||||
sitesToRemove.clear();
|
||||
}
|
||||
|
@ -228,10 +227,10 @@ void AddPictures::undo()
|
|||
void AddPictures::redo()
|
||||
{
|
||||
// Add dive sites
|
||||
for (OwningDiveSitePtr &siteToAdd: sitesToAdd) {
|
||||
sitesToRemove.push_back(siteToAdd.get());
|
||||
int idx = register_dive_site(siteToAdd.release()); // Return ownership to backend.
|
||||
emit diveListNotifier.diveSiteAdded(sitesToRemove.back(), idx); // Inform frontend of new dive site.
|
||||
for (std::unique_ptr<dive_site> &siteToAdd: sitesToAdd) {
|
||||
auto res = divelog.sites.register_site(std::move(siteToAdd)); // Return ownership to backend.
|
||||
sitesToRemove.push_back(res.ptr);
|
||||
emit diveListNotifier.diveSiteAdded(sitesToRemove.back(), res.idx); // Inform frontend of new dive site.
|
||||
}
|
||||
sitesToAdd.clear();
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ private:
|
|||
location_t location;
|
||||
};
|
||||
std::vector<PictureListForAddition> picturesToAdd; // for redo
|
||||
std::vector<OwningDiveSitePtr> sitesToAdd; //for redo
|
||||
std::vector<std::unique_ptr<dive_site>> sitesToAdd; //for redo
|
||||
std::vector<PictureListForDeletion> picturesToRemove; // for undo
|
||||
std::vector<dive_site *> sitesToRemove; // for undo
|
||||
std::vector<DiveSiteEntry> sitesToSet; // for redo and undo
|
||||
|
|
|
@ -17,7 +17,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
|||
endif()
|
||||
|
||||
if(FTDISUPPORT)
|
||||
set(SERIAL_FTDI serial_ftdi.c)
|
||||
set(SERIAL_FTDI serial_ftdi.cpp)
|
||||
endif()
|
||||
|
||||
if(BTSUPPORT)
|
||||
|
@ -61,29 +61,29 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
|||
devicedetails.h
|
||||
dive.cpp
|
||||
dive.h
|
||||
divecomputer.c
|
||||
divecomputer.cpp
|
||||
divecomputer.h
|
||||
dive.h
|
||||
divefilter.cpp
|
||||
divefilter.h
|
||||
divelist.c
|
||||
divelist.cpp
|
||||
divelist.h
|
||||
divelog.cpp
|
||||
divelog.h
|
||||
divelogexportlogic.cpp
|
||||
divelogexportlogic.h
|
||||
divesite-helper.cpp
|
||||
divesite.c
|
||||
divesite.cpp
|
||||
divesite.h
|
||||
divesitetable.h
|
||||
divesitehelpers.cpp
|
||||
divesitehelpers.h
|
||||
downloadfromdcthread.cpp
|
||||
downloadfromdcthread.h
|
||||
event.c
|
||||
event.cpp
|
||||
event.h
|
||||
eventtype.cpp
|
||||
eventtype.h
|
||||
equipment.c
|
||||
equipment.cpp
|
||||
equipment.h
|
||||
errorhelper.cpp
|
||||
exif.cpp
|
||||
|
@ -95,14 +95,16 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
|||
filterconstraint.h
|
||||
filterpreset.cpp
|
||||
filterpreset.h
|
||||
filterpresettable.cpp
|
||||
filterpresettable.h
|
||||
format.cpp
|
||||
format.h
|
||||
fulltext.cpp
|
||||
fulltext.h
|
||||
gas.c
|
||||
gas.cpp
|
||||
gas.h
|
||||
gas-model.c
|
||||
gaspressures.c
|
||||
gas-model.cpp
|
||||
gaspressures.cpp
|
||||
gaspressures.h
|
||||
gettext.h
|
||||
gettextfromc.cpp
|
||||
|
@ -131,21 +133,18 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
|||
metadata.h
|
||||
metrics.cpp
|
||||
metrics.h
|
||||
ostctools.c
|
||||
owning_ptrs.h
|
||||
ostctools.cpp
|
||||
parse-gpx.cpp
|
||||
parse-xml.cpp
|
||||
parse.cpp
|
||||
parse.h
|
||||
picture.c
|
||||
picture.cpp
|
||||
picture.h
|
||||
pictureobj.cpp
|
||||
pictureobj.h
|
||||
planner.cpp
|
||||
planner.h
|
||||
plannernotes.cpp
|
||||
pref.h
|
||||
pref.c
|
||||
pref.cpp
|
||||
profile.cpp
|
||||
profile.h
|
||||
qt-gui.h
|
||||
|
@ -158,18 +157,17 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
|||
save-git.cpp
|
||||
save-html.cpp
|
||||
save-html.h
|
||||
save-profiledata.c
|
||||
save-profiledata.cpp
|
||||
save-xml.cpp
|
||||
selection.cpp
|
||||
selection.h
|
||||
sha1.c
|
||||
sha1.cpp
|
||||
sha1.h
|
||||
ssrf.h
|
||||
statistics.c
|
||||
statistics.cpp
|
||||
statistics.h
|
||||
string-format.h
|
||||
string-format.cpp
|
||||
strtod.c
|
||||
strtod.cpp
|
||||
subsurface-float.h
|
||||
subsurface-string.cpp
|
||||
subsurface-string.h
|
||||
|
@ -179,23 +177,23 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
|||
subsurfacesysinfo.h
|
||||
tag.cpp
|
||||
tag.h
|
||||
taxonomy.c
|
||||
taxonomy.cpp
|
||||
taxonomy.h
|
||||
time.cpp
|
||||
timer.c
|
||||
timer.h
|
||||
trip.c
|
||||
trip.cpp
|
||||
trip.h
|
||||
triptable.cpp
|
||||
triptable.h
|
||||
uemis-downloader.cpp
|
||||
uemis.c
|
||||
uemis.cpp
|
||||
uemis.h
|
||||
units.h
|
||||
units.c
|
||||
units.cpp
|
||||
uploadDiveShare.cpp
|
||||
uploadDiveShare.h
|
||||
uploadDiveLogsDE.cpp
|
||||
uploadDiveLogsDE.h
|
||||
version.c
|
||||
version.cpp
|
||||
version.h
|
||||
videoframeextractor.cpp
|
||||
videoframeextractor.h
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* implements Android specific functions */
|
||||
#include "dive.h"
|
||||
#include "device.h"
|
||||
#include "libdivecomputer.h"
|
||||
#include "file.h"
|
||||
#include "qthelper.h"
|
||||
#include "subsurfacestartup.h"
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
@ -41,33 +41,31 @@ static std::string make_default_filename()
|
|||
return system_default_path() + "/subsurface.xml";
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
const char android_system_divelist_default_font[] = "Roboto";
|
||||
const char *system_divelist_default_font = android_system_divelist_default_font;
|
||||
double system_divelist_default_font_size = -1;
|
||||
using namespace std::string_literals;
|
||||
std::string system_divelist_default_font = "Roboto"s;
|
||||
double system_divelist_default_font_size = -1.0;
|
||||
|
||||
int get_usb_fd(uint16_t idVendor, uint16_t idProduct);
|
||||
void subsurface_OS_pref_setup(void)
|
||||
void subsurface_OS_pref_setup()
|
||||
{
|
||||
}
|
||||
|
||||
bool subsurface_ignore_font(const char *font)
|
||||
bool subsurface_ignore_font(const std::string &font)
|
||||
{
|
||||
// there are no old default fonts that we would want to ignore
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *system_default_directory(void)
|
||||
std::string system_default_directory()
|
||||
{
|
||||
static const std::string path = system_default_path();
|
||||
return path.c_str();
|
||||
return path;
|
||||
}
|
||||
|
||||
const char *system_default_filename(void)
|
||||
std::string system_default_filename()
|
||||
{
|
||||
static const std::string fn = make_default_filename();
|
||||
return fn.c_str();
|
||||
return fn;
|
||||
}
|
||||
|
||||
|
||||
|
@ -158,12 +156,10 @@ int get_usb_fd(uint16_t idVendor, uint16_t idProduct)
|
|||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_setUsbDevice(JNIEnv *env,
|
||||
jobject obj,
|
||||
Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_setUsbDevice(JNIEnv *,
|
||||
jobject,
|
||||
jobject javaUsbDevice)
|
||||
{
|
||||
Q_UNUSED (obj)
|
||||
Q_UNUSED (env)
|
||||
QAndroidJniObject usbDevice(javaUsbDevice);
|
||||
if (usbDevice.isValid()) {
|
||||
android_usb_serial_device_descriptor descriptor = getDescriptor(usbDevice);
|
||||
|
@ -177,12 +173,10 @@ Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_setUsbDevice(JNIEnv *
|
|||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_restartDownload(JNIEnv *env,
|
||||
jobject obj,
|
||||
Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_restartDownload(JNIEnv *,
|
||||
jobject,
|
||||
jobject javaUsbDevice)
|
||||
{
|
||||
Q_UNUSED (obj)
|
||||
Q_UNUSED (env)
|
||||
QAndroidJniObject usbDevice(javaUsbDevice);
|
||||
if (usbDevice.isValid()) {
|
||||
android_usb_serial_device_descriptor descriptor = getDescriptor(usbDevice);
|
||||
|
@ -237,12 +231,12 @@ int subsurface_zip_close(struct zip *zip)
|
|||
}
|
||||
|
||||
/* win32 console */
|
||||
void subsurface_console_init(void)
|
||||
void subsurface_console_init()
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
void subsurface_console_exit(void)
|
||||
void subsurface_console_exit()
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
|
@ -251,7 +245,6 @@ bool subsurface_user_is_root()
|
|||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* called from QML manager */
|
||||
void checkPendingIntents()
|
||||
|
|
|
@ -476,23 +476,22 @@ QString extractBluetoothAddress(const QString &address)
|
|||
return m.captured(0);
|
||||
}
|
||||
|
||||
QString extractBluetoothNameAddress(const QString &address, QString &name)
|
||||
std::pair<QString, QString> extractBluetoothNameAddress(const QString &address)
|
||||
{
|
||||
// sometimes our device text is of the form "name (address)", sometimes it's just "address"
|
||||
// let's simply return the address
|
||||
name = QString();
|
||||
QString extractedAddress = extractBluetoothAddress(address);
|
||||
if (extractedAddress == address.trimmed())
|
||||
return address;
|
||||
return { address, QString() };
|
||||
|
||||
QRegularExpression re("^([^()]+)\\(([^)]*\\))$");
|
||||
QRegularExpressionMatch m = re.match(address);
|
||||
if (m.hasMatch()) {
|
||||
name = m.captured(1).trimmed();
|
||||
return extractedAddress;
|
||||
QString name = m.captured(1).trimmed();
|
||||
return { extractedAddress, name };
|
||||
}
|
||||
report_info("can't parse address %s", qPrintable(address));
|
||||
return QString();
|
||||
return { QString(), QString() };
|
||||
}
|
||||
|
||||
void saveBtDeviceInfo(const QString &devaddr, QBluetoothDeviceInfo deviceInfo)
|
||||
|
|
|
@ -20,7 +20,7 @@ void saveBtDeviceInfo(const QString &devaddr, QBluetoothDeviceInfo deviceInfo);
|
|||
bool isBluetoothAddress(const QString &address);
|
||||
bool matchesKnownDiveComputerNames(QString btName);
|
||||
QString extractBluetoothAddress(const QString &address);
|
||||
QString extractBluetoothNameAddress(const QString &address, QString &name);
|
||||
std::pair<QString, QString> extractBluetoothNameAddress(const QString &address); // returns address/name pair
|
||||
QBluetoothDeviceInfo getBtDeviceInfo(const QString &devaddr);
|
||||
|
||||
class BTDiscovery : public QObject {
|
||||
|
|
|
@ -21,7 +21,6 @@ CheckCloudConnection::CheckCloudConnection(QObject *parent) :
|
|||
QObject(parent),
|
||||
reply(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// two free APIs to figure out where we are
|
||||
|
@ -43,7 +42,7 @@ bool CheckCloudConnection::checkServer()
|
|||
QNetworkRequest request;
|
||||
request.setRawHeader("Accept", "text/plain");
|
||||
request.setRawHeader("User-Agent", getUserAgent().toUtf8());
|
||||
request.setUrl(QString(prefs.cloud_base_url) + TEAPOT);
|
||||
request.setUrl(QString::fromStdString(prefs.cloud_base_url) + TEAPOT);
|
||||
reply = mgr->get(request);
|
||||
QTimer timer;
|
||||
timer.setSingleShot(true);
|
||||
|
@ -73,7 +72,7 @@ bool CheckCloudConnection::checkServer()
|
|||
}
|
||||
}
|
||||
if (verbose)
|
||||
report_info("connection test to cloud server %s failed %d %s %d %s", prefs.cloud_base_url,
|
||||
report_info("connection test to cloud server %s failed %d %s %d %s", prefs.cloud_base_url.c_str(),
|
||||
static_cast<int>(reply->error()), qPrintable(reply->errorString()),
|
||||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(),
|
||||
qPrintable(reply->readAll()));
|
||||
|
@ -109,19 +108,16 @@ bool CheckCloudConnection::nextServer()
|
|||
};
|
||||
const char *server = nullptr;
|
||||
for (serverTried &item: cloudServers) {
|
||||
if (strstr(prefs.cloud_base_url, item.server))
|
||||
if (contains(prefs.cloud_base_url, item.server))
|
||||
item.tried = true;
|
||||
else if (item.tried == false)
|
||||
server = item.server;
|
||||
}
|
||||
if (server) {
|
||||
int s = strlen(server);
|
||||
char *baseurl = (char *)malloc(10 + s);
|
||||
strcpy(baseurl, "https://");
|
||||
strncat(baseurl, server, s);
|
||||
strcat(baseurl, "/");
|
||||
report_info("failed to connect to %s next server to try: %s", prefs.cloud_base_url, baseurl);
|
||||
prefs.cloud_base_url = baseurl;
|
||||
using namespace std::string_literals;
|
||||
std::string base_url = "https://"s + server + "/"s;
|
||||
report_info("failed to connect to %s next server to try: %s", prefs.cloud_base_url.c_str(), base_url.c_str());
|
||||
prefs.cloud_base_url = std::move(base_url);
|
||||
git_storage_update_progress(qPrintable(tr("Trying different cloud server...")));
|
||||
return true;
|
||||
}
|
||||
|
@ -192,7 +188,7 @@ void CheckCloudConnection::gotContinent(QNetworkReply *reply)
|
|||
base_url = "https://" CLOUD_HOST_US "/";
|
||||
else
|
||||
base_url = "https://" CLOUD_HOST_EU "/";
|
||||
if (!same_string(base_url, prefs.cloud_base_url)) {
|
||||
if (base_url != prefs.cloud_base_url) {
|
||||
if (verbose)
|
||||
report_info("remember cloud server %s based on IP location in %s", base_url, qPrintable(continentString));
|
||||
qPrefCloudStorage::instance()->store_cloud_base_url(base_url);
|
||||
|
@ -200,7 +196,7 @@ void CheckCloudConnection::gotContinent(QNetworkReply *reply)
|
|||
}
|
||||
|
||||
// helper to be used from C code
|
||||
extern "C" bool canReachCloudServer(struct git_info *info)
|
||||
bool canReachCloudServer(struct git_info *info)
|
||||
{
|
||||
if (verbose)
|
||||
qWarning() << "Cloud storage: checking connection to cloud server" << info->url.c_str();
|
||||
|
@ -211,7 +207,7 @@ extern "C" bool canReachCloudServer(struct git_info *info)
|
|||
// the cloud_base_url ends with a '/', so we need the text starting at "git/..."
|
||||
size_t pos = info->url.find("org/git/");
|
||||
if (pos != std::string::npos) {
|
||||
info->url = format_string_std("%s%s", prefs.cloud_base_url, info->url.c_str() + pos + 4);
|
||||
info->url = format_string_std("%s%s", prefs.cloud_base_url.c_str(), info->url.c_str() + pos + 4);
|
||||
if (verbose)
|
||||
report_info("updating remote to: %s", info->url.c_str());
|
||||
}
|
||||
|
|
|
@ -13,24 +13,43 @@ CloudStorageAuthenticate::CloudStorageAuthenticate(QObject *parent) :
|
|||
userAgent = getUserAgent();
|
||||
}
|
||||
|
||||
#define CLOUDURL QString(prefs.cloud_base_url)
|
||||
#define CLOUDBACKENDSTORAGE CLOUDURL + "/storage"
|
||||
#define CLOUDBACKENDVERIFY CLOUDURL + "/verify"
|
||||
#define CLOUDBACKENDUPDATE CLOUDURL + "/update"
|
||||
#define CLOUDBACKENDDELETE CLOUDURL + "/delete-account"
|
||||
static QString cloudUrl()
|
||||
{
|
||||
return QString::fromStdString(prefs.cloud_base_url);
|
||||
}
|
||||
|
||||
static QUrl cloudBackendStorage()
|
||||
{
|
||||
return QUrl(cloudUrl() + "/storage");
|
||||
}
|
||||
|
||||
static QUrl cloudBackendVerify()
|
||||
{
|
||||
return QUrl(cloudUrl() + "/verify");
|
||||
}
|
||||
|
||||
static QUrl cloudBackendUpdate()
|
||||
{
|
||||
return QUrl(cloudUrl() + "/update");
|
||||
}
|
||||
|
||||
static QUrl cloudBackendDelete()
|
||||
{
|
||||
return QUrl(cloudUrl() + "/delete-account");
|
||||
}
|
||||
|
||||
QNetworkReply* CloudStorageAuthenticate::backend(const QString& email,const QString& password,const QString& pin,const QString& newpasswd)
|
||||
{
|
||||
QString payload(email + QChar(' ') + password);
|
||||
QUrl requestUrl;
|
||||
if (pin.isEmpty() && newpasswd.isEmpty()) {
|
||||
requestUrl = QUrl(CLOUDBACKENDSTORAGE);
|
||||
requestUrl = cloudBackendStorage();
|
||||
} else if (!newpasswd.isEmpty()) {
|
||||
requestUrl = QUrl(CLOUDBACKENDUPDATE);
|
||||
requestUrl = cloudBackendUpdate();
|
||||
payload += QChar(' ') + newpasswd;
|
||||
cloudNewPassword = newpasswd;
|
||||
} else {
|
||||
requestUrl = QUrl(CLOUDBACKENDVERIFY);
|
||||
requestUrl = cloudBackendVerify();
|
||||
payload += QChar(' ') + pin;
|
||||
}
|
||||
QNetworkRequest *request = new QNetworkRequest(requestUrl);
|
||||
|
@ -54,7 +73,7 @@ QNetworkReply* CloudStorageAuthenticate::backend(const QString& email,const QStr
|
|||
QNetworkReply* CloudStorageAuthenticate::deleteAccount(const QString& email, const QString& password)
|
||||
{
|
||||
QString payload(email + QChar(' ') + password);
|
||||
QNetworkRequest *request = new QNetworkRequest(QUrl(CLOUDBACKENDDELETE));
|
||||
QNetworkRequest *request = new QNetworkRequest(cloudBackendDelete());
|
||||
request->setRawHeader("Accept", "text/xml, text/plain");
|
||||
request->setRawHeader("User-Agent", userAgent.toUtf8());
|
||||
request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
|
||||
#endif
|
||||
|
||||
#include "ssrf.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
@ -440,12 +439,12 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
|
|||
{
|
||||
const unsigned char *s;
|
||||
unsigned int offset = 0, profile_period = 1, sample_cnt = 0;
|
||||
double depth = 0, temp = 0, depth_sample = 0, psi = 0, sgc_rate = 0;
|
||||
double depth = 0, temp = 0, depth_sample = 0, psi = 0;
|
||||
//int ascent_rate = 0;
|
||||
unsigned int ndl = 0;
|
||||
unsigned int in_deco = 0, deco_ceiling = 0, deco_time = 0;
|
||||
|
||||
struct divecomputer *dc = &dive->dc;
|
||||
struct divecomputer *dc = &dive->dcs[0];
|
||||
struct sample *sample;
|
||||
|
||||
// Initialize stat variables
|
||||
|
@ -458,8 +457,6 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
|
|||
+ log[CMD_START_DEPTH + 1] * 256) / 4;
|
||||
temp = log[CMD_START_TEMP];
|
||||
psi = log[CMD_START_PSI] + log[CMD_START_PSI + 1] * 256;
|
||||
sgc_rate = (double)(log[CMD_START_SGC]
|
||||
+ log[CMD_START_SGC + 1] * 256) / 2;
|
||||
profile_period = log[CMD_PROFILE_PERIOD];
|
||||
break;
|
||||
case TYPE_COMMANDER:
|
||||
|
@ -495,10 +492,6 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
|
|||
while (offset + config.sample_size < size) {
|
||||
s = samples + offset;
|
||||
|
||||
// Start with an empty sample
|
||||
sample = prepare_sample(dc);
|
||||
sample->time.seconds = sample_cnt * profile_period;
|
||||
|
||||
// Check for event
|
||||
if (s[0] & 0x80) {
|
||||
cochran_dive_event(dc, s, sample_cnt * profile_period, &in_deco, &deco_ceiling, &deco_time);
|
||||
|
@ -506,6 +499,10 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
|
|||
continue;
|
||||
}
|
||||
|
||||
// Start with an empty sample
|
||||
sample = prepare_sample(dc);
|
||||
sample->time.seconds = sample_cnt * profile_period;
|
||||
|
||||
// Depth is in every sample
|
||||
depth_sample = (double)(s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1);
|
||||
depth += depth_sample;
|
||||
|
@ -534,9 +531,6 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
|
|||
case 2: // PSI change
|
||||
psi -= (double)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4;
|
||||
break;
|
||||
case 1: // SGC rate
|
||||
sgc_rate -= (double)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 2;
|
||||
break;
|
||||
case 3: // Temperature
|
||||
temp = (double)s[1] / 2 + 20;
|
||||
break;
|
||||
|
@ -592,8 +586,6 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
|
|||
sample->sensor[0] = 0;
|
||||
sample->pressure[0].mbar = lrint(psi * PSI / 100);
|
||||
|
||||
finish_sample(dc);
|
||||
|
||||
offset += config.sample_size;
|
||||
sample_cnt++;
|
||||
}
|
||||
|
@ -604,13 +596,11 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
|
|||
|
||||
static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
|
||||
const unsigned char *in, unsigned size,
|
||||
struct dive_table *table)
|
||||
struct dive_table &table)
|
||||
{
|
||||
unsigned char *buf = (unsigned char *)malloc(size);
|
||||
struct dive *dive;
|
||||
struct divecomputer *dc;
|
||||
struct tm tm = {0};
|
||||
uint32_t csum[5];
|
||||
|
||||
double max_depth, avg_depth, min_temp;
|
||||
unsigned int duration = 0, corrupt_dive = 0;
|
||||
|
@ -668,8 +658,8 @@ static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
|
|||
puts("\nSample Data\n");
|
||||
#endif
|
||||
|
||||
dive = alloc_dive();
|
||||
dc = &dive->dc;
|
||||
auto dive = std::make_unique<struct dive>();
|
||||
dc = &dive->dcs[0];
|
||||
|
||||
unsigned char *log = (buf + 0x4914);
|
||||
|
||||
|
@ -677,24 +667,22 @@ static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
|
|||
case TYPE_GEMINI:
|
||||
case TYPE_COMMANDER:
|
||||
if (config.type == TYPE_GEMINI) {
|
||||
cylinder_t cyl = empty_cylinder;
|
||||
dc->model = "Gemini";
|
||||
dc->deviceid = buf[0x18c] * 256 + buf[0x18d]; // serial no
|
||||
fill_default_cylinder(dive, &cyl);
|
||||
cylinder_t cyl = default_cylinder(dive.get());
|
||||
cyl.gasmix.o2.permille = (log[CMD_O2_PERCENT] / 256
|
||||
+ log[CMD_O2_PERCENT + 1]) * 10;
|
||||
cyl.gasmix.he.permille = 0;
|
||||
add_cylinder(&dive->cylinders, 0, cyl);
|
||||
cyl.gasmix.he = 0_percent;
|
||||
dive->cylinders.add(0, std::move(cyl));
|
||||
} else {
|
||||
dc->model = "Commander";
|
||||
dc->deviceid = array_uint32_le(buf + 0x31e); // serial no
|
||||
for (g = 0; g < 2; g++) {
|
||||
cylinder_t cyl = empty_cylinder;
|
||||
fill_default_cylinder(dive, &cyl);
|
||||
cylinder_t cyl = default_cylinder(dive.get());
|
||||
cyl.gasmix.o2.permille = (log[CMD_O2_PERCENT + g * 2] / 256
|
||||
+ log[CMD_O2_PERCENT + g * 2 + 1]) * 10;
|
||||
cyl.gasmix.he.permille = 0;
|
||||
add_cylinder(&dive->cylinders, g, cyl);
|
||||
cyl.gasmix.he = 0_percent;
|
||||
dive->cylinders.add(g, std::move(cyl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -719,8 +707,7 @@ static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
|
|||
* (double) log[CMD_ALTITUDE] * 250 * FEET, 5.25588) * 1000);
|
||||
dc->salinity = 10000 + 150 * log[CMD_WATER_CONDUCTIVITY];
|
||||
|
||||
SHA1(log + CMD_NUMBER, 2, (unsigned char *)csum);
|
||||
dc->diveid = csum[0];
|
||||
dc->diveid = SHA1_uint32(log + CMD_NUMBER, 2);
|
||||
|
||||
if (log[CMD_MAX_DEPTH] == 0xff && log[CMD_MAX_DEPTH + 1] == 0xff)
|
||||
corrupt_dive = 1;
|
||||
|
@ -733,15 +720,14 @@ static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
|
|||
dc->model = "EMC";
|
||||
dc->deviceid = array_uint32_le(buf + 0x31e); // serial no
|
||||
for (g = 0; g < 4; g++) {
|
||||
cylinder_t cyl = empty_cylinder;
|
||||
fill_default_cylinder(dive, &cyl);
|
||||
cylinder_t cyl = default_cylinder(dive.get());
|
||||
cyl.gasmix.o2.permille =
|
||||
(log[EMC_O2_PERCENT + g * 2] / 256
|
||||
+ log[EMC_O2_PERCENT + g * 2 + 1]) * 10;
|
||||
cyl.gasmix.he.permille =
|
||||
(log[EMC_HE_PERCENT + g * 2] / 256
|
||||
+ log[EMC_HE_PERCENT + g * 2 + 1]) * 10;
|
||||
add_cylinder(&dive->cylinders, g, cyl);
|
||||
dive->cylinders.add(g, std::move(cyl));
|
||||
}
|
||||
|
||||
tm.tm_year = log[EMC_YEAR];
|
||||
|
@ -765,8 +751,7 @@ static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
|
|||
* (double) log[EMC_ALTITUDE] * 250 * FEET, 5.25588) * 1000);
|
||||
dc->salinity = 10000 + 150 * (log[EMC_WATER_CONDUCTIVITY] & 0x3);
|
||||
|
||||
SHA1(log + EMC_NUMBER, 2, (unsigned char *)csum);
|
||||
dc->diveid = csum[0];
|
||||
dc->diveid = SHA1_uint32(log + EMC_NUMBER, 2);
|
||||
|
||||
if (log[EMC_MAX_DEPTH] == 0xff && log[EMC_MAX_DEPTH + 1] == 0xff)
|
||||
corrupt_dive = 1;
|
||||
|
@ -782,7 +767,7 @@ static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
|
|||
if (sample_pre_offset < sample_end_offset && sample_end_offset != 0xffffffff)
|
||||
sample_size = sample_end_offset - sample_pre_offset;
|
||||
|
||||
cochran_parse_samples(dive, buf + 0x4914, buf + 0x4914
|
||||
cochran_parse_samples(dive.get(), buf + 0x4914, buf + 0x4914
|
||||
+ config.logbook_size, sample_size,
|
||||
&duration, &max_depth, &avg_depth, &min_temp);
|
||||
|
||||
|
@ -794,7 +779,7 @@ static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
|
|||
dc->duration.seconds = duration;
|
||||
}
|
||||
|
||||
record_dive_to_table(dive, table);
|
||||
table.record_dive(std::move(dive));
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ static inline QColor makeColor(double r, double g, double b, double a = 1.0)
|
|||
#define VELOCITY_COLORS_START_IDX VELO_STABLE
|
||||
#define VELOCITY_COLORS 5
|
||||
|
||||
typedef enum {
|
||||
enum color_index_t {
|
||||
/* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */
|
||||
SAC_1,
|
||||
SAC_2,
|
||||
|
@ -145,7 +145,7 @@ typedef enum {
|
|||
CALC_CEILING_DEEP,
|
||||
TISSUE_PERCENTAGE,
|
||||
DURATION_LINE
|
||||
} color_index_t;
|
||||
};
|
||||
|
||||
QColor getColor(const color_index_t i, bool isGrayscale = false);
|
||||
QColor getSacColor(int sac, int diveSac);
|
||||
|
|
|
@ -68,16 +68,14 @@ static QString writeGasDetails(gas g)
|
|||
bool ConfigureDiveComputer::saveXMLBackup(const QString &fileName, const DeviceDetails &details, device_data_t *data)
|
||||
{
|
||||
QString xml = "";
|
||||
QString vendor = data->vendor;
|
||||
QString product = data->product;
|
||||
QXmlStreamWriter writer(&xml);
|
||||
writer.setAutoFormatting(true);
|
||||
|
||||
writer.writeStartDocument();
|
||||
writer.writeStartElement("DiveComputerSettingsBackup");
|
||||
writer.writeStartElement("DiveComputer");
|
||||
writer.writeTextElement("Vendor", vendor);
|
||||
writer.writeTextElement("Product", product);
|
||||
writer.writeTextElement("Vendor", QString::fromStdString(data->vendor));
|
||||
writer.writeTextElement("Product", QString::fromStdString(data->product));
|
||||
writer.writeEndElement();
|
||||
writer.writeStartElement("Settings");
|
||||
writer.writeTextElement("CustomText", details.customText);
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
#include "units.h"
|
||||
#include "device.h"
|
||||
#include "file.h"
|
||||
#include "format.h"
|
||||
#include "divesite.h"
|
||||
#include "dive.h"
|
||||
#include "divelog.h"
|
||||
#include "errorhelper.h"
|
||||
#include "ssrf.h"
|
||||
#include "tag.h"
|
||||
|
||||
static unsigned int two_bytes_to_int(unsigned char x, unsigned char y)
|
||||
|
@ -104,21 +104,22 @@ static int read_file_header(unsigned char *buffer)
|
|||
* Returns libdc's equivalent model number (also from g_models) or zero if
|
||||
* this a manual dive.
|
||||
*/
|
||||
static int dtrak_prepare_data(int model, device_data_t *dev_data)
|
||||
static int dtrak_prepare_data(int model, device_data_t &dev_data)
|
||||
{
|
||||
dc_descriptor_t *d = NULL;
|
||||
int i = 0;
|
||||
|
||||
while (model != g_models[i].model_num && g_models[i].model_num != 0xEE)
|
||||
i++;
|
||||
dev_data->model = copy_string(g_models[i].name);
|
||||
dev_data->vendor = (const char *)malloc(strlen(g_models[i].name) + 1);
|
||||
sscanf(g_models[i].name, "%[A-Za-z] ", (char *)dev_data->vendor);
|
||||
dev_data->product = copy_string(strchr(g_models[i].name, ' ') + 1);
|
||||
dev_data.model = g_models[i].name;
|
||||
dev_data.vendor.clear();
|
||||
for (const char *s = g_models[i].name; isalpha(*s); ++s)
|
||||
dev_data.vendor += *s;
|
||||
dev_data.product = strchr(g_models[i].name, ' ') + 1;
|
||||
|
||||
d = get_descriptor(g_models[i].type, g_models[i].libdc_num);
|
||||
if (d)
|
||||
dev_data->descriptor = d;
|
||||
dev_data.descriptor = d;
|
||||
else
|
||||
return 0;
|
||||
return g_models[i].libdc_num;
|
||||
|
@ -129,14 +130,11 @@ static int dtrak_prepare_data(int model, device_data_t *dev_data)
|
|||
* Just get the first in the user's list for given size.
|
||||
* Reaching the end of the list means there is no tank of this size.
|
||||
*/
|
||||
static const char *cyl_type_by_size(int size)
|
||||
static std::string cyl_type_by_size(int size)
|
||||
{
|
||||
for (int i = 0; i < tank_info_table.nr; ++i) {
|
||||
const struct tank_info *ti = &tank_info_table.infos[i];
|
||||
if (ti->ml == size)
|
||||
return ti->name;
|
||||
}
|
||||
return "";
|
||||
auto it = std::find_if(tank_info_table.begin(), tank_info_table.end(),
|
||||
[size] (const tank_info &info) { return info.ml == size; });
|
||||
return it != tank_info_table.end() ? it->name : std::string();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -161,21 +159,19 @@ static dc_status_t dt_libdc_buffer(unsigned char *ptr, int prf_length, int dc_mo
|
|||
static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct divelog *log, char *maxbuf)
|
||||
{
|
||||
int rc, profile_length, libdc_model;
|
||||
char *tmp_notes_str = NULL;
|
||||
unsigned char *tmp_string1 = NULL,
|
||||
*locality = NULL,
|
||||
*dive_point = NULL,
|
||||
*compl_buffer,
|
||||
*membuf = runner;
|
||||
char buffer[1024];
|
||||
unsigned char tmp_1byte;
|
||||
unsigned int tmp_2bytes;
|
||||
unsigned long tmp_4bytes;
|
||||
struct dive_site *ds;
|
||||
std::string tmp_notes_str;
|
||||
char is_nitrox = 0, is_O2 = 0, is_SCR = 0;
|
||||
|
||||
device_data_t *devdata = (device_data_t *)calloc(1, sizeof(device_data_t));
|
||||
devdata->log = log;
|
||||
device_data_t devdata;
|
||||
devdata.log = log;
|
||||
|
||||
/*
|
||||
* Parse byte to byte till next dive entry
|
||||
|
@ -195,7 +191,7 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
* Next, Time in minutes since 00:00
|
||||
*/
|
||||
read_bytes(2);
|
||||
dt_dive->dc.when = dt_dive->when = (timestamp_t)date_time_to_ssrfc(tmp_4bytes, tmp_2bytes);
|
||||
dt_dive->dcs[0].when = dt_dive->when = (timestamp_t)date_time_to_ssrfc(tmp_4bytes, tmp_2bytes);
|
||||
|
||||
/*
|
||||
* Now, Locality, 1st byte is long of string, rest is string
|
||||
|
@ -213,11 +209,13 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
* Subsurface only have a location variable, so we have to merge DTrak's
|
||||
* Locality and Dive points.
|
||||
*/
|
||||
snprintf(buffer, sizeof(buffer), "%s, %s", locality, dive_point);
|
||||
ds = get_dive_site_by_name(buffer, log->sites);
|
||||
{
|
||||
std::string buffer2 = std::string((char *)locality) + " " + (char *)dive_point;
|
||||
struct dive_site *ds = log->sites.get_by_name(buffer2);
|
||||
if (!ds)
|
||||
ds = create_dive_site(buffer, log->sites);
|
||||
add_dive_to_dive_site(dt_dive, ds);
|
||||
ds = log->sites.create(buffer2);
|
||||
ds->add_dive(dt_dive);
|
||||
}
|
||||
free(locality);
|
||||
locality = NULL;
|
||||
free(dive_point);
|
||||
|
@ -241,19 +239,19 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
read_bytes(1);
|
||||
switch (tmp_1byte) {
|
||||
case 1:
|
||||
dt_dive->dc.surface_pressure.mbar = 1013;
|
||||
dt_dive->dcs[0].surface_pressure = 1_atm;
|
||||
break;
|
||||
case 2:
|
||||
dt_dive->dc.surface_pressure.mbar = 932;
|
||||
dt_dive->dcs[0].surface_pressure = 932_mbar;
|
||||
break;
|
||||
case 3:
|
||||
dt_dive->dc.surface_pressure.mbar = 828;
|
||||
dt_dive->dcs[0].surface_pressure = 828_mbar;
|
||||
break;
|
||||
case 4:
|
||||
dt_dive->dc.surface_pressure.mbar = 735;
|
||||
dt_dive->dcs[0].surface_pressure = 735_mbar;
|
||||
break;
|
||||
default:
|
||||
dt_dive->dc.surface_pressure.mbar = 1013;
|
||||
dt_dive->dcs[0].surface_pressure = 1_atm;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -261,32 +259,32 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
*/
|
||||
read_bytes(2);
|
||||
if (tmp_2bytes != 0x7FFF)
|
||||
dt_dive->dc.surfacetime.seconds = (uint32_t) tmp_2bytes * 60;
|
||||
dt_dive->dcs[0].surfacetime.seconds = (uint32_t) tmp_2bytes * 60;
|
||||
|
||||
/*
|
||||
* Weather, values table, 0 to 6
|
||||
* Subsurface don't have this record but we can use tags
|
||||
*/
|
||||
dt_dive->tag_list = NULL;
|
||||
dt_dive->tags.clear();
|
||||
read_bytes(1);
|
||||
switch (tmp_1byte) {
|
||||
case 1:
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "clear")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "clear"));
|
||||
break;
|
||||
case 2:
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "misty")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "misty"));
|
||||
break;
|
||||
case 3:
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fog")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "fog"));
|
||||
break;
|
||||
case 4:
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "rain")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "rain"));
|
||||
break;
|
||||
case 5:
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "storm")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "storm"));
|
||||
break;
|
||||
case 6:
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "snow")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "snow"));
|
||||
break;
|
||||
default:
|
||||
// unknown, do nothing
|
||||
|
@ -298,7 +296,7 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
*/
|
||||
read_bytes(2);
|
||||
if (tmp_2bytes != 0x7FFF)
|
||||
dt_dive->dc.airtemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
|
||||
dt_dive->dcs[0].airtemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
|
||||
|
||||
/*
|
||||
* Dive suit, values table, 0 to 6
|
||||
|
@ -306,22 +304,22 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
read_bytes(1);
|
||||
switch (tmp_1byte) {
|
||||
case 1:
|
||||
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "No suit"));
|
||||
dt_dive->suit = "No suit";
|
||||
break;
|
||||
case 2:
|
||||
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Shorty"));
|
||||
dt_dive->suit = "Shorty";
|
||||
break;
|
||||
case 3:
|
||||
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Combi"));
|
||||
dt_dive->suit = "Combi";
|
||||
break;
|
||||
case 4:
|
||||
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Wet suit"));
|
||||
dt_dive->suit = "Wet suit";
|
||||
break;
|
||||
case 5:
|
||||
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Semidry suit"));
|
||||
dt_dive->suit = "Semidry suit";
|
||||
break;
|
||||
case 6:
|
||||
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Dry suit"));
|
||||
dt_dive->suit = "Dry suit";
|
||||
break;
|
||||
default:
|
||||
// unknown, do nothing
|
||||
|
@ -335,14 +333,14 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
*/
|
||||
read_bytes(2);
|
||||
if (tmp_2bytes != 0x7FFF) {
|
||||
cylinder_t cyl = empty_cylinder;
|
||||
cylinder_t cyl;
|
||||
cyl.type.size.mliter = tmp_2bytes * 10;
|
||||
cyl.type.description = cyl_type_by_size(tmp_2bytes * 10);
|
||||
cyl.start.mbar = 200000;
|
||||
cyl.gasmix.he.permille = 0;
|
||||
cyl.gasmix.o2.permille = 210;
|
||||
cyl.start = 200_bar;
|
||||
cyl.gasmix.he = 0_percent;
|
||||
cyl.gasmix.o2 = 21_percent;
|
||||
cyl.manually_added = true;
|
||||
add_cloned_cylinder(&dt_dive->cylinders, cyl);
|
||||
dt_dive->cylinders.push_back(std::move(cyl));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -350,14 +348,14 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
*/
|
||||
read_bytes(2);
|
||||
if (tmp_2bytes != 0x7FFF)
|
||||
dt_dive->maxdepth.mm = dt_dive->dc.maxdepth.mm = (int32_t)tmp_2bytes * 10;
|
||||
dt_dive->maxdepth.mm = dt_dive->dcs[0].maxdepth.mm = (int32_t)tmp_2bytes * 10;
|
||||
|
||||
/*
|
||||
* Dive time in minutes.
|
||||
*/
|
||||
read_bytes(2);
|
||||
if (tmp_2bytes != 0x7FFF)
|
||||
dt_dive->duration.seconds = dt_dive->dc.duration.seconds = (uint32_t)tmp_2bytes * 60;
|
||||
dt_dive->duration.seconds = dt_dive->dcs[0].duration.seconds = (uint32_t)tmp_2bytes * 60;
|
||||
|
||||
/*
|
||||
* Minimum water temperature in C*100. If unknown, set it to 0K which
|
||||
|
@ -365,16 +363,16 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
*/
|
||||
read_bytes(2);
|
||||
if (tmp_2bytes != 0x7fff)
|
||||
dt_dive->watertemp.mkelvin = dt_dive->dc.watertemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
|
||||
dt_dive->watertemp.mkelvin = dt_dive->dcs[0].watertemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
|
||||
else
|
||||
dt_dive->watertemp.mkelvin = 0;
|
||||
dt_dive->watertemp = 0_K;
|
||||
|
||||
/*
|
||||
* Air used in bar*100.
|
||||
*/
|
||||
read_bytes(2);
|
||||
if (tmp_2bytes != 0x7FFF && dt_dive->cylinders.nr > 0)
|
||||
get_cylinder(dt_dive, 0)->gas_used.mliter = lrint(get_cylinder(dt_dive, 0)->type.size.mliter * (tmp_2bytes / 100.0));
|
||||
if (tmp_2bytes != 0x7FFF && dt_dive->cylinders.size() > 0)
|
||||
dt_dive->get_cylinder(0)->gas_used.mliter = lrint(dt_dive->get_cylinder(0)->type.size.mliter * (tmp_2bytes / 100.0));
|
||||
|
||||
/*
|
||||
* Dive Type 1 - Bit table. Subsurface don't have this record, but
|
||||
|
@ -382,30 +380,30 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
*/
|
||||
read_bytes(1);
|
||||
if (bit_set(tmp_1byte, 2))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "no stop")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "no stop"));
|
||||
if (bit_set(tmp_1byte, 3))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "deco")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "deco"));
|
||||
if (bit_set(tmp_1byte, 4))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "single ascent")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "single ascent"));
|
||||
if (bit_set(tmp_1byte, 5))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "multiple ascent")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "multiple ascent"));
|
||||
if (bit_set(tmp_1byte, 6))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fresh water")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "fresh water"));
|
||||
if (bit_set(tmp_1byte, 7))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "salt water")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "salt water"));
|
||||
|
||||
/*
|
||||
* Dive Type 2 - Bit table, use tags again
|
||||
*/
|
||||
read_bytes(1);
|
||||
if (bit_set(tmp_1byte, 0)) {
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup("nitrox"));
|
||||
taglist_add_tag(dt_dive->tags, "nitrox");
|
||||
is_nitrox = 1;
|
||||
}
|
||||
if (bit_set(tmp_1byte, 1)) {
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup("rebreather"));
|
||||
taglist_add_tag(dt_dive->tags, "rebreather");
|
||||
is_SCR = 1;
|
||||
dt_dive->dc.divemode = PSCR;
|
||||
dt_dive->dcs[0].divemode = PSCR;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -413,36 +411,36 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
*/
|
||||
read_bytes(1);
|
||||
if (bit_set(tmp_1byte, 0))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "sight seeing")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "sight seeing"));
|
||||
if (bit_set(tmp_1byte, 1))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "club dive")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "club dive"));
|
||||
if (bit_set(tmp_1byte, 2))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instructor")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "instructor"));
|
||||
if (bit_set(tmp_1byte, 3))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instruction")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "instruction"));
|
||||
if (bit_set(tmp_1byte, 4))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "night")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "night"));
|
||||
if (bit_set(tmp_1byte, 5))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "cave")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "cave"));
|
||||
if (bit_set(tmp_1byte, 6))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "ice")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "ice"));
|
||||
if (bit_set(tmp_1byte, 7))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "search")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "search"));
|
||||
|
||||
/*
|
||||
* Dive Activity 2 - Bit table, use tags again
|
||||
*/
|
||||
read_bytes(1);
|
||||
if (bit_set(tmp_1byte, 0))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "wreck")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "wreck"));
|
||||
if (bit_set(tmp_1byte, 1))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "river")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "river"));
|
||||
if (bit_set(tmp_1byte, 2))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "drift")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "drift"));
|
||||
if (bit_set(tmp_1byte, 3))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "photo")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "photo"));
|
||||
if (bit_set(tmp_1byte, 4))
|
||||
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "other")));
|
||||
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "other"));
|
||||
|
||||
/*
|
||||
* Other activities - String 1st byte = long
|
||||
|
@ -451,10 +449,9 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
read_bytes(1);
|
||||
if (tmp_1byte != 0) {
|
||||
read_string(tmp_string1);
|
||||
snprintf(buffer, sizeof(buffer), "%s: %s\n",
|
||||
QT_TRANSLATE_NOOP("gettextFromC", "Other activities"),
|
||||
tmp_notes_str= format_string_std("%s: %s\n",
|
||||
translate("gettextFromC", "Other activities"),
|
||||
tmp_string1);
|
||||
tmp_notes_str = strdup(buffer);
|
||||
free(tmp_string1);
|
||||
}
|
||||
|
||||
|
@ -464,7 +461,7 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
read_bytes(1);
|
||||
if (tmp_1byte != 0) {
|
||||
read_string(tmp_string1);
|
||||
dt_dive->buddy = strdup((char *)tmp_string1);
|
||||
dt_dive->buddy = (const char *)tmp_string1;
|
||||
free(tmp_string1);
|
||||
}
|
||||
|
||||
|
@ -474,15 +471,12 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
read_bytes(1);
|
||||
if (tmp_1byte != 0) {
|
||||
read_string(tmp_string1);
|
||||
int len = snprintf(buffer, sizeof(buffer), "%s%s:\n%s",
|
||||
tmp_notes_str ? tmp_notes_str : "",
|
||||
QT_TRANSLATE_NOOP("gettextFromC", "Datatrak/Wlog notes"),
|
||||
dt_dive->notes = format_string_std("%s%s:\n%s",
|
||||
tmp_notes_str.c_str(),
|
||||
translate("gettextFromC", "Datatrak/Wlog notes"),
|
||||
tmp_string1);
|
||||
dt_dive->notes = (char *)calloc((len +1), 1);
|
||||
memcpy(dt_dive->notes, buffer, len);
|
||||
free(tmp_string1);
|
||||
}
|
||||
free(tmp_notes_str);
|
||||
|
||||
/*
|
||||
* Alarms 1 and Alarms2 - Bit tables - Not in Subsurface, we use the profile
|
||||
|
@ -520,7 +514,7 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
libdc_model = dtrak_prepare_data(tmp_1byte, devdata);
|
||||
if (!libdc_model)
|
||||
report_error(translate("gettextFromC", "[Warning] Manual dive # %d\n"), dt_dive->number);
|
||||
dt_dive->dc.model = copy_string(devdata->model);
|
||||
dt_dive->dcs[0].model = devdata.model;
|
||||
|
||||
/*
|
||||
* Air usage, unknown use. Probably allows or deny manually entering gas
|
||||
|
@ -543,17 +537,17 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
compl_buffer = (unsigned char *) calloc(18 + profile_length, 1);
|
||||
rc = dt_libdc_buffer(membuf, profile_length, libdc_model, compl_buffer);
|
||||
if (rc == DC_STATUS_SUCCESS) {
|
||||
libdc_buffer_parser(dt_dive, devdata, compl_buffer, profile_length + 18);
|
||||
libdc_buffer_parser(dt_dive, &devdata, compl_buffer, profile_length + 18);
|
||||
} else {
|
||||
report_error(translate("gettextFromC", "[Error] Out of memory for dive %d. Abort parsing."), dt_dive->number);
|
||||
free(compl_buffer);
|
||||
goto bail;
|
||||
}
|
||||
if (is_nitrox && dt_dive->cylinders.nr > 0)
|
||||
get_cylinder(dt_dive, 0)->gasmix.o2.permille =
|
||||
if (is_nitrox && dt_dive->cylinders.size() > 0)
|
||||
dt_dive->get_cylinder(0)->gasmix.o2.permille =
|
||||
lrint(membuf[23] & 0x0F ? 20.0 + 2 * (membuf[23] & 0x0F) : 21.0) * 10;
|
||||
if (is_O2 && dt_dive->cylinders.nr > 0)
|
||||
get_cylinder(dt_dive, 0)->gasmix.o2.permille = membuf[23] * 10;
|
||||
if (is_O2 && dt_dive->cylinders.size() > 0)
|
||||
dt_dive->get_cylinder(0)->gasmix.o2.permille = membuf[23] * 10;
|
||||
free(compl_buffer);
|
||||
}
|
||||
JUMP(membuf, profile_length);
|
||||
|
@ -562,19 +556,16 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
|
|||
* Initialize some dive data not supported by Datatrak/WLog
|
||||
*/
|
||||
if (!libdc_model)
|
||||
dt_dive->dc.deviceid = 0;
|
||||
dt_dive->dcs[0].deviceid = 0;
|
||||
else
|
||||
dt_dive->dc.deviceid = 0xffffffff;
|
||||
dt_dive->dc.next = NULL;
|
||||
if (!is_SCR && dt_dive->cylinders.nr > 0) {
|
||||
get_cylinder(dt_dive, 0)->end.mbar = get_cylinder(dt_dive, 0)->start.mbar -
|
||||
((get_cylinder(dt_dive, 0)->gas_used.mliter / get_cylinder(dt_dive, 0)->type.size.mliter) * 1000);
|
||||
dt_dive->dcs[0].deviceid = 0xffffffff;
|
||||
if (!is_SCR && dt_dive->cylinders.size() > 0) {
|
||||
dt_dive->get_cylinder(0)->end.mbar = dt_dive->get_cylinder(0)->start.mbar -
|
||||
((dt_dive->get_cylinder(0)->gas_used.mliter / dt_dive->get_cylinder(0)->type.size.mliter) * 1000);
|
||||
}
|
||||
free(devdata);
|
||||
return (char *)membuf;
|
||||
bail:
|
||||
free(locality);
|
||||
free(devdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -607,7 +598,7 @@ static void wlog_compl_parser(std::string &wl_mem, struct dive *dt_dive, int dco
|
|||
pos_viz = offset + 258,
|
||||
pos_tank_init = offset + 266,
|
||||
pos_suit = offset + 268;
|
||||
char *wlog_notes = NULL, *wlog_suit = NULL, *buffer = NULL;
|
||||
char *wlog_notes = NULL, *wlog_suit = NULL;
|
||||
unsigned char *runner = (unsigned char *) wl_mem.data();
|
||||
|
||||
/*
|
||||
|
@ -619,24 +610,16 @@ static void wlog_compl_parser(std::string &wl_mem, struct dive *dt_dive, int dco
|
|||
(void)memcpy(wlog_notes_temp, runner + offset, NOTES_LENGTH);
|
||||
wlog_notes = to_utf8((unsigned char *) wlog_notes_temp);
|
||||
}
|
||||
if (dt_dive->notes && wlog_notes) {
|
||||
buffer = (char *)calloc (strlen(dt_dive->notes) + strlen(wlog_notes) + 1, 1);
|
||||
sprintf(buffer, "%s%s", dt_dive->notes, wlog_notes);
|
||||
free(dt_dive->notes);
|
||||
dt_dive->notes = copy_string(buffer);
|
||||
} else if (wlog_notes) {
|
||||
dt_dive->notes = copy_string(wlog_notes);
|
||||
}
|
||||
free(buffer);
|
||||
free(wlog_notes);
|
||||
if (wlog_notes)
|
||||
dt_dive->notes += wlog_notes;
|
||||
|
||||
/*
|
||||
* Weight in Kg * 100
|
||||
*/
|
||||
tmp = (int) two_bytes_to_int(runner[pos_weight + 1], runner[pos_weight]);
|
||||
if (tmp != 0x7fff) {
|
||||
weightsystem_t ws = { {tmp * 10}, QT_TRANSLATE_NOOP("gettextFromC", "unknown"), false };
|
||||
add_cloned_weightsystem(&dt_dive->weightsystems, ws);
|
||||
weightsystem_t ws = { {.grams = tmp * 10}, translate("gettextFromC", "unknown"), false };
|
||||
dt_dive->weightsystems.push_back(std::move(ws));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -655,8 +638,8 @@ static void wlog_compl_parser(std::string &wl_mem, struct dive *dt_dive, int dco
|
|||
*/
|
||||
tmp = (int) two_bytes_to_int(runner[pos_tank_init + 1], runner[pos_tank_init]);
|
||||
if (tmp != 0x7fff) {
|
||||
get_cylinder(dt_dive, 0)->start.mbar = tmp * 10;
|
||||
get_cylinder(dt_dive, 0)->end.mbar = get_cylinder(dt_dive, 0)->start.mbar - lrint(get_cylinder(dt_dive, 0)->gas_used.mliter / get_cylinder(dt_dive, 0)->type.size.mliter) * 1000;
|
||||
dt_dive->get_cylinder(0)->start.mbar = tmp * 10;
|
||||
dt_dive->get_cylinder(0)->end.mbar = dt_dive->get_cylinder(0)->start.mbar - lrint(dt_dive->get_cylinder(0)->gas_used.mliter / dt_dive->get_cylinder(0)->type.size.mliter) * 1000;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -670,7 +653,7 @@ static void wlog_compl_parser(std::string &wl_mem, struct dive *dt_dive, int dco
|
|||
wlog_suit = to_utf8((unsigned char *) wlog_suit_temp);
|
||||
}
|
||||
if (wlog_suit)
|
||||
dt_dive->suit = copy_string(wlog_suit);
|
||||
dt_dive->suit = wlog_suit;
|
||||
free(wlog_suit);
|
||||
}
|
||||
|
||||
|
@ -703,25 +686,24 @@ int datatrak_import(std::string &mem, std::string &wl_mem, struct divelog *log)
|
|||
runner = mem.data();
|
||||
JUMP(runner, 12);
|
||||
|
||||
// Secuential parsing. Abort if received NULL from dt_dive_parser.
|
||||
// Sequential parsing. Abort if received NULL from dt_dive_parser.
|
||||
while ((i < numdives) && (runner < maxbuf)) {
|
||||
struct dive *ptdive = alloc_dive();
|
||||
auto ptdive = std::make_unique<dive>();
|
||||
|
||||
runner = dt_dive_parser((unsigned char *)runner, ptdive, log, maxbuf);
|
||||
runner = dt_dive_parser((unsigned char *)runner, ptdive.get(), log, maxbuf);
|
||||
if (!wl_mem.empty())
|
||||
wlog_compl_parser(wl_mem, ptdive, i);
|
||||
wlog_compl_parser(wl_mem, ptdive.get(), i);
|
||||
if (runner == NULL) {
|
||||
report_error("%s", translate("gettextFromC", "Error: no dive"));
|
||||
free(ptdive);
|
||||
rc = 1;
|
||||
goto out;
|
||||
} else {
|
||||
record_dive_to_table(ptdive, log->dives);
|
||||
log->dives.record_dive(std::move(ptdive));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
out:
|
||||
sort_dive_table(log->dives);
|
||||
log->dives.sort();
|
||||
return rc;
|
||||
bail:
|
||||
return 1;
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include <assert.h>
|
||||
|
||||
#include "deco.h"
|
||||
#include "ssrf.h"
|
||||
#include "dive.h"
|
||||
#include "gas.h"
|
||||
#include "subsurface-string.h"
|
||||
|
@ -216,13 +215,13 @@ static double vpmb_tolerated_ambient_pressure(struct deco_state *ds, double refe
|
|||
return ds->tissue_n2_sat[ci] + ds->tissue_he_sat[ci] + vpmb_config.other_gases_pressure - total_gradient;
|
||||
}
|
||||
|
||||
extern "C" double tissue_tolerance_calc(struct deco_state *ds, const struct dive *dive, double pressure, bool in_planner)
|
||||
double tissue_tolerance_calc(struct deco_state *ds, const struct dive *dive, double pressure, bool in_planner)
|
||||
{
|
||||
int ci = -1;
|
||||
double ret_tolerance_limit_ambient_pressure = 0.0;
|
||||
double gf_high = buehlmann_config.gf_high;
|
||||
double gf_low = buehlmann_config.gf_low;
|
||||
double surface = get_surface_pressure_in_mbar(dive, true) / 1000.0;
|
||||
double surface = dive->get_surface_pressure().mbar / 1000.0;
|
||||
double lowest_ceiling = 0.0;
|
||||
double tissue_lowest_ceiling[16];
|
||||
|
||||
|
@ -323,7 +322,7 @@ static double calc_surface_phase(double surface_pressure, double he_pressure, do
|
|||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void vpmb_start_gradient(struct deco_state *ds)
|
||||
void vpmb_start_gradient(struct deco_state *ds)
|
||||
{
|
||||
int ci;
|
||||
|
||||
|
@ -333,7 +332,7 @@ extern "C" void vpmb_start_gradient(struct deco_state *ds)
|
|||
}
|
||||
}
|
||||
|
||||
extern "C" void vpmb_next_gradient(struct deco_state *ds, double deco_time, double surface_pressure, bool in_planner)
|
||||
void vpmb_next_gradient(struct deco_state *ds, double deco_time, double surface_pressure, bool in_planner)
|
||||
{
|
||||
int ci;
|
||||
double n2_b, n2_c;
|
||||
|
@ -379,7 +378,7 @@ static double solve_cubic(double A, double B, double C)
|
|||
}
|
||||
|
||||
|
||||
extern "C" void nuclear_regeneration(struct deco_state *ds, double time)
|
||||
void nuclear_regeneration(struct deco_state *ds, double time)
|
||||
{
|
||||
time /= 60.0;
|
||||
int ci;
|
||||
|
@ -411,7 +410,7 @@ static double calc_inner_pressure(double crit_radius, double onset_tension, doub
|
|||
}
|
||||
|
||||
// Calculates the crushing pressure in the given moment. Updates crushing_onset_tension and critical radius if needed
|
||||
extern "C" void calc_crushing_pressure(struct deco_state *ds, double pressure)
|
||||
void calc_crushing_pressure(struct deco_state *ds, double pressure)
|
||||
{
|
||||
int ci;
|
||||
double gradient;
|
||||
|
@ -443,12 +442,11 @@ extern "C" void calc_crushing_pressure(struct deco_state *ds, double pressure)
|
|||
}
|
||||
|
||||
/* add period_in_seconds at the given pressure and gas to the deco calculation */
|
||||
extern "C" void add_segment(struct deco_state *ds, double pressure, struct gasmix gasmix, int period_in_seconds, int ccpo2, enum divemode_t divemode, int, bool in_planner)
|
||||
void add_segment(struct deco_state *ds, double pressure, struct gasmix gasmix, int period_in_seconds, int ccpo2, enum divemode_t divemode, int, bool in_planner)
|
||||
{
|
||||
int ci;
|
||||
struct gas_pressures pressures;
|
||||
bool icd = false;
|
||||
fill_pressures(&pressures, pressure - ((in_planner && (decoMode(true) == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE),
|
||||
gas_pressures pressures = fill_pressures(pressure - ((in_planner && (decoMode(true) == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE),
|
||||
gasmix, (double) ccpo2 / 1000.0, divemode);
|
||||
|
||||
for (ci = 0; ci < 16; ci++) {
|
||||
|
@ -476,7 +474,7 @@ extern "C" void add_segment(struct deco_state *ds, double pressure, struct gasmi
|
|||
}
|
||||
|
||||
#if DECO_CALC_DEBUG
|
||||
extern "C" void dump_tissues(struct deco_state *ds)
|
||||
void dump_tissues(struct deco_state *ds)
|
||||
{
|
||||
int ci;
|
||||
printf("N2 tissues:");
|
||||
|
@ -489,7 +487,7 @@ extern "C" void dump_tissues(struct deco_state *ds)
|
|||
}
|
||||
#endif
|
||||
|
||||
extern "C" void clear_vpmb_state(struct deco_state *ds)
|
||||
void clear_vpmb_state(struct deco_state *ds)
|
||||
{
|
||||
int ci;
|
||||
for (ci = 0; ci < 16; ci++) {
|
||||
|
@ -497,15 +495,15 @@ extern "C" void clear_vpmb_state(struct deco_state *ds)
|
|||
ds->max_he_crushing_pressure[ci] = 0.0;
|
||||
}
|
||||
ds->max_ambient_pressure = 0;
|
||||
ds->first_ceiling_pressure.mbar = 0;
|
||||
ds->max_bottom_ceiling_pressure.mbar = 0;
|
||||
ds->first_ceiling_pressure = 0_bar;
|
||||
ds->max_bottom_ceiling_pressure = 0_bar;
|
||||
}
|
||||
|
||||
extern "C" void clear_deco(struct deco_state *ds, double surface_pressure, bool in_planner)
|
||||
void clear_deco(struct deco_state *ds, double surface_pressure, bool in_planner)
|
||||
{
|
||||
int ci;
|
||||
|
||||
memset(ds, 0, sizeof(*ds));
|
||||
*ds = deco_state();
|
||||
clear_vpmb_state(ds);
|
||||
for (ci = 0; ci < 16; ci++) {
|
||||
ds->tissue_n2_sat[ci] = (surface_pressure - ((in_planner && (decoMode(true) == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * N2_IN_AIR / 1000;
|
||||
|
@ -545,7 +543,7 @@ void deco_state_cache::restore(struct deco_state *target, bool keep_vpmb_state)
|
|||
*target = *data;
|
||||
}
|
||||
|
||||
extern "C" int deco_allowed_depth(double tissues_tolerance, double surface_pressure, const struct dive *dive, bool smooth)
|
||||
int deco_allowed_depth(double tissues_tolerance, double surface_pressure, const struct dive *dive, bool smooth)
|
||||
{
|
||||
int depth;
|
||||
double pressure_delta;
|
||||
|
@ -553,7 +551,7 @@ extern "C" int deco_allowed_depth(double tissues_tolerance, double surface_press
|
|||
/* Avoid negative depths */
|
||||
pressure_delta = tissues_tolerance > surface_pressure ? tissues_tolerance - surface_pressure : 0.0;
|
||||
|
||||
depth = rel_mbar_to_depth(lrint(pressure_delta * 1000), dive);
|
||||
depth = dive->rel_mbar_to_depth(lrint(pressure_delta * 1000));
|
||||
|
||||
if (!smooth)
|
||||
depth = lrint(ceil(depth / DECO_STOPS_MULTIPLIER_MM) * DECO_STOPS_MULTIPLIER_MM);
|
||||
|
@ -564,7 +562,7 @@ extern "C" int deco_allowed_depth(double tissues_tolerance, double surface_press
|
|||
return depth;
|
||||
}
|
||||
|
||||
extern "C" void set_gf(short gflow, short gfhigh)
|
||||
void set_gf(short gflow, short gfhigh)
|
||||
{
|
||||
if (gflow != -1)
|
||||
buehlmann_config.gf_low = (double)gflow / 100.0;
|
||||
|
@ -572,7 +570,7 @@ extern "C" void set_gf(short gflow, short gfhigh)
|
|||
buehlmann_config.gf_high = (double)gfhigh / 100.0;
|
||||
}
|
||||
|
||||
extern "C" void set_vpmb_conservatism(short conservatism)
|
||||
void set_vpmb_conservatism(short conservatism)
|
||||
{
|
||||
if (conservatism < 0)
|
||||
vpmb_config.conservatism = 0;
|
||||
|
@ -582,9 +580,9 @@ extern "C" void set_vpmb_conservatism(short conservatism)
|
|||
vpmb_config.conservatism = conservatism;
|
||||
}
|
||||
|
||||
extern "C" double get_gf(struct deco_state *ds, double ambpressure_bar, const struct dive *dive)
|
||||
double get_gf(struct deco_state *ds, double ambpressure_bar, const struct dive *dive)
|
||||
{
|
||||
double surface_pressure_bar = get_surface_pressure_in_mbar(dive, true) / 1000.0;
|
||||
double surface_pressure_bar = dive->get_surface_pressure().mbar / 1000.0;
|
||||
double gf_low = buehlmann_config.gf_low;
|
||||
double gf_high = buehlmann_config.gf_high;
|
||||
double gf;
|
||||
|
@ -596,7 +594,7 @@ extern "C" double get_gf(struct deco_state *ds, double ambpressure_bar, const st
|
|||
return gf;
|
||||
}
|
||||
|
||||
extern "C" double regressiona(const struct deco_state *ds)
|
||||
double regressiona(const struct deco_state *ds)
|
||||
{
|
||||
if (ds->sum1 > 1) {
|
||||
double avxy = ds->sumxy / ds->sum1;
|
||||
|
@ -609,7 +607,7 @@ extern "C" double regressiona(const struct deco_state *ds)
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
extern "C" double regressionb(const struct deco_state *ds)
|
||||
double regressionb(const struct deco_state *ds)
|
||||
{
|
||||
if (ds->sum1)
|
||||
return ds->sumy / ds->sum1 - ds->sumx * regressiona(ds) / ds->sum1;
|
||||
|
@ -617,14 +615,14 @@ extern "C" double regressionb(const struct deco_state *ds)
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
extern "C" void reset_regression(struct deco_state *ds)
|
||||
void reset_regression(struct deco_state *ds)
|
||||
{
|
||||
ds->sum1 = 0;
|
||||
ds->sumxx = ds->sumx = 0L;
|
||||
ds->sumy = ds->sumxy = 0.0;
|
||||
}
|
||||
|
||||
extern "C" void update_regression(struct deco_state *ds, const struct dive *dive)
|
||||
void update_regression(struct deco_state *ds, const struct dive *dive)
|
||||
{
|
||||
if (!ds->plot_depth)
|
||||
return;
|
||||
|
@ -632,12 +630,12 @@ extern "C" void update_regression(struct deco_state *ds, const struct dive *dive
|
|||
ds->sumx += ds->plot_depth;
|
||||
ds->sumxx += (long)ds->plot_depth * ds->plot_depth;
|
||||
double n2_gradient, he_gradient, total_gradient;
|
||||
n2_gradient = update_gradient(ds, depth_to_bar(ds->plot_depth, dive), ds->bottom_n2_gradient[ds->ci_pointing_to_guiding_tissue]);
|
||||
he_gradient = update_gradient(ds, depth_to_bar(ds->plot_depth, dive), ds->bottom_he_gradient[ds->ci_pointing_to_guiding_tissue]);
|
||||
n2_gradient = update_gradient(ds, dive->depth_to_bar(ds->plot_depth), ds->bottom_n2_gradient[ds->ci_pointing_to_guiding_tissue]);
|
||||
he_gradient = update_gradient(ds, dive->depth_to_bar(ds->plot_depth), ds->bottom_he_gradient[ds->ci_pointing_to_guiding_tissue]);
|
||||
total_gradient = ((n2_gradient * ds->tissue_n2_sat[ds->ci_pointing_to_guiding_tissue]) + (he_gradient * ds->tissue_he_sat[ds->ci_pointing_to_guiding_tissue]))
|
||||
/ (ds->tissue_n2_sat[ds->ci_pointing_to_guiding_tissue] + ds->tissue_he_sat[ds->ci_pointing_to_guiding_tissue]);
|
||||
|
||||
double buehlmann_gradient = (1.0 / ds->buehlmann_inertgas_b[ds->ci_pointing_to_guiding_tissue] - 1.0) * depth_to_bar(ds->plot_depth, dive) + ds->buehlmann_inertgas_a[ds->ci_pointing_to_guiding_tissue];
|
||||
double buehlmann_gradient = (1.0 / ds->buehlmann_inertgas_b[ds->ci_pointing_to_guiding_tissue] - 1.0) * dive->depth_to_bar(ds->plot_depth) + ds->buehlmann_inertgas_a[ds->ci_pointing_to_guiding_tissue];
|
||||
double gf = (total_gradient - vpmb_config.other_gases_pressure) / buehlmann_gradient;
|
||||
ds->sumxy += gf * ds->plot_depth;
|
||||
ds->sumy += gf;
|
||||
|
|
61
core/deco.h
|
@ -5,46 +5,43 @@
|
|||
#include "units.h"
|
||||
#include "gas.h"
|
||||
#include "divemode.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <memory>
|
||||
|
||||
struct dive;
|
||||
struct divecomputer;
|
||||
struct decostop;
|
||||
|
||||
struct deco_state {
|
||||
double tissue_n2_sat[16];
|
||||
double tissue_he_sat[16];
|
||||
double tolerated_by_tissue[16];
|
||||
double tissue_inertgas_saturation[16];
|
||||
double buehlmann_inertgas_a[16];
|
||||
double buehlmann_inertgas_b[16];
|
||||
double tissue_n2_sat[16] = {};
|
||||
double tissue_he_sat[16] = {};
|
||||
double tolerated_by_tissue[16] = {};
|
||||
double tissue_inertgas_saturation[16] = {};
|
||||
double buehlmann_inertgas_a[16] = {};
|
||||
double buehlmann_inertgas_b[16] = {};
|
||||
|
||||
double max_n2_crushing_pressure[16];
|
||||
double max_he_crushing_pressure[16];
|
||||
double max_n2_crushing_pressure[16] = {};
|
||||
double max_he_crushing_pressure[16] = {};
|
||||
|
||||
double crushing_onset_tension[16]; // total inert gas tension in the t* moment
|
||||
double n2_regen_radius[16]; // rs
|
||||
double he_regen_radius[16];
|
||||
double max_ambient_pressure; // last moment we were descending
|
||||
double crushing_onset_tension[16] = {}; // total inert gas tension in the t* moment
|
||||
double n2_regen_radius[16] = {}; // rs
|
||||
double he_regen_radius[16] = {};
|
||||
double max_ambient_pressure = 0.0; // last moment we were descending
|
||||
|
||||
double bottom_n2_gradient[16];
|
||||
double bottom_he_gradient[16];
|
||||
double bottom_n2_gradient[16] = {};
|
||||
double bottom_he_gradient[16] = {};
|
||||
|
||||
double initial_n2_gradient[16];
|
||||
double initial_he_gradient[16];
|
||||
double initial_n2_gradient[16] = {};
|
||||
double initial_he_gradient[16] = {};
|
||||
pressure_t first_ceiling_pressure;
|
||||
pressure_t max_bottom_ceiling_pressure;
|
||||
int ci_pointing_to_guiding_tissue;
|
||||
double gf_low_pressure_this_dive;
|
||||
int deco_time;
|
||||
bool icd_warning;
|
||||
int sum1;
|
||||
long sumx, sumxx;
|
||||
double sumy, sumxy;
|
||||
int plot_depth;
|
||||
int ci_pointing_to_guiding_tissue = 0;
|
||||
double gf_low_pressure_this_dive = 0.0;
|
||||
int deco_time = 0;
|
||||
bool icd_warning = false;
|
||||
int sum1 = 0;
|
||||
long sumx = 0, sumxx = 0;
|
||||
double sumy = 0, sumxy = 0;
|
||||
int plot_depth = 0;
|
||||
};
|
||||
|
||||
extern const double buehlmann_N2_t_halflife[];
|
||||
|
@ -70,12 +67,6 @@ extern double regressionb(const struct deco_state *ds);
|
|||
extern void reset_regression(struct deco_state *ds);
|
||||
extern void update_regression(struct deco_state *ds, const struct dive *dive);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
// C++ only functions
|
||||
|
||||
#include <memory>
|
||||
struct deco_state_cache {
|
||||
// Test if there is cached data
|
||||
operator bool () {
|
||||
|
@ -87,6 +78,4 @@ private:
|
|||
std::unique_ptr<deco_state> data;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // DECO_H
|
||||
|
|
217
core/device.cpp
|
@ -8,7 +8,7 @@
|
|||
#include "selection.h"
|
||||
#include "core/settings/qPrefDiveComputer.h"
|
||||
|
||||
struct fingerprint_table fingerprint_table;
|
||||
fingerprint_table fingerprints;
|
||||
|
||||
static bool same_device(const device &dev1, const device &dev2)
|
||||
{
|
||||
|
@ -18,42 +18,35 @@ static bool same_device(const device &dev1, const device &dev2)
|
|||
|
||||
bool device::operator<(const device &a) const
|
||||
{
|
||||
int diff;
|
||||
|
||||
diff = model.compare(a.model);
|
||||
if (diff)
|
||||
return diff < 0;
|
||||
|
||||
return serialNumber < a.serialNumber;
|
||||
return std::tie(model, serialNumber) < std::tie(a.model, a.serialNumber);
|
||||
}
|
||||
|
||||
extern "C" const struct device *get_device_for_dc(const struct device_table *table, const struct divecomputer *dc)
|
||||
const struct device *get_device_for_dc(const device_table &table, const struct divecomputer *dc)
|
||||
{
|
||||
if (!dc->model || !dc->serial)
|
||||
if (dc->model.empty() || dc->serial.empty())
|
||||
return NULL;
|
||||
|
||||
const std::vector<device> &dcs = table->devices;
|
||||
device dev { dc->model, dc->serial };
|
||||
auto it = std::lower_bound(dcs.begin(), dcs.end(), dev);
|
||||
return it != dcs.end() && same_device(*it, dev) ? &*it : NULL;
|
||||
auto it = std::lower_bound(table.begin(), table.end(), dev);
|
||||
return it != table.end() && same_device(*it, dev) ? &*it : NULL;
|
||||
}
|
||||
|
||||
extern "C" int get_or_add_device_for_dc(struct device_table *table, const struct divecomputer *dc)
|
||||
int get_or_add_device_for_dc(device_table &table, const struct divecomputer *dc)
|
||||
{
|
||||
if (!dc->model || !dc->serial)
|
||||
if (dc->model.empty() || dc->serial.empty())
|
||||
return -1;
|
||||
const struct device *dev = get_device_for_dc(table, dc);
|
||||
if (dev) {
|
||||
auto it = std::lower_bound(table->devices.begin(), table->devices.end(), *dev);
|
||||
return it - table->devices.begin();
|
||||
auto it = std::lower_bound(table.begin(), table.end(), *dev);
|
||||
return it - table.begin();
|
||||
}
|
||||
return create_device_node(table, dc->model, dc->serial, "");
|
||||
return create_device_node(table, dc->model, dc->serial, std::string());
|
||||
}
|
||||
|
||||
extern "C" bool device_exists(const struct device_table *device_table, const struct device *dev)
|
||||
bool device_exists(const device_table &table, const struct device &dev)
|
||||
{
|
||||
auto it = std::lower_bound(device_table->devices.begin(), device_table->devices.end(), *dev);
|
||||
return it != device_table->devices.end() && same_device(*it, *dev);
|
||||
auto it = std::lower_bound(table.begin(), table.end(), dev);
|
||||
return it != table.end() && same_device(*it, dev);
|
||||
}
|
||||
|
||||
void device::showchanges(const std::string &n) const
|
||||
|
@ -66,7 +59,7 @@ void device::showchanges(const std::string &n) const
|
|||
}
|
||||
}
|
||||
|
||||
static int addDC(std::vector<device> &dcs, const std::string &m, const std::string &s, const std::string &n)
|
||||
int create_device_node(device_table &dcs, const std::string &m, const std::string &s, const std::string &n)
|
||||
{
|
||||
if (m.empty() || s.empty())
|
||||
return -1;
|
||||
|
@ -86,220 +79,122 @@ static int addDC(std::vector<device> &dcs, const std::string &m, const std::stri
|
|||
}
|
||||
}
|
||||
|
||||
extern "C" int create_device_node(struct device_table *device_table, const char *model, const char *serial, const char *nickname)
|
||||
int add_to_device_table(device_table &device_table, const struct device &dev)
|
||||
{
|
||||
return addDC(device_table->devices, model ?: "", serial ?: "", nickname ?: "");
|
||||
return create_device_node(device_table, dev.model, dev.serialNumber, dev.nickName);
|
||||
}
|
||||
|
||||
extern "C" int add_to_device_table(struct device_table *device_table, const struct device *dev)
|
||||
int remove_device(device_table &table, const struct device &dev)
|
||||
{
|
||||
return create_device_node(device_table, dev->model.c_str(), dev->serialNumber.c_str(), dev->nickName.c_str());
|
||||
}
|
||||
|
||||
extern "C" int remove_device(struct device_table *device_table, const struct device *dev)
|
||||
{
|
||||
auto it = std::lower_bound(device_table->devices.begin(), device_table->devices.end(), *dev);
|
||||
if (it != device_table->devices.end() && same_device(*it, *dev)) {
|
||||
int idx = it - device_table->devices.begin();
|
||||
device_table->devices.erase(it);
|
||||
auto it = std::lower_bound(table.begin(), table.end(), dev);
|
||||
if (it != table.end() && same_device(*it, dev)) {
|
||||
int idx = it - table.begin();
|
||||
table.erase(it);
|
||||
return idx;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void remove_from_device_table(struct device_table *device_table, int idx)
|
||||
void remove_from_device_table(device_table &table, int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= (int)device_table->devices.size())
|
||||
if (idx < 0 || idx >= (int)table.size())
|
||||
return;
|
||||
device_table->devices.erase(device_table->devices.begin() + idx);
|
||||
}
|
||||
|
||||
extern "C" void clear_device_table(struct device_table *device_table)
|
||||
{
|
||||
device_table->devices.clear();
|
||||
table.erase(table.begin() + idx);
|
||||
}
|
||||
|
||||
/* Returns whether the given device is used by a selected dive. */
|
||||
extern "C" bool device_used_by_selected_dive(const struct device *dev)
|
||||
bool device_used_by_selected_dive(const struct device &dev)
|
||||
{
|
||||
for (dive *d: getDiveSelection()) {
|
||||
struct divecomputer *dc;
|
||||
for_each_dc (d, dc) {
|
||||
if (dc->deviceid == dev->deviceId)
|
||||
for (auto &dc: d->dcs) {
|
||||
if (dc.deviceid == dev.deviceId)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" int is_default_dive_computer_device(const char *name)
|
||||
int is_default_dive_computer_device(const char *name)
|
||||
{
|
||||
return qPrefDiveComputer::device() == name;
|
||||
}
|
||||
|
||||
const char *get_dc_nickname(const struct divecomputer *dc)
|
||||
std::string get_dc_nickname(const struct divecomputer *dc)
|
||||
{
|
||||
const device *existNode = get_device_for_dc(divelog.devices, dc);
|
||||
|
||||
if (existNode && !existNode->nickName.empty())
|
||||
return existNode->nickName.c_str();
|
||||
return existNode->nickName;
|
||||
else
|
||||
return dc->model;
|
||||
}
|
||||
|
||||
extern "C" int nr_devices(const struct device_table *table)
|
||||
{
|
||||
return (int)table->devices.size();
|
||||
}
|
||||
|
||||
extern "C" const struct device *get_device(const struct device_table *table, int i)
|
||||
{
|
||||
if (i < 0 || i > nr_devices(table))
|
||||
return NULL;
|
||||
return &table->devices[i];
|
||||
}
|
||||
|
||||
extern "C" struct device *get_device_mutable(struct device_table *table, int i)
|
||||
{
|
||||
if (i < 0 || i > nr_devices(table))
|
||||
return NULL;
|
||||
return &table->devices[i];
|
||||
}
|
||||
|
||||
extern "C" const char *device_get_model(const struct device *dev)
|
||||
{
|
||||
return dev ? dev->model.c_str() : NULL;
|
||||
}
|
||||
|
||||
extern "C" const char *device_get_serial(const struct device *dev)
|
||||
{
|
||||
return dev ? dev->serialNumber.c_str() : NULL;
|
||||
}
|
||||
|
||||
extern "C" const char *device_get_nickname(const struct device *dev)
|
||||
{
|
||||
return dev ? dev->nickName.c_str() : NULL;
|
||||
}
|
||||
|
||||
extern "C" struct device_table *alloc_device_table()
|
||||
{
|
||||
return new struct device_table;
|
||||
}
|
||||
|
||||
extern "C" void free_device_table(struct device_table *devices)
|
||||
{
|
||||
delete devices;
|
||||
}
|
||||
|
||||
// managing fingerprint data
|
||||
bool fingerprint_record::operator<(const fingerprint_record &a) const
|
||||
{
|
||||
if (model == a.model)
|
||||
return serial < a.serial;
|
||||
return model < a.model;
|
||||
return std::tie(model, serial) < std::tie(a.model, a.serial);
|
||||
}
|
||||
|
||||
// annoyingly, the Cressi Edy doesn't support a serial number (it's always 0), but still uses fingerprints
|
||||
// so we can't bail on the serial number being 0
|
||||
extern "C" unsigned int get_fingerprint_data(const struct fingerprint_table *table, uint32_t model, uint32_t serial, const unsigned char **fp_out)
|
||||
std::pair<int, const unsigned char *> get_fingerprint_data(const fingerprint_table &table, uint32_t model, uint32_t serial)
|
||||
{
|
||||
if (model == 0 || fp_out == nullptr)
|
||||
return 0;
|
||||
if (model == 0)
|
||||
return { 0, nullptr };
|
||||
struct fingerprint_record fpr = { model, serial };
|
||||
auto it = std::lower_bound(table->fingerprints.begin(), table->fingerprints.end(), fpr);
|
||||
if (it != table->fingerprints.end() && it->model == model && it->serial == serial) {
|
||||
auto it = std::lower_bound(table.begin(), table.end(), fpr);
|
||||
if (it != table.end() && it->model == model && it->serial == serial) {
|
||||
// std::lower_bound gets us the first element that isn't smaller than what we are looking
|
||||
// for - so if one is found, we still need to check for equality
|
||||
if (has_dive(it->fdeviceid, it->fdiveid)) {
|
||||
*fp_out = it->raw_data;
|
||||
return it->fsize;
|
||||
if (divelog.dives.has_dive(it->fdeviceid, it->fdiveid))
|
||||
return { it->fsize, it->raw_data.get() };
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return { 0, nullptr };
|
||||
}
|
||||
|
||||
extern "C" void create_fingerprint_node(struct fingerprint_table *table, uint32_t model, uint32_t serial,
|
||||
void create_fingerprint_node(fingerprint_table &table, uint32_t model, uint32_t serial,
|
||||
const unsigned char *raw_data_in, unsigned int fsize, uint32_t fdeviceid, uint32_t fdiveid)
|
||||
{
|
||||
// since raw data can contain \0 we copy this manually, not as string
|
||||
unsigned char *raw_data = (unsigned char *)malloc(fsize);
|
||||
if (!raw_data)
|
||||
return;
|
||||
memcpy(raw_data, raw_data_in, fsize);
|
||||
auto raw_data = std::make_unique<unsigned char []>(fsize);
|
||||
std::copy(raw_data_in, raw_data_in + fsize, raw_data.get());
|
||||
|
||||
struct fingerprint_record fpr = { model, serial, raw_data, fsize, fdeviceid, fdiveid };
|
||||
auto it = std::lower_bound(table->fingerprints.begin(), table->fingerprints.end(), fpr);
|
||||
if (it != table->fingerprints.end() && it->model == model && it->serial == serial) {
|
||||
struct fingerprint_record fpr = { model, serial, std::move(raw_data), fsize, fdeviceid, fdiveid };
|
||||
auto it = std::lower_bound(table.begin(), table.end(), fpr);
|
||||
if (it != table.end() && it->model == model && it->serial == serial) {
|
||||
// std::lower_bound gets us the first element that isn't smaller than what we are looking
|
||||
// for - so if one is found, we still need to check for equality - and then we
|
||||
// can update the existing entry; first we free the memory for the stored raw data
|
||||
free(it->raw_data);
|
||||
it->fdeviceid = fdeviceid;
|
||||
it->fdiveid = fdiveid;
|
||||
it->raw_data = raw_data;
|
||||
it->raw_data = std::move(fpr.raw_data);
|
||||
it->fsize = fsize;
|
||||
} else {
|
||||
// insert a new one
|
||||
table->fingerprints.insert(it, fpr);
|
||||
table.insert(it, std::move(fpr));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void create_fingerprint_node_from_hex(struct fingerprint_table *table, uint32_t model, uint32_t serial,
|
||||
const char *hex_data, uint32_t fdeviceid, uint32_t fdiveid)
|
||||
void create_fingerprint_node_from_hex(fingerprint_table &table, uint32_t model, uint32_t serial,
|
||||
const std::string &hex_data, uint32_t fdeviceid, uint32_t fdiveid)
|
||||
{
|
||||
QByteArray raw = QByteArray::fromHex(hex_data);
|
||||
QByteArray raw = QByteArray::fromHex(hex_data.c_str());
|
||||
create_fingerprint_node(table, model, serial,
|
||||
(const unsigned char *)raw.constData(), raw.size(), fdeviceid, fdiveid);
|
||||
}
|
||||
|
||||
extern "C" int nr_fingerprints(struct fingerprint_table *table)
|
||||
{
|
||||
return table->fingerprints.size();
|
||||
}
|
||||
|
||||
extern "C" uint32_t fp_get_model(struct fingerprint_table *table, unsigned int i)
|
||||
{
|
||||
if (!table || i >= table->fingerprints.size())
|
||||
return 0;
|
||||
return table->fingerprints[i].model;
|
||||
}
|
||||
|
||||
extern "C" uint32_t fp_get_serial(struct fingerprint_table *table, unsigned int i)
|
||||
{
|
||||
if (!table || i >= table->fingerprints.size())
|
||||
return 0;
|
||||
return table->fingerprints[i].serial;
|
||||
}
|
||||
|
||||
extern "C" uint32_t fp_get_deviceid(struct fingerprint_table *table, unsigned int i)
|
||||
{
|
||||
if (!table || i >= table->fingerprints.size())
|
||||
return 0;
|
||||
return table->fingerprints[i].fdeviceid;
|
||||
}
|
||||
|
||||
extern "C" uint32_t fp_get_diveid(struct fingerprint_table *table, unsigned int i)
|
||||
{
|
||||
if (!table || i >= table->fingerprints.size())
|
||||
return 0;
|
||||
return table->fingerprints[i].fdiveid;
|
||||
}
|
||||
|
||||
static char to_hex_digit(unsigned char d)
|
||||
{
|
||||
return d <= 9 ? d + '0' : d - 10 + 'a';
|
||||
}
|
||||
|
||||
std::string fp_get_data(struct fingerprint_table *table, unsigned int i)
|
||||
std::string fingerprint_record::get_data() const
|
||||
{
|
||||
if (!table || i >= table->fingerprints.size())
|
||||
return std::string();
|
||||
struct fingerprint_record *fpr = &table->fingerprints[i];
|
||||
std::string res(fpr->fsize * 2, ' ');
|
||||
for (unsigned int i = 0; i < fpr->fsize; ++i) {
|
||||
res[2 * i] = to_hex_digit((fpr->raw_data[i] >> 4) & 0xf);
|
||||
res[2 * i + 1] = to_hex_digit(fpr->raw_data[i] & 0xf);
|
||||
std::string res(fsize * 2, ' ');
|
||||
for (unsigned int i = 0; i < fsize; ++i) {
|
||||
res[2 * i] = to_hex_digit((raw_data[i] >> 4) & 0xf);
|
||||
res[2 * i + 1] = to_hex_digit(raw_data[i] & 0xf);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
112
core/device.h
|
@ -3,73 +3,13 @@
|
|||
#define DEVICE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct divecomputer;
|
||||
struct device;
|
||||
struct device_table;
|
||||
struct dive_table;
|
||||
|
||||
// global device table
|
||||
extern struct fingerprint_table fingerprint_table;
|
||||
|
||||
extern int create_device_node(struct device_table *table, const char *model, const char *serial, const char *nickname);
|
||||
extern int nr_devices(const struct device_table *table);
|
||||
extern const struct device *get_device(const struct device_table *table, int i);
|
||||
extern struct device *get_device_mutable(struct device_table *table, int i);
|
||||
extern void clear_device_table(struct device_table *table);
|
||||
const char *get_dc_nickname(const struct divecomputer *dc);
|
||||
extern bool device_used_by_selected_dive(const struct device *dev);
|
||||
|
||||
extern const struct device *get_device_for_dc(const struct device_table *table, const struct divecomputer *dc);
|
||||
extern int get_or_add_device_for_dc(struct device_table *table, const struct divecomputer *dc);
|
||||
extern bool device_exists(const struct device_table *table, const struct device *dev);
|
||||
extern int add_to_device_table(struct device_table *table, const struct device *dev); // returns index
|
||||
extern int remove_device(struct device_table *table, const struct device *dev); // returns index or -1 if not found
|
||||
extern void remove_from_device_table(struct device_table *table, int idx);
|
||||
|
||||
// struct device accessors for C-code. The returned strings are not stable!
|
||||
const char *device_get_model(const struct device *dev);
|
||||
const char *device_get_serial(const struct device *dev);
|
||||
const char *device_get_nickname(const struct device *dev);
|
||||
|
||||
// for C code that needs to alloc/free a device table. (Let's try to get rid of those)
|
||||
extern struct device_table *alloc_device_table();
|
||||
extern void free_device_table(struct device_table *devices);
|
||||
|
||||
// create fingerprint entry - raw data remains owned by caller
|
||||
extern void create_fingerprint_node(struct fingerprint_table *table, uint32_t model, uint32_t serial,
|
||||
const unsigned char *raw_data, unsigned int fsize, uint32_t fdeviceid, uint32_t fdiveid);
|
||||
extern void create_fingerprint_node_from_hex(struct fingerprint_table *table, uint32_t model, uint32_t serial,
|
||||
const char *hex_data, uint32_t fdeviceid, uint32_t fdiveid);
|
||||
// look up the fingerprint for model/serial - returns the number of bytes in the fingerprint; memory owned by the table
|
||||
extern unsigned int get_fingerprint_data(const struct fingerprint_table *table, uint32_t model, uint32_t serial, const unsigned char **fp_out);
|
||||
|
||||
// access the fingerprint data from C
|
||||
extern int nr_fingerprints(struct fingerprint_table *table);
|
||||
extern uint32_t fp_get_model(struct fingerprint_table *table, unsigned int i);
|
||||
extern uint32_t fp_get_serial(struct fingerprint_table *table, unsigned int i);
|
||||
extern uint32_t fp_get_deviceid(struct fingerprint_table *table, unsigned int i);
|
||||
extern uint32_t fp_get_diveid(struct fingerprint_table *table, unsigned int i);
|
||||
|
||||
extern int is_default_dive_computer_device(const char *);
|
||||
|
||||
typedef void (*device_callback_t)(const char *name, void *userdata);
|
||||
|
||||
extern int enumerate_devices(device_callback_t callback, void *userdata, unsigned int transport);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// Functions and global variables that are only available to C++ code
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct divecomputer;
|
||||
struct dive_table;
|
||||
|
||||
struct device {
|
||||
bool operator<(const device &a) const;
|
||||
void showchanges(const std::string &n) const;
|
||||
|
@ -79,28 +19,48 @@ struct device {
|
|||
uint32_t deviceId; // Always the string hash of the serialNumber
|
||||
};
|
||||
|
||||
using device_table = std::vector<device>;
|
||||
|
||||
extern int create_device_node(device_table &table, const std::string &model, const std::string &serial, const std::string &nickname);
|
||||
std::string get_dc_nickname(const struct divecomputer *dc);
|
||||
extern bool device_used_by_selected_dive(const struct device &dev);
|
||||
|
||||
extern const struct device *get_device_for_dc(const device_table &table, const struct divecomputer *dc);
|
||||
extern int get_or_add_device_for_dc(device_table &table, const struct divecomputer *dc);
|
||||
extern bool device_exists(const device_table &table, const struct device &dev);
|
||||
extern int add_to_device_table(device_table &table, const struct device &dev); // returns index
|
||||
extern int remove_device(device_table &table, const struct device &dev); // returns index or -1 if not found
|
||||
extern void remove_from_device_table(device_table &table, int idx);
|
||||
|
||||
struct fingerprint_record {
|
||||
bool operator<(const fingerprint_record &a) const;
|
||||
uint32_t model; // model and libdivecomputer serial number to
|
||||
uint32_t serial; // look up the fingerprint
|
||||
unsigned char *raw_data; // fingerprint data as provided by libdivecomputer
|
||||
std::unique_ptr<unsigned char[]> raw_data; // fingerprint data as provided by libdivecomputer
|
||||
unsigned int fsize; // size of raw fingerprint data
|
||||
unsigned int fdeviceid; // corresponding deviceid
|
||||
unsigned int fdiveid; // corresponding diveid
|
||||
std::string get_data() const; // As hex-string
|
||||
};
|
||||
|
||||
struct device_table {
|
||||
// Keep the dive computers in a vector sorted by (model, serial)
|
||||
std::vector<device> devices;
|
||||
};
|
||||
using fingerprint_table = std::vector<fingerprint_record>;
|
||||
|
||||
struct fingerprint_table {
|
||||
// Keep the fingerprint records in a vector sorted by (model, serial) - these are uint32_t here
|
||||
std::vector<fingerprint_record> fingerprints;
|
||||
};
|
||||
// global device table
|
||||
extern fingerprint_table fingerprints;
|
||||
|
||||
std::string fp_get_data(struct fingerprint_table *table, unsigned int i);
|
||||
// create fingerprint entry - raw data remains owned by caller
|
||||
extern void create_fingerprint_node(fingerprint_table &table, uint32_t model, uint32_t serial,
|
||||
const unsigned char *raw_data, unsigned int fsize, uint32_t fdeviceid, uint32_t fdiveid);
|
||||
extern void create_fingerprint_node_from_hex(fingerprint_table &table, uint32_t model, uint32_t serial,
|
||||
const std::string &hex_data, uint32_t fdeviceid, uint32_t fdiveid);
|
||||
// look up the fingerprint for model/serial - returns the number of bytes in the fingerprint; memory owned by the table
|
||||
extern std::pair <int, const unsigned char *> get_fingerprint_data(const fingerprint_table &table, uint32_t model, uint32_t serial);
|
||||
|
||||
extern int is_default_dive_computer_device(const char *);
|
||||
|
||||
typedef void (*device_callback_t)(const char *name, void *userdata);
|
||||
|
||||
extern int enumerate_devices(device_callback_t callback, void *userdata, unsigned int transport);
|
||||
|
||||
#endif
|
||||
|
||||
#endif // DEVICE_H
|
||||
|
|
2855
core/dive.cpp
300
core/dive.h
|
@ -7,14 +7,14 @@
|
|||
#include "divemode.h"
|
||||
#include "divecomputer.h"
|
||||
#include "equipment.h"
|
||||
#include "picture.h"
|
||||
#include "picture.h" // TODO: remove
|
||||
#include "tag.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern int last_xml_version;
|
||||
|
||||
|
@ -22,44 +22,119 @@ extern const char *divemode_text_ui[];
|
|||
extern const char *divemode_text[];
|
||||
|
||||
struct dive_site;
|
||||
struct dive_site_table;
|
||||
struct dive_table;
|
||||
struct dive_trip;
|
||||
struct full_text_cache;
|
||||
struct event;
|
||||
struct trip_table;
|
||||
|
||||
/* A unique_ptr that will not be copied if the parent class is copied.
|
||||
* This is used to keep a pointer to the fulltext cache and avoid
|
||||
* having it copied when the dive is copied, since the new dive is
|
||||
* not (yet) registered in the fulltext system. Quite hackish.
|
||||
*/
|
||||
template<typename T>
|
||||
struct non_copying_unique_ptr : public std::unique_ptr<T> {
|
||||
using std::unique_ptr<T>::unique_ptr;
|
||||
using std::unique_ptr<T>::operator=;
|
||||
non_copying_unique_ptr(const non_copying_unique_ptr<T> &) { }
|
||||
void operator=(const non_copying_unique_ptr<T> &) { }
|
||||
};
|
||||
|
||||
struct dive {
|
||||
struct dive_trip *divetrip;
|
||||
timestamp_t when;
|
||||
struct dive_site *dive_site;
|
||||
char *notes;
|
||||
char *diveguide, *buddy;
|
||||
struct cylinder_table cylinders;
|
||||
struct weightsystem_table weightsystems;
|
||||
char *suit;
|
||||
int number;
|
||||
int rating;
|
||||
int wavesize, current, visibility, surge, chill; /* 0 - 5 star ratings */
|
||||
int sac, otu, cns, maxcns;
|
||||
struct dive_trip *divetrip = nullptr;
|
||||
timestamp_t when = 0;
|
||||
struct dive_site *dive_site = nullptr;
|
||||
std::string notes;
|
||||
std::string diveguide, buddy;
|
||||
std::string suit;
|
||||
cylinder_table cylinders;
|
||||
weightsystem_table weightsystems;
|
||||
int number = 0;
|
||||
int rating = 0;
|
||||
int wavesize = 0, current = 0, visibility = 0, surge = 0, chill = 0; /* 0 - 5 star ratings */
|
||||
int sac = 0, otu = 0, cns = 0, maxcns = 0;
|
||||
|
||||
/* Calculated based on dive computer data */
|
||||
temperature_t mintemp, maxtemp, watertemp, airtemp;
|
||||
depth_t maxdepth, meandepth;
|
||||
pressure_t surface_pressure;
|
||||
duration_t duration;
|
||||
int salinity; // kg per 10000 l
|
||||
int user_salinity; // water density reflecting a user-specified type
|
||||
int salinity = 0; // kg per 10000 l
|
||||
int user_salinity = 0; // water density reflecting a user-specified type
|
||||
|
||||
struct tag_entry *tag_list;
|
||||
struct divecomputer dc;
|
||||
int id; // unique ID for this dive
|
||||
struct picture_table pictures;
|
||||
unsigned char git_id[20];
|
||||
bool notrip; /* Don't autogroup this dive to a trip */
|
||||
bool selected;
|
||||
bool hidden_by_filter;
|
||||
struct full_text_cache *full_text; /* word cache for full text search */
|
||||
bool invalid;
|
||||
tag_list tags;
|
||||
std::vector<divecomputer> dcs; // Attn: pointers to divecomputers are not stable!
|
||||
int id = 0; // unique ID for this dive
|
||||
picture_table pictures;
|
||||
std::array<unsigned char, 20> git_id = {};
|
||||
bool notrip = false; /* Don't autogroup this dive to a trip */
|
||||
bool selected = false;
|
||||
bool hidden_by_filter = false;
|
||||
non_copying_unique_ptr<full_text_cache> full_text; /* word cache for full text search */
|
||||
bool invalid = false;
|
||||
|
||||
dive();
|
||||
~dive();
|
||||
dive(const dive &);
|
||||
dive(dive &&);
|
||||
dive &operator=(const dive &);
|
||||
|
||||
void invalidate_cache();
|
||||
bool cache_is_valid() const;
|
||||
|
||||
struct divecomputer *get_dc(int nr);
|
||||
const struct divecomputer *get_dc(int nr) const;
|
||||
|
||||
void clear();
|
||||
int number_of_computers() const;
|
||||
void fixup_no_cylinder(); /* to fix cylinders, we need the divelist (to calculate cns) */
|
||||
timestamp_t endtime() const; /* maximum over divecomputers (with samples) */
|
||||
duration_t totaltime() const; /* maximum over divecomputers (with samples) */
|
||||
temperature_t dc_airtemp() const; /* average over divecomputers */
|
||||
temperature_t dc_watertemp() const; /* average over divecomputers */
|
||||
pressure_t get_surface_pressure() const;
|
||||
|
||||
struct get_maximal_gas_result { int o2_p; int he_p; int o2low_p; };
|
||||
get_maximal_gas_result get_maximal_gas() const;
|
||||
|
||||
bool is_planned() const;
|
||||
bool is_logged() const;
|
||||
bool likely_same(const struct dive &b) const;
|
||||
bool is_cylinder_used(int idx) const;
|
||||
bool is_cylinder_prot(int idx) const;
|
||||
int get_cylinder_index(const struct event &ev) const;
|
||||
bool has_gaschange_event(const struct divecomputer *dc, int idx) const;
|
||||
struct gasmix get_gasmix_from_event(const struct event &ev) const;
|
||||
struct gasmix get_gasmix_at_time(const struct divecomputer &dc, duration_t time) const;
|
||||
cylinder_t *get_cylinder(int idx);
|
||||
cylinder_t *get_or_create_cylinder(int idx);
|
||||
const cylinder_t *get_cylinder(int idx) const;
|
||||
weight_t total_weight() const;
|
||||
int get_salinity() const;
|
||||
bool time_during_dive_with_offset(timestamp_t when, timestamp_t offset) const;
|
||||
std::string get_country() const;
|
||||
std::string get_location() const;
|
||||
|
||||
int depth_to_mbar(int depth) const;
|
||||
double depth_to_mbarf(int depth) const;
|
||||
double depth_to_bar(int depth) const;
|
||||
double depth_to_atm(int depth) const;
|
||||
int rel_mbar_to_depth(int mbar) const;
|
||||
int mbar_to_depth(int mbar) const;
|
||||
|
||||
pressure_t calculate_surface_pressure() const;
|
||||
pressure_t un_fixup_surface_pressure() const;
|
||||
depth_t gas_mod(struct gasmix mix, pressure_t po2_limit, int roundto) const;
|
||||
depth_t gas_mnd(struct gasmix mix, depth_t end, int roundto) const;
|
||||
fraction_t best_o2(depth_t depth, bool in_planner) const;
|
||||
fraction_t best_he(depth_t depth, bool o2narcotic, fraction_t fo2) const;
|
||||
|
||||
bool dive_has_gps_location() const;
|
||||
location_t get_gps_location() const;
|
||||
|
||||
/* Don't call directly, use dive_table::merge_dives()! */
|
||||
static std::unique_ptr<dive> create_merged_dive(const struct dive &a, const struct dive &b, int offset, bool prefer_downloaded);
|
||||
};
|
||||
|
||||
/* For the top-level list: an entry is either a dive or a trip */
|
||||
|
@ -68,170 +143,67 @@ struct dive_or_trip {
|
|||
struct dive_trip *trip;
|
||||
};
|
||||
|
||||
extern void invalidate_dive_cache(struct dive *dive);
|
||||
extern bool dive_cache_is_valid(const struct dive *dive);
|
||||
extern void cylinder_renumber(struct dive &dive, int mapping[]);
|
||||
extern int same_gasmix_cylinder(const cylinder_t &cyl, int cylid, const struct dive *dive, bool check_unused);
|
||||
extern bool is_cylinder_use_appropriate(const struct divecomputer &dc, const cylinder_t &cyl, bool allowNonUsable);
|
||||
|
||||
extern int get_cylinder_idx_by_use(const struct dive *dive, enum cylinderuse cylinder_use_type);
|
||||
extern void cylinder_renumber(struct dive *dive, int mapping[]);
|
||||
extern int same_gasmix_cylinder(const cylinder_t *cyl, int cylid, const struct dive *dive, bool check_unused);
|
||||
|
||||
/* when selectively copying dive information, which parts should be copied? */
|
||||
struct dive_components {
|
||||
unsigned int divesite : 1;
|
||||
unsigned int notes : 1;
|
||||
unsigned int diveguide : 1;
|
||||
unsigned int buddy : 1;
|
||||
unsigned int suit : 1;
|
||||
unsigned int rating : 1;
|
||||
unsigned int visibility : 1;
|
||||
unsigned int wavesize : 1;
|
||||
unsigned int current : 1;
|
||||
unsigned int surge : 1;
|
||||
unsigned int chill : 1;
|
||||
unsigned int tags : 1;
|
||||
unsigned int cylinders : 1;
|
||||
unsigned int weights : 1;
|
||||
unsigned int number : 1;
|
||||
unsigned int when : 1;
|
||||
/* Data stored when copying a dive */
|
||||
struct dive_paste_data {
|
||||
std::optional<uint32_t> divesite; // We save the uuid not a pointer, because the
|
||||
// user might copy and then delete the dive site.
|
||||
std::optional<std::string> notes;
|
||||
std::optional<std::string> diveguide;
|
||||
std::optional<std::string> buddy;
|
||||
std::optional<std::string> suit;
|
||||
std::optional<int> rating;
|
||||
std::optional<int> visibility;
|
||||
std::optional<int> wavesize;
|
||||
std::optional<int> current;
|
||||
std::optional<int> surge;
|
||||
std::optional<int> chill;
|
||||
std::optional<tag_list> tags;
|
||||
std::optional<cylinder_table> cylinders;
|
||||
std::optional<weightsystem_table> weights;
|
||||
std::optional<int> number;
|
||||
std::optional<timestamp_t> when;
|
||||
};
|
||||
|
||||
extern bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx);
|
||||
extern int explicit_first_cylinder(const struct dive *dive, const struct divecomputer *dc);
|
||||
|
||||
extern fraction_t best_o2(depth_t depth, const struct dive *dive, bool in_planner);
|
||||
extern fraction_t best_he(depth_t depth, const struct dive *dive, bool o2narcotic, fraction_t fo2);
|
||||
|
||||
extern int get_surface_pressure_in_mbar(const struct dive *dive, bool non_null);
|
||||
extern int depth_to_mbar(int depth, const struct dive *dive);
|
||||
extern double depth_to_mbarf(int depth, const struct dive *dive);
|
||||
extern double depth_to_bar(int depth, const struct dive *dive);
|
||||
extern double depth_to_atm(int depth, const struct dive *dive);
|
||||
extern int rel_mbar_to_depth(int mbar, const struct dive *dive);
|
||||
extern int mbar_to_depth(int mbar, const struct dive *dive);
|
||||
extern depth_t gas_mod(struct gasmix mix, pressure_t po2_limit, const struct dive *dive, int roundto);
|
||||
extern depth_t gas_mnd(struct gasmix mix, depth_t end, const struct dive *dive, int roundto);
|
||||
|
||||
extern struct dive *get_dive(int nr);
|
||||
extern struct dive *get_dive_from_table(int nr, const struct dive_table *dt);
|
||||
extern struct dive_site *get_dive_site_for_dive(const struct dive *dive);
|
||||
extern const char *get_dive_country(const struct dive *dive);
|
||||
extern const char *get_dive_location(const struct dive *dive);
|
||||
extern unsigned int number_of_computers(const struct dive *dive);
|
||||
extern struct divecomputer *get_dive_dc(struct dive *dive, int nr);
|
||||
extern const struct divecomputer *get_dive_dc_const(const struct dive *dive, int nr);
|
||||
extern timestamp_t dive_endtime(const struct dive *dive);
|
||||
|
||||
extern void set_git_prefs(const char *prefs);
|
||||
|
||||
extern struct dive *make_first_dc(const struct dive *d, int dc_number);
|
||||
extern struct dive *clone_delete_divecomputer(const struct dive *d, int dc_number);
|
||||
void split_divecomputer(const struct dive *src, int num, struct dive **out1, struct dive **out2);
|
||||
|
||||
/*
|
||||
* Iterate over each dive, with the first parameter being the index
|
||||
* iterator variable, and the second one being the dive one.
|
||||
*
|
||||
* I don't think anybody really wants the index, and we could make
|
||||
* it local to the for-loop, but that would make us requires C99.
|
||||
*/
|
||||
#define for_each_dive(_i, _x) \
|
||||
for ((_i) = 0; ((_x) = get_dive(_i)) != NULL; (_i)++)
|
||||
|
||||
#define for_each_dc(_dive, _dc) \
|
||||
for (_dc = &_dive->dc; _dc; _dc = _dc->next)
|
||||
|
||||
#define for_each_relevant_dc(_dive, _dc) \
|
||||
for (_dc = &_dive->dc; _dc; _dc = _dc->next) if (!is_logged(_dive) || !is_dc_planner(_dc))
|
||||
|
||||
extern struct dive *get_dive_by_uniq_id(int id);
|
||||
extern int get_idx_by_uniq_id(int id);
|
||||
extern bool dive_site_has_gps_location(const struct dive_site *ds);
|
||||
extern int dive_has_gps_location(const struct dive *dive);
|
||||
extern location_t dive_get_gps_location(const struct dive *d);
|
||||
|
||||
extern bool time_during_dive_with_offset(const struct dive *dive, timestamp_t when, timestamp_t offset);
|
||||
extern std::unique_ptr<dive> clone_make_first_dc(const struct dive &d, int dc_number);
|
||||
|
||||
extern int save_dives(const char *filename);
|
||||
extern int save_dives_logic(const char *filename, bool select_only, bool anonymize);
|
||||
extern int save_dive(FILE *f, struct dive *dive, bool anonymize);
|
||||
extern int save_dive(FILE *f, const struct dive &dive, bool anonymize);
|
||||
extern int export_dives_xslt(const char *filename, bool selected, const int units, const char *export_xslt, bool anonymize);
|
||||
|
||||
extern int save_dive_sites_logic(const char *filename, const struct dive_site *sites[], int nr_sites, bool anonymize);
|
||||
|
||||
struct membuffer;
|
||||
extern void save_one_dive_to_mb(struct membuffer *b, struct dive *dive, bool anonymize);
|
||||
extern void save_one_dive_to_mb(struct membuffer *b, const struct dive &dive, bool anonymize);
|
||||
|
||||
extern void subsurface_console_init(void);
|
||||
extern void subsurface_console_exit(void);
|
||||
extern bool subsurface_user_is_root(void);
|
||||
|
||||
extern struct dive *alloc_dive(void);
|
||||
extern void free_dive(struct dive *);
|
||||
extern void record_dive_to_table(struct dive *dive, struct dive_table *table);
|
||||
extern void clear_dive(struct dive *dive);
|
||||
extern void copy_dive(const struct dive *s, struct dive *d);
|
||||
extern void selective_copy_dive(const struct dive *s, struct dive *d, struct dive_components what, bool clear);
|
||||
extern struct dive *move_dive(struct dive *s);
|
||||
|
||||
extern int legacy_format_o2pressures(const struct dive *dive, const struct divecomputer *dc);
|
||||
|
||||
extern bool dive_less_than(const struct dive *a, const struct dive *b);
|
||||
extern bool dive_less_than(const struct dive &a, const struct dive &b);
|
||||
extern bool dive_less_than_ptr(const struct dive *a, const struct dive *b);
|
||||
extern bool dive_or_trip_less_than(struct dive_or_trip a, struct dive_or_trip b);
|
||||
extern struct dive *fixup_dive(struct dive *dive);
|
||||
extern pressure_t calculate_surface_pressure(const struct dive *dive);
|
||||
extern pressure_t un_fixup_surface_pressure(const struct dive *d);
|
||||
extern int get_dive_salinity(const struct dive *dive);
|
||||
extern int dive_getUniqID();
|
||||
extern int split_dive(const struct dive *dive, struct dive **new1, struct dive **new2);
|
||||
extern int split_dive_at_time(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2);
|
||||
extern struct dive *merge_dives(const struct dive *a, const struct dive *b, int offset, bool prefer_downloaded, struct dive_trip **trip, struct dive_site **site);
|
||||
extern struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded);
|
||||
|
||||
extern void copy_events_until(const struct dive *sd, struct dive *dd, int dcNr, int time);
|
||||
extern void copy_used_cylinders(const struct dive *s, struct dive *d, bool used_only);
|
||||
extern bool is_cylinder_used(const struct dive *dive, int idx);
|
||||
extern bool is_cylinder_prot(const struct dive *dive, int idx);
|
||||
extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx);
|
||||
extern struct event *create_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx);
|
||||
extern void update_event_name(struct dive *d, int dc_number, struct event *event, const char *name);
|
||||
extern struct event create_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx);
|
||||
extern void per_cylinder_mean_depth(const struct dive *dive, struct divecomputer *dc, int *mean, int *duration);
|
||||
extern int get_cylinder_index(const struct dive *dive, const struct event *ev);
|
||||
extern struct gasmix get_gasmix_from_event(const struct dive *, const struct event *ev);
|
||||
extern int nr_cylinders(const struct dive *dive);
|
||||
extern int nr_weightsystems(const struct dive *dive);
|
||||
extern bool cylinder_with_sensor_sample(const struct dive *dive, int cylinder_id);
|
||||
|
||||
/* UI related protopypes */
|
||||
|
||||
extern void invalidate_dive_cache(struct dive *dc);
|
||||
|
||||
extern int total_weight(const struct dive *);
|
||||
|
||||
extern bool is_planned(const struct dive *dive);
|
||||
extern bool is_logged(const struct dive *dive);
|
||||
|
||||
/* Get gasmixes at increasing timestamps.
|
||||
* In "evp", pass a pointer to a "struct event *" which is NULL-initialized on first invocation.
|
||||
* On subsequent calls, pass the same "evp" and the "gasmix" from previous calls.
|
||||
*/
|
||||
extern struct gasmix get_gasmix(const struct dive *dive, const struct divecomputer *dc, int time, const struct event **evp, struct gasmix gasmix);
|
||||
|
||||
/* Get gasmix at a given time */
|
||||
extern struct gasmix get_gasmix_at_time(const struct dive *dive, const struct divecomputer *dc, duration_t time);
|
||||
|
||||
extern void update_setpoint_events(const struct dive *dive, struct divecomputer *dc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
/* Make pointers to dive and dive_trip "Qt metatypes" so that they can be passed through
|
||||
* QVariants and through QML.
|
||||
*/
|
||||
#include <QObject>
|
||||
#include <string>
|
||||
Q_DECLARE_METATYPE(struct dive *);
|
||||
|
||||
extern std::string existing_filename;
|
||||
|
||||
#endif
|
||||
|
||||
#endif // DIVE_H
|
||||
|
|
|
@ -1,570 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include "divecomputer.h"
|
||||
#include "event.h"
|
||||
#include "extradata.h"
|
||||
#include "pref.h"
|
||||
#include "sample.h"
|
||||
#include "structured_list.h"
|
||||
#include "subsurface-string.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* Good fake dive profiles are hard.
|
||||
*
|
||||
* "depthtime" is the integral of the dive depth over
|
||||
* time ("area" of the dive profile). We want that
|
||||
* area to match the average depth (avg_d*max_t).
|
||||
*
|
||||
* To do that, we generate a 6-point profile:
|
||||
*
|
||||
* (0, 0)
|
||||
* (t1, max_d)
|
||||
* (t2, max_d)
|
||||
* (t3, d)
|
||||
* (t4, d)
|
||||
* (max_t, 0)
|
||||
*
|
||||
* with the same ascent/descent rates between the
|
||||
* different depths.
|
||||
*
|
||||
* NOTE: avg_d, max_d and max_t are given constants.
|
||||
* The rest we can/should play around with to get a
|
||||
* good-looking profile.
|
||||
*
|
||||
* That six-point profile gives a total area of:
|
||||
*
|
||||
* (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3)
|
||||
*
|
||||
* And the "same ascent/descent rates" requirement
|
||||
* gives us (time per depth must be same):
|
||||
*
|
||||
* t1 / max_d = (t3-t2) / (max_d-d)
|
||||
* t1 / max_d = (max_t-t4) / d
|
||||
*
|
||||
* We also obviously require:
|
||||
*
|
||||
* 0 <= t1 <= t2 <= t3 <= t4 <= max_t
|
||||
*
|
||||
* Let us call 'd_frac = d / max_d', and we get:
|
||||
*
|
||||
* Total area must match average depth-time:
|
||||
*
|
||||
* (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) = avg_d*max_t
|
||||
* max_d*(max_t-t1-(1-d_frac)*(t4-t3)) = avg_d*max_t
|
||||
* max_t-t1-(1-d_frac)*(t4-t3) = avg_d*max_t/max_d
|
||||
* t1+(1-d_frac)*(t4-t3) = max_t*(1-avg_d/max_d)
|
||||
*
|
||||
* and descent slope must match ascent slopes:
|
||||
*
|
||||
* t1 / max_d = (t3-t2) / (max_d*(1-d_frac))
|
||||
* t1 = (t3-t2)/(1-d_frac)
|
||||
*
|
||||
* and
|
||||
*
|
||||
* t1 / max_d = (max_t-t4) / (max_d*d_frac)
|
||||
* t1 = (max_t-t4)/d_frac
|
||||
*
|
||||
* In general, we have more free variables than we have constraints,
|
||||
* but we can aim for certain basics, like a good ascent slope.
|
||||
*/
|
||||
static int fill_samples(struct sample *s, int max_d, int avg_d, int max_t, double slope, double d_frac)
|
||||
{
|
||||
double t_frac = max_t * (1 - avg_d / (double)max_d);
|
||||
int t1 = lrint(max_d / slope);
|
||||
int t4 = lrint(max_t - t1 * d_frac);
|
||||
int t3 = lrint(t4 - (t_frac - t1) / (1 - d_frac));
|
||||
int t2 = lrint(t3 - t1 * (1 - d_frac));
|
||||
|
||||
if (t1 < 0 || t1 > t2 || t2 > t3 || t3 > t4 || t4 > max_t)
|
||||
return 0;
|
||||
|
||||
s[1].time.seconds = t1;
|
||||
s[1].depth.mm = max_d;
|
||||
s[2].time.seconds = t2;
|
||||
s[2].depth.mm = max_d;
|
||||
s[3].time.seconds = t3;
|
||||
s[3].depth.mm = lrint(max_d * d_frac);
|
||||
s[4].time.seconds = t4;
|
||||
s[4].depth.mm = lrint(max_d * d_frac);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* we have no average depth; instead of making up a random average depth
|
||||
* we should assume either a PADI rectangular profile (for short and/or
|
||||
* shallow dives) or more reasonably a six point profile with a 3 minute
|
||||
* safety stop at 5m */
|
||||
static void fill_samples_no_avg(struct sample *s, int max_d, int max_t, double slope)
|
||||
{
|
||||
// shallow or short dives are just trapecoids based on the given slope
|
||||
if (max_d < 10000 || max_t < 600) {
|
||||
s[1].time.seconds = lrint(max_d / slope);
|
||||
s[1].depth.mm = max_d;
|
||||
s[2].time.seconds = max_t - lrint(max_d / slope);
|
||||
s[2].depth.mm = max_d;
|
||||
} else {
|
||||
s[1].time.seconds = lrint(max_d / slope);
|
||||
s[1].depth.mm = max_d;
|
||||
s[2].time.seconds = max_t - lrint(max_d / slope) - 180;
|
||||
s[2].depth.mm = max_d;
|
||||
s[3].time.seconds = max_t - lrint(5000 / slope) - 180;
|
||||
s[3].depth.mm = 5000;
|
||||
s[4].time.seconds = max_t - lrint(5000 / slope);
|
||||
s[4].depth.mm = 5000;
|
||||
}
|
||||
}
|
||||
|
||||
void fake_dc(struct divecomputer *dc)
|
||||
{
|
||||
alloc_samples(dc, 6);
|
||||
struct sample *fake = dc->sample;
|
||||
int i;
|
||||
|
||||
dc->samples = 6;
|
||||
|
||||
/* The dive has no samples, so create a few fake ones */
|
||||
int max_t = dc->duration.seconds;
|
||||
int max_d = dc->maxdepth.mm;
|
||||
int avg_d = dc->meandepth.mm;
|
||||
|
||||
memset(fake, 0, 6 * sizeof(struct sample));
|
||||
fake[5].time.seconds = max_t;
|
||||
for (i = 0; i < 6; i++) {
|
||||
fake[i].bearing.degrees = -1;
|
||||
fake[i].ndl.seconds = -1;
|
||||
}
|
||||
if (!max_t || !max_d) {
|
||||
dc->samples = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set last manually entered time to the total dive length */
|
||||
dc->last_manual_time = dc->duration;
|
||||
|
||||
/*
|
||||
* We want to fake the profile so that the average
|
||||
* depth ends up correct. However, in the absence of
|
||||
* a reasonable average, let's just make something
|
||||
* up. Note that 'avg_d == max_d' is _not_ a reasonable
|
||||
* average.
|
||||
* We explicitly treat avg_d == 0 differently */
|
||||
if (avg_d == 0) {
|
||||
/* we try for a sane slope, but bow to the insanity of
|
||||
* the user supplied data */
|
||||
fill_samples_no_avg(fake, max_d, max_t, MAX(2.0 * max_d / max_t, (double)prefs.ascratelast6m));
|
||||
if (fake[3].time.seconds == 0) { // just a 4 point profile
|
||||
dc->samples = 4;
|
||||
fake[3].time.seconds = max_t;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (avg_d < max_d / 10 || avg_d >= max_d) {
|
||||
avg_d = (max_d + 10000) / 3;
|
||||
if (avg_d > max_d)
|
||||
avg_d = max_d * 2 / 3;
|
||||
}
|
||||
if (!avg_d)
|
||||
avg_d = 1;
|
||||
|
||||
/*
|
||||
* Ok, first we try a basic profile with a specific ascent
|
||||
* rate (5 meters per minute) and d_frac (1/3).
|
||||
*/
|
||||
if (fill_samples(fake, max_d, avg_d, max_t, (double)prefs.ascratelast6m, 0.33))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Ok, assume that didn't work because we cannot make the
|
||||
* average come out right because it was a quick deep dive
|
||||
* followed by a much shallower region
|
||||
*/
|
||||
if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Uhhuh. That didn't work. We'd need to find a good combination that
|
||||
* satisfies our constraints. Currently, we don't, we just give insane
|
||||
* slopes.
|
||||
*/
|
||||
if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01))
|
||||
return;
|
||||
|
||||
/* Even that didn't work? Give up, there's something wrong */
|
||||
}
|
||||
|
||||
/* Find the divemode at time 'time' (in seconds) into the dive. Sequentially step through the divemode-change events,
|
||||
* saving the dive mode for each event. When the events occur AFTER 'time' seconds, the last stored divemode
|
||||
* is returned. This function is self-tracking, relying on setting the event pointer 'evp' so that, in each iteration
|
||||
* that calls this function, the search does not have to begin at the first event of the dive */
|
||||
enum divemode_t get_current_divemode(const struct divecomputer *dc, int time, const struct event **evp, enum divemode_t *divemode)
|
||||
{
|
||||
const struct event *ev = *evp;
|
||||
if (dc) {
|
||||
if (*divemode == UNDEF_COMP_TYPE) {
|
||||
*divemode = dc->divemode;
|
||||
ev = get_next_event(dc->events, "modechange");
|
||||
}
|
||||
} else {
|
||||
ev = NULL;
|
||||
}
|
||||
while (ev && ev->time.seconds < time) {
|
||||
*divemode = (enum divemode_t) ev->value;
|
||||
ev = get_next_event(ev->next, "modechange");
|
||||
}
|
||||
*evp = ev;
|
||||
return *divemode;
|
||||
}
|
||||
|
||||
|
||||
/* helper function to make it easier to work with our structures
|
||||
* we don't interpolate here, just use the value from the last sample up to that time */
|
||||
int get_depth_at_time(const struct divecomputer *dc, unsigned int time)
|
||||
{
|
||||
int depth = 0;
|
||||
if (dc && dc->sample)
|
||||
for (int i = 0; i < dc->samples; i++) {
|
||||
if (dc->sample[i].time.seconds > time)
|
||||
break;
|
||||
depth = dc->sample[i].depth.mm;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
|
||||
/* The first divecomputer is embedded in the dive structure. Free its data but not
|
||||
* the structure itself. For all remainding dcs in the list, free data *and* structures. */
|
||||
void free_dive_dcs(struct divecomputer *dc)
|
||||
{
|
||||
free_dc_contents(dc);
|
||||
STRUCTURED_LIST_FREE(struct divecomputer, dc->next, free_dc);
|
||||
}
|
||||
|
||||
/* make room for num samples; if not enough space is available, the sample
|
||||
* array is reallocated and the existing samples are copied. */
|
||||
void alloc_samples(struct divecomputer *dc, int num)
|
||||
{
|
||||
if (num > dc->alloc_samples) {
|
||||
dc->alloc_samples = (num * 3) / 2 + 10;
|
||||
dc->sample = realloc(dc->sample, dc->alloc_samples * sizeof(struct sample));
|
||||
if (!dc->sample)
|
||||
dc->samples = dc->alloc_samples = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void free_samples(struct divecomputer *dc)
|
||||
{
|
||||
if (dc) {
|
||||
free(dc->sample);
|
||||
dc->sample = 0;
|
||||
dc->samples = 0;
|
||||
dc->alloc_samples = 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct sample *prepare_sample(struct divecomputer *dc)
|
||||
{
|
||||
if (dc) {
|
||||
int nr = dc->samples;
|
||||
struct sample *sample;
|
||||
alloc_samples(dc, nr + 1);
|
||||
if (!dc->sample)
|
||||
return NULL;
|
||||
sample = dc->sample + nr;
|
||||
memset(sample, 0, sizeof(*sample));
|
||||
|
||||
// Copy the sensor numbers - but not the pressure values
|
||||
// from the previous sample if any.
|
||||
if (nr) {
|
||||
for (int idx = 0; idx < MAX_SENSORS; idx++)
|
||||
sample->sensor[idx] = sample[-1].sensor[idx];
|
||||
}
|
||||
// Init some values with -1
|
||||
sample->bearing.degrees = -1;
|
||||
sample->ndl.seconds = -1;
|
||||
|
||||
return sample;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void finish_sample(struct divecomputer *dc)
|
||||
{
|
||||
dc->samples++;
|
||||
}
|
||||
|
||||
struct sample *add_sample(const struct sample *sample, int time, struct divecomputer *dc)
|
||||
{
|
||||
struct sample *p = prepare_sample(dc);
|
||||
|
||||
if (p) {
|
||||
*p = *sample;
|
||||
p->time.seconds = time;
|
||||
finish_sample(dc);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate how long we were actually under water, and the average
|
||||
* depth while under water.
|
||||
*
|
||||
* This ignores any surface time in the middle of the dive.
|
||||
*/
|
||||
void fixup_dc_duration(struct divecomputer *dc)
|
||||
{
|
||||
int duration, i;
|
||||
int lasttime, lastdepth, depthtime;
|
||||
|
||||
duration = 0;
|
||||
lasttime = 0;
|
||||
lastdepth = 0;
|
||||
depthtime = 0;
|
||||
for (i = 0; i < dc->samples; i++) {
|
||||
struct sample *sample = dc->sample + i;
|
||||
int time = sample->time.seconds;
|
||||
int depth = sample->depth.mm;
|
||||
|
||||
/* We ignore segments at the surface */
|
||||
if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) {
|
||||
duration += time - lasttime;
|
||||
depthtime += (time - lasttime) * (depth + lastdepth) / 2;
|
||||
}
|
||||
lastdepth = depth;
|
||||
lasttime = time;
|
||||
}
|
||||
if (duration) {
|
||||
dc->duration.seconds = duration;
|
||||
dc->meandepth.mm = (depthtime + duration / 2) / duration;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* What do the dive computers say the water temperature is?
|
||||
* (not in the samples, but as dc property for dcs that support that)
|
||||
*/
|
||||
unsigned int dc_watertemp(const struct divecomputer *dc)
|
||||
{
|
||||
int sum = 0, nr = 0;
|
||||
|
||||
do {
|
||||
if (dc->watertemp.mkelvin) {
|
||||
sum += dc->watertemp.mkelvin;
|
||||
nr++;
|
||||
}
|
||||
} while ((dc = dc->next) != NULL);
|
||||
if (!nr)
|
||||
return 0;
|
||||
return (sum + nr / 2) / nr;
|
||||
}
|
||||
|
||||
/*
|
||||
* What do the dive computers say the air temperature is?
|
||||
*/
|
||||
unsigned int dc_airtemp(const struct divecomputer *dc)
|
||||
{
|
||||
int sum = 0, nr = 0;
|
||||
|
||||
do {
|
||||
if (dc->airtemp.mkelvin) {
|
||||
sum += dc->airtemp.mkelvin;
|
||||
nr++;
|
||||
}
|
||||
} while ((dc = dc->next) != NULL);
|
||||
if (!nr)
|
||||
return 0;
|
||||
return (sum + nr / 2) / nr;
|
||||
}
|
||||
|
||||
/* copies all events in this dive computer */
|
||||
void copy_events(const struct divecomputer *s, struct divecomputer *d)
|
||||
{
|
||||
const struct event *ev;
|
||||
struct event **pev;
|
||||
if (!s || !d)
|
||||
return;
|
||||
ev = s->events;
|
||||
pev = &d->events;
|
||||
while (ev != NULL) {
|
||||
struct event *new_ev = clone_event(ev);
|
||||
*pev = new_ev;
|
||||
pev = &new_ev->next;
|
||||
ev = ev->next;
|
||||
}
|
||||
*pev = NULL;
|
||||
}
|
||||
|
||||
void copy_samples(const struct divecomputer *s, struct divecomputer *d)
|
||||
{
|
||||
/* instead of carefully copying them one by one and calling add_sample
|
||||
* over and over again, let's just copy the whole blob */
|
||||
if (!s || !d)
|
||||
return;
|
||||
int nr = s->samples;
|
||||
d->samples = nr;
|
||||
d->alloc_samples = nr;
|
||||
// We expect to be able to read the memory in the other end of the pointer
|
||||
// if its a valid pointer, so don't expect malloc() to return NULL for
|
||||
// zero-sized malloc, do it ourselves.
|
||||
d->sample = NULL;
|
||||
|
||||
if(!nr)
|
||||
return;
|
||||
|
||||
d->sample = malloc(nr * sizeof(struct sample));
|
||||
if (d->sample)
|
||||
memcpy(d->sample, s->sample, nr * sizeof(struct sample));
|
||||
}
|
||||
|
||||
void add_event_to_dc(struct divecomputer *dc, struct event *ev)
|
||||
{
|
||||
struct event **p;
|
||||
|
||||
p = &dc->events;
|
||||
|
||||
/* insert in the sorted list of events */
|
||||
while (*p && (*p)->time.seconds <= ev->time.seconds)
|
||||
p = &(*p)->next;
|
||||
ev->next = *p;
|
||||
*p = ev;
|
||||
}
|
||||
|
||||
struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name)
|
||||
{
|
||||
struct event *ev = create_event(time, type, flags, value, name);
|
||||
|
||||
if (!ev)
|
||||
return NULL;
|
||||
|
||||
add_event_to_dc(dc, ev);
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
/* Substitutes an event in a divecomputer for another. No reordering is performed! */
|
||||
void swap_event(struct divecomputer *dc, struct event *from, struct event *to)
|
||||
{
|
||||
for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) {
|
||||
if (*ep == from) {
|
||||
to->next = from->next;
|
||||
*ep = to;
|
||||
from->next = NULL; // For good measure.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove given event from dive computer. Does *not* free the event. */
|
||||
void remove_event_from_dc(struct divecomputer *dc, struct event *event)
|
||||
{
|
||||
for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) {
|
||||
if (*ep == event) {
|
||||
*ep = event->next;
|
||||
event->next = NULL; // For good measure.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_extra_data(struct divecomputer *dc, const char *key, const char *value)
|
||||
{
|
||||
struct extra_data **ed = &dc->extra_data;
|
||||
|
||||
if (!strcasecmp(key, "Serial")) {
|
||||
dc->deviceid = calculate_string_hash(value);
|
||||
dc->serial = strdup(value);
|
||||
}
|
||||
if (!strcmp(key, "FW Version")) {
|
||||
dc->fw_version = strdup(value);
|
||||
}
|
||||
|
||||
while (*ed)
|
||||
ed = &(*ed)->next;
|
||||
*ed = malloc(sizeof(struct extra_data));
|
||||
if (*ed) {
|
||||
(*ed)->key = strdup(key);
|
||||
(*ed)->value = strdup(value);
|
||||
(*ed)->next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Match two dive computer entries against each other, and
|
||||
* tell if it's the same dive. Return 0 if "don't know",
|
||||
* positive for "same dive" and negative for "definitely
|
||||
* not the same dive"
|
||||
*/
|
||||
int match_one_dc(const struct divecomputer *a, const struct divecomputer *b)
|
||||
{
|
||||
/* Not same model? Don't know if matching.. */
|
||||
if (!a->model || !b->model)
|
||||
return 0;
|
||||
if (strcasecmp(a->model, b->model))
|
||||
return 0;
|
||||
|
||||
/* Different device ID's? Don't know */
|
||||
if (a->deviceid != b->deviceid)
|
||||
return 0;
|
||||
|
||||
/* Do we have dive IDs? */
|
||||
if (!a->diveid || !b->diveid)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If they have different dive ID's on the same
|
||||
* dive computer, that's a definite "same or not"
|
||||
*/
|
||||
return a->diveid == b->diveid && a->when == b->when ? 1 : -1;
|
||||
}
|
||||
|
||||
static void free_extra_data(struct extra_data *ed)
|
||||
{
|
||||
free((void *)ed->key);
|
||||
free((void *)ed->value);
|
||||
}
|
||||
|
||||
void free_dc_contents(struct divecomputer *dc)
|
||||
{
|
||||
free(dc->sample);
|
||||
free((void *)dc->model);
|
||||
free((void *)dc->serial);
|
||||
free((void *)dc->fw_version);
|
||||
free_events(dc->events);
|
||||
STRUCTURED_LIST_FREE(struct extra_data, dc->extra_data, free_extra_data);
|
||||
}
|
||||
|
||||
void free_dc(struct divecomputer *dc)
|
||||
{
|
||||
free_dc_contents(dc);
|
||||
free(dc);
|
||||
}
|
||||
|
||||
static const char *planner_dc_name = "planned dive";
|
||||
|
||||
bool is_dc_planner(const struct divecomputer *dc)
|
||||
{
|
||||
return dc && same_string(dc->model, planner_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)
|
||||
{
|
||||
free((void *)dc->model);
|
||||
dc->model = strdup(manual_dc_name);
|
||||
}
|
400
core/divecomputer.cpp
Normal file
|
@ -0,0 +1,400 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include "divecomputer.h"
|
||||
#include "errorhelper.h"
|
||||
#include "event.h"
|
||||
#include "extradata.h"
|
||||
#include "pref.h"
|
||||
#include "sample.h"
|
||||
#include "subsurface-string.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <tuple>
|
||||
|
||||
divecomputer::divecomputer() = default;
|
||||
divecomputer::~divecomputer() = default;
|
||||
divecomputer::divecomputer(const divecomputer &) = default;
|
||||
divecomputer::divecomputer(divecomputer &&) = default;
|
||||
divecomputer &divecomputer::operator=(const divecomputer &) = default;
|
||||
|
||||
/*
|
||||
* Good fake dive profiles are hard.
|
||||
*
|
||||
* "depthtime" is the integral of the dive depth over
|
||||
* time ("area" of the dive profile). We want that
|
||||
* area to match the average depth (avg_d*max_t).
|
||||
*
|
||||
* To do that, we generate a 6-point profile:
|
||||
*
|
||||
* (0, 0)
|
||||
* (t1, max_d)
|
||||
* (t2, max_d)
|
||||
* (t3, d)
|
||||
* (t4, d)
|
||||
* (max_t, 0)
|
||||
*
|
||||
* with the same ascent/descent rates between the
|
||||
* different depths.
|
||||
*
|
||||
* NOTE: avg_d, max_d and max_t are given constants.
|
||||
* The rest we can/should play around with to get a
|
||||
* good-looking profile.
|
||||
*
|
||||
* That six-point profile gives a total area of:
|
||||
*
|
||||
* (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3)
|
||||
*
|
||||
* And the "same ascent/descent rates" requirement
|
||||
* gives us (time per depth must be same):
|
||||
*
|
||||
* t1 / max_d = (t3-t2) / (max_d-d)
|
||||
* t1 / max_d = (max_t-t4) / d
|
||||
*
|
||||
* We also obviously require:
|
||||
*
|
||||
* 0 <= t1 <= t2 <= t3 <= t4 <= max_t
|
||||
*
|
||||
* Let us call 'd_frac = d / max_d', and we get:
|
||||
*
|
||||
* Total area must match average depth-time:
|
||||
*
|
||||
* (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) = avg_d*max_t
|
||||
* max_d*(max_t-t1-(1-d_frac)*(t4-t3)) = avg_d*max_t
|
||||
* max_t-t1-(1-d_frac)*(t4-t3) = avg_d*max_t/max_d
|
||||
* t1+(1-d_frac)*(t4-t3) = max_t*(1-avg_d/max_d)
|
||||
*
|
||||
* and descent slope must match ascent slopes:
|
||||
*
|
||||
* t1 / max_d = (t3-t2) / (max_d*(1-d_frac))
|
||||
* t1 = (t3-t2)/(1-d_frac)
|
||||
*
|
||||
* and
|
||||
*
|
||||
* t1 / max_d = (max_t-t4) / (max_d*d_frac)
|
||||
* t1 = (max_t-t4)/d_frac
|
||||
*
|
||||
* In general, we have more free variables than we have constraints,
|
||||
* but we can aim for certain basics, like a good ascent slope.
|
||||
*/
|
||||
static int fill_samples(std::vector<sample> &s, int max_d, int avg_d, int max_t, double slope, double d_frac)
|
||||
{
|
||||
double t_frac = max_t * (1 - avg_d / (double)max_d);
|
||||
int t1 = lrint(max_d / slope);
|
||||
int t4 = lrint(max_t - t1 * d_frac);
|
||||
int t3 = lrint(t4 - (t_frac - t1) / (1 - d_frac));
|
||||
int t2 = lrint(t3 - t1 * (1 - d_frac));
|
||||
|
||||
if (t1 < 0 || t1 > t2 || t2 > t3 || t3 > t4 || t4 > max_t)
|
||||
return 0;
|
||||
|
||||
s[1].time.seconds = t1;
|
||||
s[1].depth.mm = max_d;
|
||||
s[2].time.seconds = t2;
|
||||
s[2].depth.mm = max_d;
|
||||
s[3].time.seconds = t3;
|
||||
s[3].depth.mm = lrint(max_d * d_frac);
|
||||
s[4].time.seconds = t4;
|
||||
s[4].depth.mm = lrint(max_d * d_frac);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* we have no average depth; instead of making up a random average depth
|
||||
* we should assume either a PADI rectangular profile (for short and/or
|
||||
* shallow dives) or more reasonably a six point profile with a 3 minute
|
||||
* safety stop at 5m */
|
||||
static void fill_samples_no_avg(std::vector<sample> &s, int max_d, int max_t, double slope)
|
||||
{
|
||||
// shallow or short dives are just trapecoids based on the given slope
|
||||
if (max_d < 10000 || max_t < 600) {
|
||||
s[1].time.seconds = lrint(max_d / slope);
|
||||
s[1].depth.mm = max_d;
|
||||
s[2].time.seconds = max_t - lrint(max_d / slope);
|
||||
s[2].depth.mm = max_d;
|
||||
} else {
|
||||
s[1].time.seconds = lrint(max_d / slope);
|
||||
s[1].depth.mm = max_d;
|
||||
s[2].time.seconds = max_t - lrint(max_d / slope) - 180;
|
||||
s[2].depth.mm = max_d;
|
||||
s[3].time.seconds = max_t - lrint(5000 / slope) - 180;
|
||||
s[3].depth = 5_m;
|
||||
s[4].time.seconds = max_t - lrint(5000 / slope);
|
||||
s[4].depth = 5_m;
|
||||
}
|
||||
}
|
||||
|
||||
void fake_dc(struct divecomputer *dc)
|
||||
{
|
||||
/* The dive has no samples, so create a few fake ones */
|
||||
int max_t = dc->duration.seconds;
|
||||
int max_d = dc->maxdepth.mm;
|
||||
int avg_d = dc->meandepth.mm;
|
||||
|
||||
if (!max_t || !max_d) {
|
||||
dc->samples.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<struct sample> &fake = dc->samples;
|
||||
fake.resize(6);
|
||||
|
||||
fake[5].time.seconds = max_t;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
fake[i].bearing.degrees = -1;
|
||||
fake[i].ndl.seconds = -1;
|
||||
}
|
||||
|
||||
/* Set last manually entered time to the total dive length */
|
||||
dc->last_manual_time = dc->duration;
|
||||
|
||||
/*
|
||||
* We want to fake the profile so that the average
|
||||
* depth ends up correct. However, in the absence of
|
||||
* a reasonable average, let's just make something
|
||||
* up. Note that 'avg_d == max_d' is _not_ a reasonable
|
||||
* average.
|
||||
* We explicitly treat avg_d == 0 differently */
|
||||
if (avg_d == 0) {
|
||||
/* we try for a sane slope, but bow to the insanity of
|
||||
* the user supplied data */
|
||||
fill_samples_no_avg(fake, max_d, max_t, std::max(2.0 * max_d / max_t, (double)prefs.ascratelast6m));
|
||||
if (fake[3].time.seconds == 0) { // just a 4 point profile
|
||||
dc->samples.resize(4);
|
||||
fake[3].time.seconds = max_t;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (avg_d < max_d / 10 || avg_d >= max_d) {
|
||||
avg_d = (max_d + 10000) / 3;
|
||||
if (avg_d > max_d)
|
||||
avg_d = max_d * 2 / 3;
|
||||
}
|
||||
if (!avg_d)
|
||||
avg_d = 1;
|
||||
|
||||
/*
|
||||
* Ok, first we try a basic profile with a specific ascent
|
||||
* rate (5 meters per minute) and d_frac (1/3).
|
||||
*/
|
||||
if (fill_samples(fake, max_d, avg_d, max_t, (double)prefs.ascratelast6m, 0.33))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Ok, assume that didn't work because we cannot make the
|
||||
* average come out right because it was a quick deep dive
|
||||
* followed by a much shallower region
|
||||
*/
|
||||
if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Uhhuh. That didn't work. We'd need to find a good combination that
|
||||
* satisfies our constraints. Currently, we don't, we just give insane
|
||||
* slopes.
|
||||
*/
|
||||
if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01))
|
||||
return;
|
||||
|
||||
/* Even that didn't work? Give up, there's something wrong */
|
||||
}
|
||||
|
||||
divemode_loop::divemode_loop(const struct divecomputer &dc) :
|
||||
last(dc.divemode), loop("modechange", dc)
|
||||
{
|
||||
/* on first invocation, get first event (if any) */
|
||||
ev = loop.next();
|
||||
}
|
||||
|
||||
divemode_t divemode_loop::at(int time)
|
||||
{
|
||||
while (ev && ev->time.seconds <= time) {
|
||||
last = static_cast<divemode_t>(ev->value);
|
||||
ev = loop.next();
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
/* helper function to make it easier to work with our structures
|
||||
* we don't interpolate here, just use the value from the last sample up to that time */
|
||||
int get_depth_at_time(const struct divecomputer *dc, unsigned int time)
|
||||
{
|
||||
int depth = 0;
|
||||
if (dc) {
|
||||
for (const auto &sample: dc->samples) {
|
||||
if (sample.time.seconds > (int)time)
|
||||
break;
|
||||
depth = sample.depth.mm;
|
||||
}
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
struct sample *prepare_sample(struct divecomputer *dc)
|
||||
{
|
||||
if (dc) {
|
||||
dc->samples.emplace_back();
|
||||
auto &sample = dc->samples.back();
|
||||
|
||||
// Copy the sensor numbers - but not the pressure values
|
||||
// from the previous sample if any.
|
||||
if (dc->samples.size() >= 2) {
|
||||
auto &prev = dc->samples[dc->samples.size() - 2];
|
||||
for (int idx = 0; idx < MAX_SENSORS; idx++)
|
||||
sample.sensor[idx] = prev.sensor[idx];
|
||||
}
|
||||
// Init some values with -1
|
||||
sample.bearing.degrees = -1;
|
||||
sample.ndl.seconds = -1;
|
||||
|
||||
return &sample;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void append_sample(const struct sample &sample, struct divecomputer *dc)
|
||||
{
|
||||
dc->samples.push_back(sample);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate how long we were actually under water, and the average
|
||||
* depth while under water.
|
||||
*
|
||||
* This ignores any surface time in the middle of the dive.
|
||||
*/
|
||||
void fixup_dc_duration(struct divecomputer &dc)
|
||||
{
|
||||
int duration = 0;
|
||||
int lasttime = 0, lastdepth = 0, depthtime = 0;
|
||||
|
||||
for (const auto &sample: dc.samples) {
|
||||
int time = sample.time.seconds;
|
||||
int depth = sample.depth.mm;
|
||||
|
||||
/* Do we *have* a depth? */
|
||||
if (depth < 0)
|
||||
continue;
|
||||
|
||||
/* We ignore segments at the surface */
|
||||
if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) {
|
||||
duration += time - lasttime;
|
||||
depthtime += (time - lasttime) * (depth + lastdepth) / 2;
|
||||
}
|
||||
lastdepth = depth;
|
||||
lasttime = time;
|
||||
}
|
||||
if (duration) {
|
||||
dc.duration.seconds = duration;
|
||||
dc.meandepth.mm = (depthtime + duration / 2) / duration;
|
||||
}
|
||||
}
|
||||
|
||||
static bool operator<(const event &ev1, const event &ev2)
|
||||
{
|
||||
return std::tie(ev1.time.seconds, ev1.name) <
|
||||
std::tie(ev2.time.seconds, ev2.name);
|
||||
}
|
||||
|
||||
int add_event_to_dc(struct divecomputer *dc, struct event ev)
|
||||
{
|
||||
// Do a binary search for insertion point
|
||||
auto it = std::lower_bound(dc->events.begin(), dc->events.end(), ev);
|
||||
int idx = it - dc->events.begin();
|
||||
dc->events.insert(it, ev);
|
||||
return idx;
|
||||
}
|
||||
|
||||
struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const std::string &name)
|
||||
{
|
||||
struct event ev(time, type, flags, value, name);
|
||||
int idx = add_event_to_dc(dc, std::move(ev));
|
||||
|
||||
return &dc->events[idx];
|
||||
}
|
||||
|
||||
/* Remove given event from dive computer. Returns the removed event. */
|
||||
struct event remove_event_from_dc(struct divecomputer *dc, int idx)
|
||||
{
|
||||
if (idx < 0 || static_cast<size_t>(idx) > dc->events.size()) {
|
||||
report_info("removing invalid event %d", idx);
|
||||
return event();
|
||||
}
|
||||
event res = std::move(dc->events[idx]);
|
||||
dc->events.erase(dc->events.begin() + idx);
|
||||
return res;
|
||||
}
|
||||
|
||||
struct event *get_event(struct divecomputer *dc, int idx)
|
||||
{
|
||||
if (idx < 0 || static_cast<size_t>(idx) > dc->events.size()) {
|
||||
report_info("accessing invalid event %d", idx);
|
||||
return nullptr;
|
||||
}
|
||||
return &dc->events[idx];
|
||||
}
|
||||
|
||||
void add_extra_data(struct divecomputer *dc, const std::string &key, const std::string &value)
|
||||
{
|
||||
if (key == "Serial") {
|
||||
dc->deviceid = calculate_string_hash(value.c_str());
|
||||
dc->serial = value;
|
||||
}
|
||||
if (key == "FW Version")
|
||||
dc->fw_version = value;
|
||||
|
||||
dc->extra_data.push_back(extra_data { key, value });
|
||||
}
|
||||
|
||||
/*
|
||||
* Match two dive computer entries against each other, and
|
||||
* tell if it's the same dive. Return 0 if "don't know",
|
||||
* positive for "same dive" and negative for "definitely
|
||||
* not the same dive"
|
||||
*/
|
||||
int match_one_dc(const struct divecomputer &a, const struct divecomputer &b)
|
||||
{
|
||||
/* Not same model? Don't know if matching.. */
|
||||
if (a.model.empty() || b.model.empty())
|
||||
return 0;
|
||||
if (strcasecmp(a.model.c_str(), b.model.c_str()))
|
||||
return 0;
|
||||
|
||||
/* Different device ID's? Don't know */
|
||||
if (a.deviceid != b.deviceid)
|
||||
return 0;
|
||||
|
||||
/* Do we have dive IDs? */
|
||||
if (!a.diveid || !b.diveid)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If they have different dive ID's on the same
|
||||
* dive computer, that's a definite "same or not"
|
||||
*/
|
||||
return a.diveid == b.diveid && a.when == b.when ? 1 : -1;
|
||||
}
|
||||
|
||||
static const char *planner_dc_name = "planned dive";
|
||||
|
||||
bool is_dc_planner(const struct divecomputer *dc)
|
||||
{
|
||||
return dc->model == planner_dc_name;
|
||||
}
|
||||
|
||||
void make_planner_dc(struct divecomputer *dc)
|
||||
{
|
||||
dc->model = planner_dc_name;
|
||||
}
|
||||
|
||||
const char *manual_dc_name = "manually added dive";
|
||||
|
||||
bool is_dc_manually_added_dive(const struct divecomputer *dc)
|
||||
{
|
||||
return dc->model == manual_dc_name;
|
||||
}
|
||||
|
||||
void make_manually_added_dive_dc(struct divecomputer *dc)
|
||||
{
|
||||
dc->model = manual_dc_name;
|
||||
}
|
|
@ -4,12 +4,11 @@
|
|||
|
||||
#include "divemode.h"
|
||||
#include "units.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct extra_data;
|
||||
struct event;
|
||||
struct sample;
|
||||
|
||||
/* Is this header the correct place? */
|
||||
|
@ -29,44 +28,40 @@ struct sample;
|
|||
* A deviceid or diveid of zero is assumed to be "no ID".
|
||||
*/
|
||||
struct divecomputer {
|
||||
timestamp_t when;
|
||||
timestamp_t when = 0;
|
||||
duration_t duration, surfacetime, last_manual_time;
|
||||
depth_t maxdepth, meandepth;
|
||||
temperature_t airtemp, watertemp;
|
||||
pressure_t surface_pressure;
|
||||
enum divemode_t divemode; // dive computer type: OC(default) or CCR
|
||||
uint8_t no_o2sensors; // rebreathers: number of O2 sensors used
|
||||
int salinity; // kg per 10000 l
|
||||
const char *model, *serial, *fw_version;
|
||||
uint32_t deviceid, diveid;
|
||||
int samples, alloc_samples;
|
||||
struct sample *sample;
|
||||
struct event *events;
|
||||
struct extra_data *extra_data;
|
||||
struct divecomputer *next;
|
||||
enum divemode_t divemode = OC; // dive computer type: OC(default) or CCR
|
||||
uint8_t no_o2sensors = 0; // rebreathers: number of O2 sensors used
|
||||
int salinity = 0; // kg per 10000 l
|
||||
std::string model, serial, fw_version;
|
||||
uint32_t deviceid = 0, diveid = 0;
|
||||
// Note: ve store samples, events and extra_data in std::vector<>s.
|
||||
// This means that pointers to these items are *not* stable.
|
||||
std::vector<struct sample> samples;
|
||||
std::vector<struct event> events;
|
||||
std::vector<struct extra_data> extra_data;
|
||||
|
||||
divecomputer();
|
||||
~divecomputer();
|
||||
divecomputer(const divecomputer &);
|
||||
divecomputer(divecomputer &&);
|
||||
divecomputer &operator=(const divecomputer &);
|
||||
};
|
||||
|
||||
extern void fake_dc(struct divecomputer *dc);
|
||||
extern void free_dc(struct divecomputer *dc);
|
||||
extern void free_dc_contents(struct divecomputer *dc);
|
||||
extern enum divemode_t get_current_divemode(const struct divecomputer *dc, int time, const struct event **evp, enum divemode_t *divemode);
|
||||
extern int get_depth_at_time(const struct divecomputer *dc, unsigned int time);
|
||||
extern void free_dive_dcs(struct divecomputer *dc);
|
||||
extern void alloc_samples(struct divecomputer *dc, int num);
|
||||
extern void free_samples(struct divecomputer *dc);
|
||||
extern struct sample *prepare_sample(struct divecomputer *dc);
|
||||
extern void finish_sample(struct divecomputer *dc);
|
||||
extern struct sample *add_sample(const struct sample *sample, int time, struct divecomputer *dc);
|
||||
extern void fixup_dc_duration(struct divecomputer *dc);
|
||||
extern unsigned int dc_airtemp(const struct divecomputer *dc);
|
||||
extern unsigned int dc_watertemp(const struct divecomputer *dc);
|
||||
extern void copy_events(const struct divecomputer *s, struct divecomputer *d);
|
||||
extern void swap_event(struct divecomputer *dc, struct event *from, struct event *to);
|
||||
extern void copy_samples(const struct divecomputer *s, struct divecomputer *d);
|
||||
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 void append_sample(const struct sample &sample, struct divecomputer *dc);
|
||||
extern void fixup_dc_duration(struct divecomputer &dc);
|
||||
extern int add_event_to_dc(struct divecomputer *dc, struct event ev); // event structure is consumed, returns index of inserted event
|
||||
extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const std::string &name);
|
||||
extern struct event remove_event_from_dc(struct divecomputer *dc, int idx);
|
||||
struct event *get_event(struct divecomputer *dc, int idx);
|
||||
extern void add_extra_data(struct divecomputer *dc, const std::string &key, const std::string &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);
|
||||
|
@ -75,10 +70,6 @@ extern bool is_dc_manually_added_dive(const struct divecomputer *dc);
|
|||
extern void make_manually_added_dive_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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
extern int match_one_dc(const struct divecomputer &a, const struct divecomputer &b);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "divelog.h"
|
||||
#include "gettextfromc.h"
|
||||
#include "qthelper.h"
|
||||
#include "range.h"
|
||||
#include "selection.h"
|
||||
#include "subsurface-qt/divelistnotifier.h"
|
||||
#if !defined(SUBSURFACE_MOBILE) && !defined(SUBSURFACE_DOWNLOADER)
|
||||
|
@ -61,7 +62,7 @@ ShownChange DiveFilter::update(const QVector<dive *> &dives) const
|
|||
std::vector<dive *> removeFromSelection;
|
||||
for (dive *d: dives) {
|
||||
// There are three modes: divesite, fulltext, normal
|
||||
bool newStatus = doDS ? dive_sites.contains(d->dive_site) :
|
||||
bool newStatus = doDS ? range_contains(dive_sites, d->dive_site) :
|
||||
doFullText ? fulltext_dive_matches(d, filterData.fullText, filterData.fulltextStringMode) && showDive(d) :
|
||||
showDive(d);
|
||||
updateDiveStatus(d, newStatus, res, removeFromSelection);
|
||||
|
@ -73,10 +74,8 @@ ShownChange DiveFilter::update(const QVector<dive *> &dives) const
|
|||
|
||||
void DiveFilter::reset()
|
||||
{
|
||||
int i;
|
||||
dive *d;
|
||||
shown_dives = divelog.dives->nr;
|
||||
for_each_dive(i, d)
|
||||
shown_dives = static_cast<int>(divelog.dives.size());
|
||||
for (auto &d: divelog.dives)
|
||||
d->hidden_by_filter = false;
|
||||
updateAll();
|
||||
}
|
||||
|
@ -84,26 +83,24 @@ void DiveFilter::reset()
|
|||
ShownChange DiveFilter::updateAll() const
|
||||
{
|
||||
ShownChange res;
|
||||
int i;
|
||||
dive *d;
|
||||
std::vector<dive *> selection = getDiveSelection();
|
||||
std::vector<dive *> removeFromSelection;
|
||||
// There are three modes: divesite, fulltext, normal
|
||||
if (diveSiteMode()) {
|
||||
for_each_dive(i, d) {
|
||||
bool newStatus = dive_sites.contains(d->dive_site);
|
||||
updateDiveStatus(d, newStatus, res, removeFromSelection);
|
||||
for (auto &d: divelog.dives) {
|
||||
bool newStatus = range_contains(dive_sites, d->dive_site);
|
||||
updateDiveStatus(d.get(), newStatus, res, removeFromSelection);
|
||||
}
|
||||
} else if (filterData.fullText.doit()) {
|
||||
FullTextResult ft = fulltext_find_dives(filterData.fullText, filterData.fulltextStringMode);
|
||||
for_each_dive(i, d) {
|
||||
bool newStatus = ft.dive_matches(d) && showDive(d);
|
||||
updateDiveStatus(d, newStatus, res, removeFromSelection);
|
||||
for (auto &d: divelog.dives) {
|
||||
bool newStatus = ft.dive_matches(d.get()) && showDive(d.get());
|
||||
updateDiveStatus(d.get(), newStatus, res, removeFromSelection);
|
||||
}
|
||||
} else {
|
||||
for_each_dive(i, d) {
|
||||
bool newStatus = showDive(d);
|
||||
updateDiveStatus(d, newStatus, res, removeFromSelection);
|
||||
for (auto &d: divelog.dives) {
|
||||
bool newStatus = showDive(d.get());
|
||||
updateDiveStatus(d.get(), newStatus, res, removeFromSelection);
|
||||
}
|
||||
}
|
||||
updateSelection(selection, std::vector<dive *>(), removeFromSelection);
|
||||
|
@ -142,7 +139,7 @@ bool DiveFilter::showDive(const struct dive *d) const
|
|||
}
|
||||
|
||||
#if !defined(SUBSURFACE_MOBILE) && !defined(SUBSURFACE_DOWNLOADER)
|
||||
void DiveFilter::startFilterDiveSites(QVector<dive_site *> ds)
|
||||
void DiveFilter::startFilterDiveSites(std::vector<dive_site *> ds)
|
||||
{
|
||||
if (++diveSiteRefCount > 1) {
|
||||
setFilterDiveSite(std::move(ds));
|
||||
|
@ -169,7 +166,7 @@ void DiveFilter::stopFilterDiveSites()
|
|||
#endif
|
||||
}
|
||||
|
||||
void DiveFilter::setFilterDiveSite(QVector<dive_site *> ds)
|
||||
void DiveFilter::setFilterDiveSite(std::vector<dive_site *> ds)
|
||||
{
|
||||
// If the filter didn't change, return early to avoid a full
|
||||
// map reload. For a well-defined comparison, sort the vector first.
|
||||
|
@ -185,7 +182,7 @@ void DiveFilter::setFilterDiveSite(QVector<dive_site *> ds)
|
|||
MainWindow::instance()->diveList->expandAll();
|
||||
}
|
||||
|
||||
const QVector<dive_site *> &DiveFilter::filteredDiveSites() const
|
||||
const std::vector<dive_site *> &DiveFilter::filteredDiveSites() const
|
||||
{
|
||||
return dive_sites;
|
||||
}
|
||||
|
@ -203,7 +200,7 @@ bool DiveFilter::diveSiteMode() const
|
|||
|
||||
QString DiveFilter::shownText() const
|
||||
{
|
||||
int num = divelog.dives->nr;
|
||||
size_t num = divelog.dives.size();
|
||||
if (diveSiteMode() || filterData.validFilter())
|
||||
return gettextFromC::tr("%L1/%L2 shown").arg(shown_dives).arg(num);
|
||||
else
|
||||
|
@ -229,11 +226,9 @@ std::vector<dive *> DiveFilter::visibleDives() const
|
|||
std::vector<dive *> res;
|
||||
res.reserve(shown_dives);
|
||||
|
||||
int i;
|
||||
dive *d;
|
||||
for_each_dive(i, d) {
|
||||
for (auto &d: divelog.dives) {
|
||||
if (!d->hidden_by_filter)
|
||||
res.push_back(d);
|
||||
res.push_back(d.get());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -45,9 +45,9 @@ public:
|
|||
bool diveSiteMode() const; // returns true if we're filtering on dive site (on mobile always returns false)
|
||||
std::vector<dive *> visibleDives() const;
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
const QVector<dive_site *> &filteredDiveSites() const;
|
||||
void startFilterDiveSites(QVector<dive_site *> ds);
|
||||
void setFilterDiveSite(QVector<dive_site *> ds);
|
||||
const std::vector<dive_site *> &filteredDiveSites() const;
|
||||
void startFilterDiveSites(std::vector<dive_site *> ds);
|
||||
void setFilterDiveSite(std::vector<dive_site *> ds);
|
||||
void stopFilterDiveSites();
|
||||
#endif
|
||||
void setFilter(const FilterData &data);
|
||||
|
@ -62,7 +62,7 @@ private:
|
|||
void updateDiveStatus(dive *d, bool newStatus, ShownChange &change,
|
||||
std::vector<dive *> &removeFromSelection) const;
|
||||
|
||||
QVector<dive_site *> dive_sites;
|
||||
std::vector<dive_site *> dive_sites;
|
||||
FilterData filterData;
|
||||
mutable int shown_dives;
|
||||
|
||||
|
|
1417
core/divelist.c
1255
core/divelist.cpp
Normal file
|
@ -2,69 +2,55 @@
|
|||
#ifndef DIVELIST_H
|
||||
#define DIVELIST_H
|
||||
|
||||
#include "triptable.h"
|
||||
#include "divesitetable.h"
|
||||
#include "units.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
struct dive;
|
||||
struct divelog;
|
||||
struct trip_table;
|
||||
struct dive_site_table;
|
||||
struct device_table;
|
||||
struct device;
|
||||
struct deco_state;
|
||||
|
||||
struct dive_table {
|
||||
int nr, allocated;
|
||||
struct dive **dives;
|
||||
int comp_dives(const struct dive &a, const struct dive &b);
|
||||
int comp_dives_ptr(const struct dive *a, const struct dive *b);
|
||||
|
||||
struct merge_result {
|
||||
std::unique_ptr<struct dive> dive;
|
||||
std::optional<location_t> set_location; // change location of dive site
|
||||
};
|
||||
static const struct dive_table empty_dive_table = { 0, 0, (struct dive **)0 };
|
||||
|
||||
/* this is used for both git and xml format */
|
||||
#define DATAFORMAT_VERSION 3
|
||||
struct dive_table : public sorted_owning_table<dive, &comp_dives> {
|
||||
dive *get_by_uniq_id(int id) const;
|
||||
void record_dive(std::unique_ptr<dive> d); // call fixup_dive() before adding dive to table.
|
||||
struct dive *register_dive(std::unique_ptr<dive> d);
|
||||
std::unique_ptr<dive> unregister_dive(int idx);
|
||||
std::unique_ptr<dive> default_dive(); // generate a sensible looking defaultdive 1h from now.
|
||||
|
||||
extern void sort_dive_table(struct dive_table *table);
|
||||
extern void update_cylinder_related_info(struct dive *);
|
||||
extern int init_decompression(struct deco_state *ds, const struct dive *dive, bool in_planner);
|
||||
// Some of these functions act on dives, but need data from adjacent dives,
|
||||
// notably to calculate CNS, surface interval, etc. Therefore, they are called
|
||||
// on the dive_table and not on the dive.
|
||||
void fixup_dive(struct dive &dive) const;
|
||||
void force_fixup_dive(struct dive &d) const;
|
||||
int init_decompression(struct deco_state *ds, const struct dive *dive, bool in_planner) const;
|
||||
void update_cylinder_related_info(struct dive &dive) const;
|
||||
int get_dive_nr_at_idx(int idx) const;
|
||||
timestamp_t get_surface_interval(timestamp_t when) const;
|
||||
struct dive *find_next_visible_dive(timestamp_t when);
|
||||
std::array<std::unique_ptr<dive>, 2> split_divecomputer(const struct dive &src, int num) const;
|
||||
std::array<std::unique_ptr<dive>, 2> split_dive(const struct dive &dive) const;
|
||||
std::array<std::unique_ptr<dive>, 2> split_dive_at_time(const struct dive &dive, duration_t time) const;
|
||||
merge_result merge_dives(const std::vector<dive *> &dives) const;
|
||||
std::unique_ptr<dive> try_to_merge(const struct dive &a, const struct dive &b, bool prefer_downloaded) const;
|
||||
bool has_dive(unsigned int deviceid, unsigned int diveid) const;
|
||||
std::unique_ptr<dive> clone_delete_divecomputer(const struct dive &d, int dc_number);
|
||||
private:
|
||||
int calculate_cns(struct dive &dive) const; // Note: writes into dive->cns
|
||||
std::array<std::unique_ptr<dive>, 2> split_dive_at(const struct dive &dive, int a, int b) const;
|
||||
std::unique_ptr<dive> merge_two_dives(const struct dive &a_in, const struct dive &b_in, int offset, bool prefer_downloaded) const;
|
||||
};
|
||||
|
||||
/* divelist core logic functions */
|
||||
extern void process_loaded_dives();
|
||||
/* flags for process_imported_dives() */
|
||||
#define IMPORT_PREFER_IMPORTED (1 << 0)
|
||||
#define IMPORT_IS_DOWNLOADED (1 << 1)
|
||||
#define IMPORT_MERGE_ALL_TRIPS (1 << 2)
|
||||
#define IMPORT_ADD_TO_NEW_TRIP (1 << 3)
|
||||
extern void add_imported_dives(struct divelog *log, int flags);
|
||||
extern void process_imported_dives(struct divelog *import_log, int flags,
|
||||
struct dive_table *dives_to_add, struct dive_table *dives_to_remove,
|
||||
struct trip_table *trips_to_add, struct dive_site_table *sites_to_add,
|
||||
struct device_table *devices_to_add);
|
||||
|
||||
extern int dive_table_get_insertion_index(struct dive_table *table, struct dive *dive);
|
||||
extern void add_to_dive_table(struct dive_table *table, int idx, struct dive *dive);
|
||||
extern void insert_dive(struct dive_table *table, struct dive *d);
|
||||
extern void get_dive_gas(const struct dive *dive, int *o2_p, int *he_p, int *o2low_p);
|
||||
extern int get_divenr(const struct dive *dive);
|
||||
extern int remove_dive(const struct dive *dive, struct dive_table *table);
|
||||
extern int get_dive_nr_at_idx(int idx);
|
||||
extern timestamp_t get_surface_interval(timestamp_t when);
|
||||
extern void delete_dive_from_table(struct dive_table *table, int idx);
|
||||
extern struct dive *find_next_visible_dive(timestamp_t when);
|
||||
|
||||
extern int comp_dives(const struct dive *a, const struct dive *b);
|
||||
|
||||
int get_min_datafile_version();
|
||||
void reset_min_datafile_version();
|
||||
void report_datafile_version(int version);
|
||||
void clear_dive_file_data();
|
||||
void clear_dive_table(struct dive_table *table);
|
||||
void move_dive_table(struct dive_table *src, struct dive_table *dst);
|
||||
struct dive *unregister_dive(int idx);
|
||||
extern bool has_dive(unsigned int deviceid, unsigned int diveid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // DIVELIST_H
|
||||
|
|
582
core/divelog.cpp
|
@ -3,96 +3,534 @@
|
|||
#include "divelist.h"
|
||||
#include "divesite.h"
|
||||
#include "device.h"
|
||||
#include "dive.h"
|
||||
#include "errorhelper.h"
|
||||
#include "filterpreset.h"
|
||||
#include "filterpresettable.h"
|
||||
#include "qthelper.h" // for emit_reset_signal() -> should be removed
|
||||
#include "range.h"
|
||||
#include "selection.h" // clearly, a layering violation -> should be removed
|
||||
#include "trip.h"
|
||||
|
||||
struct divelog divelog;
|
||||
|
||||
// We can't use smart pointers, since this is used from C
|
||||
// and it would be bold to presume that std::unique_ptr<>
|
||||
// and a plain pointer have the same memory layout.
|
||||
divelog::divelog() :
|
||||
dives(new dive_table),
|
||||
trips(new trip_table),
|
||||
sites(new dive_site_table),
|
||||
devices(new device_table),
|
||||
filter_presets(new filter_preset_table),
|
||||
autogroup(false)
|
||||
{
|
||||
*dives = empty_dive_table;
|
||||
*trips = empty_trip_table;
|
||||
*sites = empty_dive_site_table;
|
||||
}
|
||||
|
||||
divelog::~divelog()
|
||||
{
|
||||
clear_dive_table(dives);
|
||||
clear_trip_table(trips);
|
||||
clear_dive_site_table(sites);
|
||||
delete dives;
|
||||
delete trips;
|
||||
delete sites;
|
||||
delete devices;
|
||||
delete filter_presets;
|
||||
}
|
||||
|
||||
divelog::divelog(divelog &&log) :
|
||||
dives(new dive_table),
|
||||
trips(new trip_table),
|
||||
sites(new dive_site_table),
|
||||
devices(new device_table),
|
||||
filter_presets(new filter_preset_table)
|
||||
{
|
||||
*dives = empty_dive_table;
|
||||
*trips = empty_trip_table;
|
||||
*sites = empty_dive_site_table;
|
||||
move_dive_table(log.dives, dives);
|
||||
move_trip_table(log.trips, trips);
|
||||
move_dive_site_table(log.sites, sites);
|
||||
*devices = std::move(*log.devices);
|
||||
*filter_presets = std::move(*log.filter_presets);
|
||||
}
|
||||
|
||||
struct divelog &divelog::operator=(divelog &&log)
|
||||
{
|
||||
move_dive_table(log.dives, dives);
|
||||
move_trip_table(log.trips, trips);
|
||||
move_dive_site_table(log.sites, sites);
|
||||
*devices = std::move(*log.devices);
|
||||
*filter_presets = std::move(*log.filter_presets);
|
||||
return *this;
|
||||
}
|
||||
divelog::divelog() = default;
|
||||
divelog::~divelog() = default;
|
||||
divelog::divelog(divelog &&) = default;
|
||||
struct divelog &divelog::operator=(divelog &&) = default;
|
||||
|
||||
/* this implements the mechanics of removing the dive from the
|
||||
* dive log and the trip, but doesn't deal with updating dive trips, etc */
|
||||
void delete_single_dive(struct divelog *log, int idx)
|
||||
void divelog::delete_single_dive(int idx)
|
||||
{
|
||||
if (idx < 0 || idx > log->dives->nr) {
|
||||
report_info("Warning: deleting unexisting dive with index %d", idx);
|
||||
if (idx < 0 || static_cast<size_t>(idx) >= dives.size()) {
|
||||
report_info("Warning: deleting non-existing dive with index %d", idx);
|
||||
return;
|
||||
}
|
||||
struct dive *dive = log->dives->dives[idx];
|
||||
remove_dive_from_trip(dive, log->trips);
|
||||
struct dive *dive = dives[idx].get();
|
||||
struct dive_trip *trip = unregister_dive_from_trip(dive);
|
||||
|
||||
// Deleting a dive may change the order of trips!
|
||||
if (trip)
|
||||
trips.sort();
|
||||
|
||||
if (trip && trip->dives.empty())
|
||||
trips.pull(trip);
|
||||
unregister_dive_from_dive_site(dive);
|
||||
delete_dive_from_table(log->dives, idx);
|
||||
dives.erase(dives.begin() + idx);
|
||||
}
|
||||
|
||||
void divelog::delete_multiple_dives(const std::vector<dive *> &dives_to_delete)
|
||||
{
|
||||
bool trips_changed = false;
|
||||
|
||||
for (dive *d: dives_to_delete) {
|
||||
// Delete dive from trip and delete trip if empty
|
||||
struct dive_trip *trip = unregister_dive_from_trip(d);
|
||||
if (trip && trip->dives.empty()) {
|
||||
trips_changed = true;
|
||||
trips.pull(trip);
|
||||
}
|
||||
|
||||
unregister_dive_from_dive_site(d);
|
||||
dives.pull(d);
|
||||
}
|
||||
|
||||
// Deleting a dive may change the order of trips!
|
||||
if (trips_changed)
|
||||
trips.sort();
|
||||
}
|
||||
|
||||
void divelog::clear()
|
||||
{
|
||||
while (dives->nr > 0)
|
||||
delete_single_dive(this, dives->nr - 1);
|
||||
while (sites->nr)
|
||||
delete_dive_site(get_dive_site(0, sites), sites);
|
||||
if (trips->nr != 0) {
|
||||
report_info("Warning: trip table not empty in divelog::clear()!");
|
||||
trips->nr = 0;
|
||||
}
|
||||
clear_device_table(devices);
|
||||
filter_presets->clear();
|
||||
dives.clear();
|
||||
sites.clear();
|
||||
trips.clear();
|
||||
devices.clear();
|
||||
filter_presets.clear();
|
||||
}
|
||||
|
||||
extern "C" void clear_divelog(struct divelog *log)
|
||||
/* check if we have a trip right before / after this dive */
|
||||
bool divelog::is_trip_before_after(const struct dive *dive, bool before) const
|
||||
{
|
||||
log->clear();
|
||||
auto it = std::find_if(dives.begin(), dives.end(),
|
||||
[dive](auto &d) { return d.get() == dive; });
|
||||
if (it == dives.end())
|
||||
return false;
|
||||
|
||||
if (before) {
|
||||
do {
|
||||
if (it == dives.begin())
|
||||
return false;
|
||||
--it;
|
||||
} while ((*it)->invalid);
|
||||
return (*it)->divetrip != nullptr;
|
||||
} else {
|
||||
++it;
|
||||
while (it != dives.end() && (*it)->invalid)
|
||||
++it;
|
||||
return it != dives.end() && (*it)->divetrip != nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Merge subsequent dives in a table, if mergeable. This assumes
|
||||
* that the dives are neither selected, not part of a trip, as
|
||||
* is the case of freshly imported dives.
|
||||
*/
|
||||
static void merge_imported_dives(struct dive_table &table)
|
||||
{
|
||||
for (size_t i = 1; i < table.size(); i++) {
|
||||
auto &prev = table[i - 1];
|
||||
auto &dive = table[i];
|
||||
|
||||
/* only try to merge overlapping dives - or if one of the dives has
|
||||
* zero duration (that might be a gps marker from the webservice) */
|
||||
if (prev->duration.seconds && dive->duration.seconds &&
|
||||
prev->endtime() < dive->when)
|
||||
continue;
|
||||
|
||||
auto merged = table.try_to_merge(*prev, *dive, false);
|
||||
if (!merged)
|
||||
continue;
|
||||
|
||||
/* Add dive to dive site; try_to_merge() does not do that! */
|
||||
struct dive_site *ds = merged->dive_site;
|
||||
if (ds) {
|
||||
merged->dive_site = NULL;
|
||||
ds->add_dive(merged.get());
|
||||
}
|
||||
unregister_dive_from_dive_site(prev.get());
|
||||
unregister_dive_from_dive_site(dive.get());
|
||||
unregister_dive_from_trip(prev.get());
|
||||
unregister_dive_from_trip(dive.get());
|
||||
|
||||
/* Overwrite the first of the two dives and remove the second */
|
||||
table[i - 1] = std::move(merged);
|
||||
table.erase(table.begin() + i);
|
||||
|
||||
/* Redo the new 'i'th dive */
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk the dives from the oldest dive in the given table, and see if we
|
||||
* can autogroup them. But only do this when the user selected autogrouping.
|
||||
*/
|
||||
static void autogroup_dives(struct divelog &log)
|
||||
{
|
||||
if (!log.autogroup)
|
||||
return;
|
||||
|
||||
for (auto &entry: get_dives_to_autogroup(log.dives)) {
|
||||
for (auto it = log.dives.begin() + entry.from; it != log.dives.begin() + entry.to; ++it)
|
||||
entry.trip->add_dive(it->get());
|
||||
/* If this was newly allocated, add trip to list */
|
||||
if (entry.created_trip)
|
||||
log.trips.put(std::move(entry.created_trip));
|
||||
}
|
||||
log.trips.sort(); // Necessary?
|
||||
#ifdef DEBUG_TRIP
|
||||
dump_trip_list();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Check if a dive is ranked after the last dive of the global dive list */
|
||||
static bool dive_is_after_last(const struct dive &d)
|
||||
{
|
||||
if (divelog.dives.empty())
|
||||
return true;
|
||||
return dive_less_than(*divelog.dives.back(), d);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to merge a new dive into the dive at position idx. Return
|
||||
* true on success. On success, the old dive will be added to the
|
||||
* dives_to_remove table and the merged dive to the dives_to_add
|
||||
* table. On failure everything stays unchanged.
|
||||
* If "prefer_imported" is true, use data of the new dive.
|
||||
*/
|
||||
static bool try_to_merge_into(struct dive &dive_to_add, struct dive *old_dive, bool prefer_imported,
|
||||
/* output parameters: */
|
||||
struct dive_table &dives_to_add, struct std::vector<dive *> &dives_to_remove)
|
||||
{
|
||||
auto merged = dives_to_add.try_to_merge(*old_dive, dive_to_add, prefer_imported);
|
||||
if (!merged)
|
||||
return false;
|
||||
|
||||
merged->divetrip = old_dive->divetrip;
|
||||
range_insert_sorted(dives_to_remove, old_dive, comp_dives_ptr);
|
||||
dives_to_add.put(std::move(merged));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Merge dives from "dives_from", owned by "delete" into the owned by "dives_to".
|
||||
* Overlapping dives will be merged, non-overlapping dives will be moved. The results
|
||||
* will be added to the "dives_to_add" table. Dives that were merged are added to
|
||||
* the "dives_to_remove" table. Any newly added (not merged) dive will be assigned
|
||||
* to the trip of the "trip" paremeter. If "delete_from" is non-null dives will be
|
||||
* removed from this table.
|
||||
* This function supposes that all input tables are sorted.
|
||||
* Returns true if any dive was added (not merged) that is not past the
|
||||
* last dive of the global dive list (i.e. the sequence will change).
|
||||
* The integer referenced by "num_merged" will be increased for every
|
||||
* merged dive that is added to "dives_to_add" */
|
||||
static bool merge_dive_tables(const std::vector<dive *> &dives_from, struct dive_table &delete_from,
|
||||
const std::vector<dive *> &dives_to,
|
||||
bool prefer_imported, struct dive_trip *trip,
|
||||
/* output parameters: */
|
||||
struct dive_table &dives_to_add, struct std::vector<dive *> &dives_to_remove,
|
||||
int &num_merged)
|
||||
{
|
||||
bool sequence_changed = false;
|
||||
|
||||
/* Merge newly imported dives into the dive table.
|
||||
* Since both lists (old and new) are sorted, we can step
|
||||
* through them concurrently and locate the insertions points.
|
||||
* Once found, check if the new dive can be merged in the
|
||||
* previous or next dive.
|
||||
* Note that this doesn't consider pathological cases such as:
|
||||
* - New dive "connects" two old dives (turn three into one).
|
||||
* - New dive can not be merged into adjacent but some further dive.
|
||||
*/
|
||||
size_t j = 0; /* Index in dives_to */
|
||||
size_t last_merged_into = std::string::npos;
|
||||
for (dive *add: dives_from) {
|
||||
/* This gets an owning pointer to the dive to add and removes it from
|
||||
* the delete_from table. If the dive is not explicitly stored, it will
|
||||
* be automatically deleting when ending the loop iteration */
|
||||
auto [dive_to_add, idx] = delete_from.pull(add);
|
||||
if (!dive_to_add) {
|
||||
report_info("merging unknown dives!");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Find insertion point. */
|
||||
while (j < dives_to.size() && dive_less_than(*dives_to[j], *dive_to_add))
|
||||
j++;
|
||||
|
||||
/* Try to merge into previous dive.
|
||||
* We are extra-careful to not merge into the same dive twice, as that
|
||||
* would put the merged-into dive twice onto the dives-to-delete list.
|
||||
* In principle that shouldn't happen as all dives that compare equal
|
||||
* by is_same_dive() were already merged, and is_same_dive() should be
|
||||
* transitive. But let's just go *completely* sure for the odd corner-case. */
|
||||
if (j > 0 && (last_merged_into == std::string::npos || j > last_merged_into + 1) &&
|
||||
dives_to[j - 1]->endtime() > dive_to_add->when) {
|
||||
if (try_to_merge_into(*dive_to_add, dives_to[j - 1], prefer_imported,
|
||||
dives_to_add, dives_to_remove)) {
|
||||
last_merged_into = j - 1;
|
||||
num_merged++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* That didn't merge into the previous dive.
|
||||
* Try to merge into next dive. */
|
||||
if (j < dives_to.size() && (last_merged_into == std::string::npos || j > last_merged_into) &&
|
||||
dive_to_add->endtime() > dives_to[j]->when) {
|
||||
if (try_to_merge_into(*dive_to_add, dives_to[j], prefer_imported,
|
||||
dives_to_add, dives_to_remove)) {
|
||||
last_merged_into = j;
|
||||
num_merged++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
sequence_changed |= !dive_is_after_last(*dive_to_add);
|
||||
dives_to_add.put(std::move(dive_to_add));
|
||||
}
|
||||
|
||||
return sequence_changed;
|
||||
}
|
||||
|
||||
/* Helper function for process_imported_dives():
|
||||
* Try to merge a trip into one of the existing trips.
|
||||
* The bool pointed to by "sequence_changed" is set to true, if the sequence of
|
||||
* the existing dives changes.
|
||||
* The int pointed to by "start_renumbering_at" keeps track of the first dive
|
||||
* to be renumbered in the dives_to_add table.
|
||||
* For other parameters see process_imported_dives()
|
||||
* Returns true if trip was merged. In this case, the trip will be
|
||||
* freed.
|
||||
*/
|
||||
static bool try_to_merge_trip(dive_trip &trip_import, struct dive_table &import_table, bool prefer_imported,
|
||||
/* output parameters: */
|
||||
struct dive_table &dives_to_add, std::vector<dive *> &dives_to_remove,
|
||||
bool &sequence_changed, int &start_renumbering_at)
|
||||
{
|
||||
for (auto &trip_old: divelog.trips) {
|
||||
if (trips_overlap(trip_import, *trip_old)) {
|
||||
sequence_changed |= merge_dive_tables(trip_import.dives, import_table, trip_old->dives,
|
||||
prefer_imported, trip_old.get(),
|
||||
dives_to_add, dives_to_remove,
|
||||
start_renumbering_at);
|
||||
/* we took care of all dives of the trip, clean up the table */
|
||||
trip_import.dives.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper function to convert a table of owned dives into a table of non-owning pointers.
|
||||
// Used to merge *all* dives of a log into a different table.
|
||||
static std::vector<dive *> dive_table_to_non_owning(const dive_table &dives)
|
||||
{
|
||||
std::vector<dive *> res;
|
||||
res.reserve(dives.size());
|
||||
for (auto &d: dives)
|
||||
res.push_back(d.get());
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Process imported dives: take a log.trips of dives to be imported and
|
||||
* generate five lists:
|
||||
* 1) Dives to be added (newly created, owned)
|
||||
* 2) Dives to be removed (old, non-owned, references global divelog)
|
||||
* 3) Trips to be added (newly created, owned)
|
||||
* 4) Dive sites to be added (newly created, owned)
|
||||
* 5) Devices to be added (newly created, owned)
|
||||
* The dives, trips and sites in import_log are consumed.
|
||||
* On return, the tables have * size 0.
|
||||
*
|
||||
* Note: The new dives will have their divetrip- and divesites-fields
|
||||
* set, but will *not* be part of the trip and site. The caller has to
|
||||
* add them to the trip and site.
|
||||
*
|
||||
* The lists are generated by merging dives if possible. This is
|
||||
* performed trip-wise. Finer control on merging is provided by
|
||||
* the "flags" parameter:
|
||||
* - If import_flags::prefer_imported is set, data of the new dives are
|
||||
* prioritized on merging.
|
||||
* - If import_flags::merge_all_trips is set, all overlapping trips will
|
||||
* be merged, not only non-autogenerated trips.
|
||||
* - If import_flags::is_downloaded is true, only the divecomputer of the
|
||||
* first dive will be considered, as it is assumed that all dives
|
||||
* come from the same computer.
|
||||
* - If import_flags::add_to_new_trip is true, dives that are not assigned
|
||||
* to a trip will be added to a newly generated trip.
|
||||
*/
|
||||
divelog::process_imported_dives_result divelog::process_imported_dives(struct divelog &import_log, int flags)
|
||||
{
|
||||
int start_renumbering_at = 0;
|
||||
bool sequence_changed = false;
|
||||
bool last_old_dive_is_numbered;
|
||||
|
||||
process_imported_dives_result res;
|
||||
|
||||
/* If no dives were imported, don't bother doing anything */
|
||||
if (import_log.dives.empty())
|
||||
return res;
|
||||
|
||||
/* Check if any of the new dives has a number. This will be
|
||||
* important later to decide if we want to renumber the added
|
||||
* dives */
|
||||
bool new_dive_has_number = std::any_of(import_log.dives.begin(), import_log.dives.end(),
|
||||
[](auto &d) { return d->number > 0; });
|
||||
|
||||
/* Add only the devices that we don't know about yet. */
|
||||
for (auto &dev: import_log.devices) {
|
||||
if (!device_exists(devices, dev))
|
||||
add_to_device_table(res.devices_to_add, dev);
|
||||
}
|
||||
|
||||
/* Sort the table of dives to be imported and combine mergable dives */
|
||||
import_log.dives.sort();
|
||||
merge_imported_dives(import_log.dives);
|
||||
|
||||
/* Autogroup tripless dives if desired by user. But don't autogroup
|
||||
* if tripless dives should be added to a new trip. */
|
||||
if (!(flags & import_flags::add_to_new_trip))
|
||||
autogroup_dives(import_log);
|
||||
|
||||
/* If dive sites already exist, use the existing versions. */
|
||||
for (auto &new_ds: import_log.sites) {
|
||||
/* Check if it dive site is actually used by new dives. */
|
||||
if (std::none_of(import_log.dives.begin(), import_log.dives.end(), [ds=new_ds.get()]
|
||||
(auto &d) { return d->dive_site == ds; }))
|
||||
continue;
|
||||
|
||||
struct dive_site *old_ds = sites.get_same(*new_ds);
|
||||
if (!old_ds) {
|
||||
/* Dive site doesn't exist. Add it to list of dive sites to be added. */
|
||||
new_ds->dives.clear(); /* Caller is responsible for adding dives to site */
|
||||
res.sites_to_add.put(std::move(new_ds));
|
||||
} else {
|
||||
/* Dive site already exists - use the old one. */
|
||||
for (auto &d: import_log.dives) {
|
||||
if (d->dive_site == new_ds.get())
|
||||
d->dive_site = old_ds;
|
||||
}
|
||||
}
|
||||
}
|
||||
import_log.sites.clear();
|
||||
|
||||
/* Merge overlapping trips. Since both trip tables are sorted, we
|
||||
* could be smarter here, but realistically not a whole lot of trips
|
||||
* will be imported so do a simple n*m loop until someone complains.
|
||||
*/
|
||||
for (auto &trip_import: import_log.trips) {
|
||||
if ((flags & import_flags::merge_all_trips) || trip_import->autogen) {
|
||||
if (try_to_merge_trip(*trip_import, import_log.dives, flags & import_flags::prefer_imported,
|
||||
res.dives_to_add, res.dives_to_remove,
|
||||
sequence_changed, start_renumbering_at))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If no trip to merge-into was found, add trip as-is.
|
||||
* First, add dives to list of dives to add */
|
||||
for (struct dive *d: trip_import->dives) {
|
||||
/* Add dive to list of dives to-be-added. */
|
||||
auto [owned, idx] = import_log.dives.pull(d);
|
||||
if (!owned)
|
||||
continue;
|
||||
sequence_changed |= !dive_is_after_last(*owned);
|
||||
res.dives_to_add.put(std::move(owned));
|
||||
}
|
||||
|
||||
trip_import->dives.clear(); /* Caller is responsible for adding dives to trip */
|
||||
|
||||
/* Finally, add trip to list of trips to add */
|
||||
res.trips_to_add.put(std::move(trip_import));
|
||||
}
|
||||
import_log.trips.clear(); /* All trips were consumed */
|
||||
|
||||
if ((flags & import_flags::add_to_new_trip) && !import_log.dives.empty()) {
|
||||
/* Create a new trip for unassigned dives, if desired. */
|
||||
auto [new_trip, idx] = res.trips_to_add.put(
|
||||
create_trip_from_dive(import_log.dives.front().get())
|
||||
);
|
||||
|
||||
/* Add all remaining dives to this trip */
|
||||
for (auto &d: import_log.dives) {
|
||||
sequence_changed |= !dive_is_after_last(*d);
|
||||
d->divetrip = new_trip;
|
||||
res.dives_to_add.put(std::move(d));
|
||||
}
|
||||
|
||||
import_log.dives.clear(); /* All dives were consumed */
|
||||
} else if (!import_log.dives.empty()) {
|
||||
/* The remaining dives in import_log.dives are those that don't belong to
|
||||
* a trip and the caller does not want them to be associated to a
|
||||
* new trip. Merge them into the global table. */
|
||||
sequence_changed |= merge_dive_tables(dive_table_to_non_owning(import_log.dives),
|
||||
import_log.dives,
|
||||
dive_table_to_non_owning(dives),
|
||||
flags & import_flags::prefer_imported, NULL,
|
||||
res.dives_to_add, res.dives_to_remove, start_renumbering_at);
|
||||
}
|
||||
|
||||
/* If new dives were only added at the end, renumber the added dives.
|
||||
* But only if
|
||||
* - The last dive in the old dive table had a number itself (if there is a last dive).
|
||||
* - None of the new dives has a number.
|
||||
*/
|
||||
last_old_dive_is_numbered = dives.empty() || dives.back()->number > 0;
|
||||
|
||||
/* We counted the number of merged dives that were added to dives_to_add.
|
||||
* Skip those. Since sequence_changed is false all added dives are *after*
|
||||
* all merged dives. */
|
||||
if (!sequence_changed && last_old_dive_is_numbered && !new_dive_has_number) {
|
||||
int nr = !dives.empty() ? dives.back()->number : 0;
|
||||
for (auto it = res.dives_to_add.begin() + start_renumbering_at; it < res.dives_to_add.end(); ++it)
|
||||
(*it)->number = ++nr;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// TODO: This accesses global state, namely fulltext and selection.
|
||||
// TODO: Move up the call chain?
|
||||
void divelog::process_loaded_dives()
|
||||
{
|
||||
dives.sort();
|
||||
trips.sort();
|
||||
|
||||
/* Autogroup dives if desired by user. */
|
||||
autogroup_dives(*this);
|
||||
|
||||
fulltext_populate();
|
||||
|
||||
/* Inform frontend of reset data. This should reset all the models. */
|
||||
emit_reset_signal();
|
||||
|
||||
/* Now that everything is settled, select the newest dive. */
|
||||
select_newest_visible_dive();
|
||||
}
|
||||
|
||||
/* Merge the dives of the trip "from" and the dive_table "dives_from" into the trip "to"
|
||||
* and dive_table "dives_to". If "prefer_imported" is true, dive data of "from" takes
|
||||
* precedence */
|
||||
void divelog::add_imported_dives(struct divelog &import_log, int flags)
|
||||
{
|
||||
/* Process imported dives and generate lists of dives
|
||||
* to-be-added and to-be-removed */
|
||||
auto [dives_to_add, dives_to_remove, trips_to_add, dive_sites_to_add, devices_to_add] =
|
||||
process_imported_dives(import_log, flags);
|
||||
|
||||
/* Start by deselecting all dives, so that we don't end up with an invalid selection */
|
||||
select_single_dive(NULL);
|
||||
|
||||
/* Add new dives to trip and site to get reference count correct. */
|
||||
for (auto &d: dives_to_add) {
|
||||
struct dive_trip *trip = d->divetrip;
|
||||
struct dive_site *site = d->dive_site;
|
||||
d->divetrip = NULL;
|
||||
d->dive_site = NULL;
|
||||
trip->add_dive(d.get());
|
||||
if (site)
|
||||
site->add_dive(d.get());
|
||||
}
|
||||
|
||||
/* Remove old dives */
|
||||
delete_multiple_dives(dives_to_remove);
|
||||
|
||||
/* Add new dives */
|
||||
for (auto &d: dives_to_add)
|
||||
dives.put(std::move(d));
|
||||
dives_to_add.clear();
|
||||
|
||||
/* Add new trips */
|
||||
for (auto &trip: trips_to_add)
|
||||
trips.put(std::move(trip));
|
||||
trips_to_add.clear();
|
||||
|
||||
/* Add new dive sites */
|
||||
for (auto &ds: dive_sites_to_add)
|
||||
sites.register_site(std::move(ds));
|
||||
|
||||
/* Add new devices */
|
||||
for (auto &dev: devices_to_add)
|
||||
add_to_device_table(devices, dev);
|
||||
|
||||
/* We might have deleted the old selected dive.
|
||||
* Choose the newest dive as selected (if any) */
|
||||
current_dive = !dives.empty() ? dives.back().get() : nullptr;
|
||||
|
||||
/* Inform frontend of reset data. This should reset all the models. */
|
||||
emit_reset_signal();
|
||||
}
|
||||
|
|
|
@ -1,43 +1,57 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// A structure that contains all the values we save in a divelog file
|
||||
// A structure that contains all the data we store in divelog files
|
||||
#ifndef DIVELOG_H
|
||||
#define DIVELOG_H
|
||||
|
||||
struct dive_table;
|
||||
struct trip_table;
|
||||
struct dive_site_table;
|
||||
struct device_table;
|
||||
struct filter_preset_table;
|
||||
#include "divelist.h"
|
||||
#include "divesitetable.h"
|
||||
#include "filterpresettable.h"
|
||||
#include "triptable.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <vector>
|
||||
|
||||
struct device;
|
||||
|
||||
/* flags for process_imported_dives() */
|
||||
struct import_flags {
|
||||
static constexpr int prefer_imported = 1 << 0;
|
||||
static constexpr int is_downloaded = 1 << 1;
|
||||
static constexpr int merge_all_trips = 1 << 2;
|
||||
static constexpr int add_to_new_trip = 1 << 3;
|
||||
};
|
||||
|
||||
struct divelog {
|
||||
struct dive_table *dives;
|
||||
struct trip_table *trips;
|
||||
struct dive_site_table *sites;
|
||||
struct device_table *devices;
|
||||
struct filter_preset_table *filter_presets;
|
||||
bool autogroup;
|
||||
#ifdef __cplusplus
|
||||
void clear();
|
||||
dive_table dives;
|
||||
trip_table trips;
|
||||
dive_site_table sites;
|
||||
std::vector<device> devices;
|
||||
filter_preset_table filter_presets;
|
||||
bool autogroup = false;
|
||||
|
||||
divelog();
|
||||
~divelog();
|
||||
divelog(divelog &&log); // move constructor (argument is consumed).
|
||||
divelog &operator=(divelog &&log); // move assignment (argument is consumed).
|
||||
#endif
|
||||
divelog(divelog &&); // move constructor (argument is consumed).
|
||||
divelog &operator=(divelog &&); // move assignment (argument is consumed).
|
||||
|
||||
void delete_single_dive(int idx);
|
||||
void delete_multiple_dives(const std::vector<dive *> &dives);
|
||||
void clear();
|
||||
bool is_trip_before_after(const struct dive *dive, bool before) const;
|
||||
|
||||
struct process_imported_dives_result {
|
||||
dive_table dives_to_add;
|
||||
std::vector<dive *> dives_to_remove;
|
||||
trip_table trips_to_add;
|
||||
dive_site_table sites_to_add;
|
||||
std::vector<device> devices_to_add;
|
||||
};
|
||||
|
||||
/* divelist core logic functions */
|
||||
process_imported_dives_result process_imported_dives(struct divelog &import_log, int flags); // import_log will be consumed
|
||||
void process_loaded_dives();
|
||||
void add_imported_dives(struct divelog &log, int flags); // log will be consumed
|
||||
};
|
||||
|
||||
extern struct divelog divelog;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void clear_divelog(struct divelog *);
|
||||
extern void delete_single_dive(struct divelog *, int idx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -75,45 +75,42 @@ static void exportHTMLstatistics(const QString filename, struct htmlExportSettin
|
|||
QFile file(filename);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream out(&file);
|
||||
stats_summary_auto_free stats;
|
||||
|
||||
stats_t total_stats;
|
||||
|
||||
calculate_stats_summary(&stats, hes.selectedOnly);
|
||||
stats_summary stats = calculate_stats_summary(hes.selectedOnly);
|
||||
total_stats.selection_size = 0;
|
||||
total_stats.total_time.seconds = 0;
|
||||
total_stats.total_time = 0_sec;
|
||||
|
||||
int i = 0;
|
||||
out << "divestat=[";
|
||||
if (hes.yearlyStatistics) {
|
||||
while (stats.stats_yearly != NULL && stats.stats_yearly[i].period) {
|
||||
for (const auto &s: stats.stats_yearly) {
|
||||
out << "{";
|
||||
out << "\"YEAR\":\"" << stats.stats_yearly[i].period << "\",";
|
||||
out << "\"DIVES\":\"" << stats.stats_yearly[i].selection_size << "\",";
|
||||
out << "\"TOTAL_TIME\":\"" << get_dive_duration_string(stats.stats_yearly[i].total_time.seconds,
|
||||
out << "\"YEAR\":\"" << s.period << "\",";
|
||||
out << "\"DIVES\":\"" << s.selection_size << "\",";
|
||||
out << "\"TOTAL_TIME\":\"" << get_dive_duration_string(s.total_time.seconds,
|
||||
gettextFromC::tr("h"), gettextFromC::tr("min"), gettextFromC::tr("sec"), " ") << "\",";
|
||||
out << "\"AVERAGE_TIME\":\"" << formatMinutes(stats.stats_yearly[i].total_time.seconds / stats.stats_yearly[i].selection_size) << "\",";
|
||||
out << "\"SHORTEST_TIME\":\"" << formatMinutes(stats.stats_yearly[i].shortest_time.seconds) << "\",";
|
||||
out << "\"LONGEST_TIME\":\"" << formatMinutes(stats.stats_yearly[i].longest_time.seconds) << "\",";
|
||||
out << "\"AVG_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].avg_depth) << "\",";
|
||||
out << "\"MIN_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].min_depth) << "\",";
|
||||
out << "\"MAX_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].max_depth) << "\",";
|
||||
out << "\"AVG_SAC\":\"" << get_volume_string(stats.stats_yearly[i].avg_sac) << "\",";
|
||||
out << "\"MIN_SAC\":\"" << get_volume_string(stats.stats_yearly[i].min_sac) << "\",";
|
||||
out << "\"MAX_SAC\":\"" << get_volume_string(stats.stats_yearly[i].max_sac) << "\",";
|
||||
if (stats.stats_yearly[i].combined_count) {
|
||||
out << "\"AVERAGE_TIME\":\"" << formatMinutes(s.total_time.seconds / s.selection_size) << "\",";
|
||||
out << "\"SHORTEST_TIME\":\"" << formatMinutes(s.shortest_time.seconds) << "\",";
|
||||
out << "\"LONGEST_TIME\":\"" << formatMinutes(s.longest_time.seconds) << "\",";
|
||||
out << "\"AVG_DEPTH\":\"" << get_depth_string(s.avg_depth) << "\",";
|
||||
out << "\"MIN_DEPTH\":\"" << get_depth_string(s.min_depth) << "\",";
|
||||
out << "\"MAX_DEPTH\":\"" << get_depth_string(s.max_depth) << "\",";
|
||||
out << "\"AVG_SAC\":\"" << get_volume_string(s.avg_sac) << "\",";
|
||||
out << "\"MIN_SAC\":\"" << get_volume_string(s.min_sac) << "\",";
|
||||
out << "\"MAX_SAC\":\"" << get_volume_string(s.max_sac) << "\",";
|
||||
if (s.combined_count) {
|
||||
temperature_t avg_temp;
|
||||
avg_temp.mkelvin = stats.stats_yearly[i].combined_temp.mkelvin / stats.stats_yearly[i].combined_count;
|
||||
avg_temp.mkelvin = s.combined_temp.mkelvin / s.combined_count;
|
||||
out << "\"AVG_TEMP\":\"" << get_temperature_string(avg_temp) << "\",";
|
||||
} else {
|
||||
out << "\"AVG_TEMP\":\"0.0\",";
|
||||
}
|
||||
out << "\"MIN_TEMP\":\"" << (stats.stats_yearly[i].min_temp.mkelvin == 0 ? 0 : get_temperature_string(stats.stats_yearly[i].min_temp)) << "\",";
|
||||
out << "\"MAX_TEMP\":\"" << (stats.stats_yearly[i].max_temp.mkelvin == 0 ? 0 : get_temperature_string(stats.stats_yearly[i].max_temp)) << "\",";
|
||||
out << "\"MIN_TEMP\":\"" << (s.min_temp.mkelvin == 0 ? 0 : get_temperature_string(s.min_temp)) << "\",";
|
||||
out << "\"MAX_TEMP\":\"" << (s.max_temp.mkelvin == 0 ? 0 : get_temperature_string(s.max_temp)) << "\",";
|
||||
out << "},";
|
||||
total_stats.selection_size += stats.stats_yearly[i].selection_size;
|
||||
total_stats.total_time.seconds += stats.stats_yearly[i].total_time.seconds;
|
||||
i++;
|
||||
total_stats.selection_size += s.selection_size;
|
||||
total_stats.total_time += s.total_time;
|
||||
}
|
||||
exportHTMLstatisticsTotal(out, &total_stats);
|
||||
}
|
||||
|
|
|
@ -4,4 +4,6 @@
|
|||
|
||||
enum divemode_t {OC, CCR, PSCR, FREEDIVE, NUM_DIVEMODE, UNDEF_COMP_TYPE}; // Flags (Open-circuit and Closed-circuit-rebreather) for setting dive computer type
|
||||
|
||||
#define IS_REBREATHER_MODE(divemode) ((divemode) == CCR || (divemode) == PSCR)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "divesite.h"
|
||||
#include "pref.h"
|
||||
#include "gettextfromc.h"
|
||||
|
||||
QString constructLocationTags(struct taxonomy_data *taxonomy, bool for_maintab)
|
||||
{
|
||||
QString locationTag;
|
||||
|
||||
if (!taxonomy->nr)
|
||||
return locationTag;
|
||||
|
||||
/* Check if the user set any of the 3 geocoding categories */
|
||||
bool prefs_set = false;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (prefs.geocoding.category[i] != TC_NONE)
|
||||
prefs_set = true;
|
||||
}
|
||||
|
||||
if (!prefs_set && !for_maintab) {
|
||||
locationTag = QString("<small><small>") + gettextFromC::tr("No dive site layout categories set in preferences!") +
|
||||
QString("</small></small>");
|
||||
return locationTag;
|
||||
}
|
||||
else if (!prefs_set)
|
||||
return locationTag;
|
||||
|
||||
if (for_maintab)
|
||||
locationTag = QString("<small><small>(") + gettextFromC::tr("Tags") + QString(": ");
|
||||
else
|
||||
locationTag = QString("<small><small>");
|
||||
QString connector;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (prefs.geocoding.category[i] == TC_NONE)
|
||||
continue;
|
||||
for (int j = 0; j < taxonomy->nr; j++) {
|
||||
if (taxonomy->category[j].category == prefs.geocoding.category[i]) {
|
||||
QString tag = taxonomy->category[j].value;
|
||||
if (!tag.isEmpty()) {
|
||||
locationTag += connector + tag;
|
||||
connector = " / ";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (for_maintab)
|
||||
locationTag += ")</small></small>";
|
||||
else
|
||||
locationTag += "</small></small>";
|
||||
return locationTag;
|
||||
}
|
401
core/divesite.c
|
@ -1,401 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* divesite.c */
|
||||
#include "divesite.h"
|
||||
#include "dive.h"
|
||||
#include "divelist.h"
|
||||
#include "divelog.h"
|
||||
#include "errorhelper.h"
|
||||
#include "membuffer.h"
|
||||
#include "subsurface-string.h"
|
||||
#include "table.h"
|
||||
#include "sha1.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
int get_divesite_idx(const struct dive_site *ds, struct dive_site_table *ds_table)
|
||||
{
|
||||
int i;
|
||||
const struct dive_site *d;
|
||||
// tempting as it may be, don't die when called with ds=NULL
|
||||
if (ds)
|
||||
for_each_dive_site(i, d, ds_table) {
|
||||
if (d == ds)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: keep table sorted by UUID and do a binary search?
|
||||
struct dive_site *get_dive_site_by_uuid(uint32_t uuid, struct dive_site_table *ds_table)
|
||||
{
|
||||
int i;
|
||||
struct dive_site *ds;
|
||||
for_each_dive_site (i, ds, ds_table)
|
||||
if (ds->uuid == uuid)
|
||||
return get_dive_site(i, ds_table);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* there could be multiple sites of the same name - return the first one */
|
||||
struct dive_site *get_dive_site_by_name(const char *name, struct dive_site_table *ds_table)
|
||||
{
|
||||
int i;
|
||||
struct dive_site *ds;
|
||||
for_each_dive_site (i, ds, ds_table) {
|
||||
if (same_string(ds->name, name))
|
||||
return ds;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* there could be multiple sites at the same GPS fix - return the first one */
|
||||
struct dive_site *get_dive_site_by_gps(const location_t *loc, struct dive_site_table *ds_table)
|
||||
{
|
||||
int i;
|
||||
struct dive_site *ds;
|
||||
for_each_dive_site (i, ds, ds_table) {
|
||||
if (same_location(loc, &ds->location))
|
||||
return ds;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* to avoid a bug where we have two dive sites with different name and the same GPS coordinates
|
||||
* and first get the gps coordinates (reading a V2 file) and happen to get back "the other" name,
|
||||
* this function allows us to verify if a very specific name/GPS combination already exists */
|
||||
struct dive_site *get_dive_site_by_gps_and_name(const char *name, const location_t *loc, struct dive_site_table *ds_table)
|
||||
{
|
||||
int i;
|
||||
struct dive_site *ds;
|
||||
for_each_dive_site (i, ds, ds_table) {
|
||||
if (same_location(loc, &ds->location) && same_string(ds->name, name))
|
||||
return ds;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Calculate the distance in meters between two coordinates.
|
||||
unsigned int get_distance(const location_t *loc1, const location_t *loc2)
|
||||
{
|
||||
double lat1_r = udeg_to_radians(loc1->lat.udeg);
|
||||
double lat2_r = udeg_to_radians(loc2->lat.udeg);
|
||||
double lat_d_r = udeg_to_radians(loc2->lat.udeg - loc1->lat.udeg);
|
||||
double lon_d_r = udeg_to_radians(loc2->lon.udeg - loc1->lon.udeg);
|
||||
|
||||
double a = sin(lat_d_r/2) * sin(lat_d_r/2) +
|
||||
cos(lat1_r) * cos(lat2_r) * sin(lon_d_r/2) * sin(lon_d_r/2);
|
||||
if (a < 0.0) a = 0.0;
|
||||
if (a > 1.0) a = 1.0;
|
||||
double c = 2 * atan2(sqrt(a), sqrt(1.0 - a));
|
||||
|
||||
// Earth radius in metres
|
||||
return lrint(6371000 * c);
|
||||
}
|
||||
|
||||
/* find the closest one, no more than distance meters away - if more than one at same distance, pick the first */
|
||||
struct dive_site *get_dive_site_by_gps_proximity(const location_t *loc, int distance, struct dive_site_table *ds_table)
|
||||
{
|
||||
int i;
|
||||
struct dive_site *ds, *res = NULL;
|
||||
unsigned int cur_distance, min_distance = distance;
|
||||
for_each_dive_site (i, ds, ds_table) {
|
||||
if (dive_site_has_gps_location(ds) &&
|
||||
(cur_distance = get_distance(&ds->location, loc)) < min_distance) {
|
||||
min_distance = cur_distance;
|
||||
res = ds;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int register_dive_site(struct dive_site *ds)
|
||||
{
|
||||
return add_dive_site_to_table(ds, divelog.sites);
|
||||
}
|
||||
|
||||
static int compare_sites(const struct dive_site *a, const struct dive_site *b)
|
||||
{
|
||||
return a->uuid > b->uuid ? 1 : a->uuid == b->uuid ? 0 : -1;
|
||||
}
|
||||
|
||||
static int site_less_than(const struct dive_site *a, const struct dive_site *b)
|
||||
{
|
||||
return compare_sites(a, b) < 0;
|
||||
}
|
||||
|
||||
static MAKE_GROW_TABLE(dive_site_table, struct dive_site *, dive_sites)
|
||||
static MAKE_GET_INSERTION_INDEX(dive_site_table, struct dive_site *, dive_sites, site_less_than)
|
||||
static MAKE_ADD_TO(dive_site_table, struct dive_site *, dive_sites)
|
||||
static MAKE_REMOVE_FROM(dive_site_table, dive_sites)
|
||||
static MAKE_GET_IDX(dive_site_table, struct dive_site *, dive_sites)
|
||||
MAKE_SORT(dive_site_table, struct dive_site *, dive_sites, compare_sites)
|
||||
static MAKE_REMOVE(dive_site_table, struct dive_site *, dive_site)
|
||||
MAKE_CLEAR_TABLE(dive_site_table, dive_sites, dive_site)
|
||||
MAKE_MOVE_TABLE(dive_site_table, dive_sites)
|
||||
|
||||
int add_dive_site_to_table(struct dive_site *ds, struct dive_site_table *ds_table)
|
||||
{
|
||||
/* If the site doesn't yet have an UUID, create a new one.
|
||||
* Make this deterministic for testing. */
|
||||
if (!ds->uuid) {
|
||||
SHA_CTX ctx;
|
||||
uint32_t csum[5];
|
||||
|
||||
SHA1_Init(&ctx);
|
||||
if (ds->name)
|
||||
SHA1_Update(&ctx, ds->name, strlen(ds->name));
|
||||
if (ds->description)
|
||||
SHA1_Update(&ctx, ds->description, strlen(ds->description));
|
||||
if (ds->notes)
|
||||
SHA1_Update(&ctx, ds->notes, strlen(ds->notes));
|
||||
SHA1_Final((unsigned char *)csum, &ctx);
|
||||
ds->uuid = csum[0];
|
||||
}
|
||||
|
||||
/* Take care to never have the same uuid twice. This could happen on
|
||||
* reimport of a log where the dive sites have diverged */
|
||||
while (ds->uuid == 0 || get_dive_site_by_uuid(ds->uuid, ds_table) != NULL)
|
||||
++ds->uuid;
|
||||
|
||||
int idx = dive_site_table_get_insertion_index(ds_table, ds);
|
||||
add_to_dive_site_table(ds_table, idx, ds);
|
||||
return idx;
|
||||
}
|
||||
|
||||
struct dive_site *alloc_dive_site()
|
||||
{
|
||||
struct dive_site *ds;
|
||||
ds = calloc(1, sizeof(*ds));
|
||||
if (!ds)
|
||||
exit(1);
|
||||
return ds;
|
||||
}
|
||||
|
||||
struct dive_site *alloc_dive_site_with_name(const char *name)
|
||||
{
|
||||
struct dive_site *ds = alloc_dive_site();
|
||||
ds->name = copy_string(name);
|
||||
return ds;
|
||||
}
|
||||
|
||||
struct dive_site *alloc_dive_site_with_gps(const char *name, const location_t *loc)
|
||||
{
|
||||
struct dive_site *ds = alloc_dive_site_with_name(name);
|
||||
ds->location = *loc;
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
/* when parsing, dive sites are identified by uuid */
|
||||
struct dive_site *alloc_or_get_dive_site(uint32_t uuid, struct dive_site_table *ds_table)
|
||||
{
|
||||
struct dive_site *ds;
|
||||
|
||||
if (uuid && (ds = get_dive_site_by_uuid(uuid, ds_table)) != NULL)
|
||||
return ds;
|
||||
|
||||
ds = alloc_dive_site();
|
||||
ds->uuid = uuid;
|
||||
|
||||
add_dive_site_to_table(ds, ds_table);
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
int nr_of_dives_at_dive_site(struct dive_site *ds)
|
||||
{
|
||||
return ds->dives.nr;
|
||||
}
|
||||
|
||||
bool is_dive_site_selected(struct dive_site *ds)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ds->dives.nr; i++) {
|
||||
if (ds->dives.dives[i]->selected)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void free_dive_site(struct dive_site *ds)
|
||||
{
|
||||
if (ds) {
|
||||
free(ds->name);
|
||||
free(ds->notes);
|
||||
free(ds->description);
|
||||
free(ds->dives.dives);
|
||||
free_taxonomy(&ds->taxonomy);
|
||||
free(ds);
|
||||
}
|
||||
}
|
||||
|
||||
int unregister_dive_site(struct dive_site *ds)
|
||||
{
|
||||
return remove_dive_site(ds, divelog.sites);
|
||||
}
|
||||
|
||||
void delete_dive_site(struct dive_site *ds, struct dive_site_table *ds_table)
|
||||
{
|
||||
if (!ds)
|
||||
return;
|
||||
remove_dive_site(ds, ds_table);
|
||||
free_dive_site(ds);
|
||||
}
|
||||
|
||||
/* allocate a new site and add it to the table */
|
||||
struct dive_site *create_dive_site(const char *name, struct dive_site_table *ds_table)
|
||||
{
|
||||
struct dive_site *ds = alloc_dive_site_with_name(name);
|
||||
add_dive_site_to_table(ds, ds_table);
|
||||
return ds;
|
||||
}
|
||||
|
||||
/* same as before, but with GPS data */
|
||||
struct dive_site *create_dive_site_with_gps(const char *name, const location_t *loc, struct dive_site_table *ds_table)
|
||||
{
|
||||
struct dive_site *ds = alloc_dive_site_with_gps(name, loc);
|
||||
add_dive_site_to_table(ds, ds_table);
|
||||
return ds;
|
||||
}
|
||||
|
||||
/* if all fields are empty, the dive site is pointless */
|
||||
bool dive_site_is_empty(struct dive_site *ds)
|
||||
{
|
||||
return !ds ||
|
||||
(empty_string(ds->name) &&
|
||||
empty_string(ds->description) &&
|
||||
empty_string(ds->notes) &&
|
||||
!has_location(&ds->location));
|
||||
}
|
||||
|
||||
void copy_dive_site(struct dive_site *orig, struct dive_site *copy)
|
||||
{
|
||||
free(copy->name);
|
||||
free(copy->notes);
|
||||
free(copy->description);
|
||||
|
||||
copy->location = orig->location;
|
||||
copy->name = copy_string(orig->name);
|
||||
copy->notes = copy_string(orig->notes);
|
||||
copy->description = copy_string(orig->description);
|
||||
copy_taxonomy(&orig->taxonomy, ©->taxonomy);
|
||||
}
|
||||
|
||||
static void merge_string(char **a, char **b)
|
||||
{
|
||||
char *s1 = *a, *s2 = *b;
|
||||
|
||||
if (!s2)
|
||||
return;
|
||||
|
||||
if (same_string(s1, s2))
|
||||
return;
|
||||
|
||||
if (!s1) {
|
||||
*a = strdup(s2);
|
||||
return;
|
||||
}
|
||||
|
||||
*a = format_string("(%s) or (%s)", s1, s2);
|
||||
free(s1);
|
||||
}
|
||||
|
||||
/* Used to check on import if two dive sites are equivalent.
|
||||
* Since currently no merging is performed, be very conservative
|
||||
* and only consider equal dive sites that are exactly the same.
|
||||
* Taxonomy is not compared, as no taxonomy is generated on
|
||||
* import.
|
||||
*/
|
||||
static bool same_dive_site(const struct dive_site *a, const struct dive_site *b)
|
||||
{
|
||||
return same_string(a->name, b->name)
|
||||
&& same_location(&a->location, &b->location)
|
||||
&& same_string(a->description, b->description)
|
||||
&& same_string(a->notes, b->notes);
|
||||
}
|
||||
|
||||
struct dive_site *get_same_dive_site(const struct dive_site *site)
|
||||
{
|
||||
int i;
|
||||
struct dive_site *ds;
|
||||
for_each_dive_site (i, ds, divelog.sites)
|
||||
if (same_dive_site(ds, site))
|
||||
return ds;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void merge_dive_site(struct dive_site *a, struct dive_site *b)
|
||||
{
|
||||
if (!has_location(&a->location)) a->location = b->location;
|
||||
merge_string(&a->name, &b->name);
|
||||
merge_string(&a->notes, &b->notes);
|
||||
merge_string(&a->description, &b->description);
|
||||
|
||||
if (!a->taxonomy.category) {
|
||||
a->taxonomy = b->taxonomy;
|
||||
memset(&b->taxonomy, 0, sizeof(b->taxonomy));
|
||||
}
|
||||
}
|
||||
|
||||
struct dive_site *find_or_create_dive_site_with_name(const char *name, struct dive_site_table *ds_table)
|
||||
{
|
||||
int i;
|
||||
struct dive_site *ds;
|
||||
for_each_dive_site(i,ds, ds_table) {
|
||||
if (same_string(name, ds->name))
|
||||
break;
|
||||
}
|
||||
if (ds)
|
||||
return ds;
|
||||
return create_dive_site(name, ds_table);
|
||||
}
|
||||
|
||||
void purge_empty_dive_sites(struct dive_site_table *ds_table)
|
||||
{
|
||||
int i, j;
|
||||
struct dive *d;
|
||||
struct dive_site *ds;
|
||||
|
||||
for (i = 0; i < ds_table->nr; i++) {
|
||||
ds = get_dive_site(i, ds_table);
|
||||
if (!dive_site_is_empty(ds))
|
||||
continue;
|
||||
for_each_dive(j, d) {
|
||||
if (d->dive_site == ds)
|
||||
unregister_dive_from_dive_site(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_dive_to_dive_site(struct dive *d, struct dive_site *ds)
|
||||
{
|
||||
int idx;
|
||||
if (!d) {
|
||||
report_info("Warning: add_dive_to_dive_site called with NULL dive");
|
||||
return;
|
||||
}
|
||||
if (!ds) {
|
||||
report_info("Warning: add_dive_to_dive_site called with NULL dive site");
|
||||
return;
|
||||
}
|
||||
if (d->dive_site == ds)
|
||||
return;
|
||||
if (d->dive_site) {
|
||||
report_info("Warning: adding dive that already belongs to a dive site to a different site");
|
||||
unregister_dive_from_dive_site(d);
|
||||
}
|
||||
idx = dive_table_get_insertion_index(&ds->dives, d);
|
||||
add_to_dive_table(&ds->dives, idx, d);
|
||||
d->dive_site = ds;
|
||||
}
|
||||
|
||||
struct dive_site *unregister_dive_from_dive_site(struct dive *d)
|
||||
{
|
||||
struct dive_site *ds = d->dive_site;
|
||||
if (!ds)
|
||||
return NULL;
|
||||
remove_dive(d, &ds->dives);
|
||||
d->dive_site = NULL;
|
||||
return ds;
|
||||
}
|
262
core/divesite.cpp
Normal file
|
@ -0,0 +1,262 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* divesite.c */
|
||||
#include "divesite.h"
|
||||
#include "dive.h"
|
||||
#include "divelist.h"
|
||||
#include "errorhelper.h"
|
||||
#include "format.h"
|
||||
#include "membuffer.h"
|
||||
#include "subsurface-string.h"
|
||||
#include "sha1.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
int divesite_comp_uuid(const dive_site &ds1, const dive_site &ds2)
|
||||
{
|
||||
if (ds1.uuid == ds2.uuid)
|
||||
return 0;
|
||||
return ds1.uuid < ds2.uuid ? -1 : 1;
|
||||
}
|
||||
|
||||
template <typename PRED>
|
||||
dive_site *get_by_predicate(const dive_site_table &ds_table, PRED pred)
|
||||
{
|
||||
auto it = std::find_if(ds_table.begin(), ds_table.end(), pred);
|
||||
return it != ds_table.end() ? it->get() : NULL;
|
||||
}
|
||||
|
||||
dive_site *dive_site_table::get_by_uuid(uint32_t uuid) const
|
||||
{
|
||||
// The table is sorted by uuid
|
||||
auto it = std::lower_bound(begin(), end(), uuid,
|
||||
[] (const auto &ds, auto uuid) { return ds->uuid < uuid; });
|
||||
return it != end() && (*it)->uuid == uuid ? it->get() : NULL;
|
||||
}
|
||||
|
||||
/* there could be multiple sites of the same name - return the first one */
|
||||
dive_site *dive_site_table::get_by_name(const std::string &name) const
|
||||
{
|
||||
return get_by_predicate(*this, [&name](const auto &ds) { return ds->name == name; });
|
||||
}
|
||||
|
||||
/* there could be multiple sites at the same GPS fix - return the first one */
|
||||
dive_site *dive_site_table::get_by_gps(const location_t *loc) const
|
||||
{
|
||||
return get_by_predicate(*this, [loc](const auto &ds) { return ds->location == *loc; });
|
||||
}
|
||||
|
||||
/* to avoid a bug where we have two dive sites with different name and the same GPS coordinates
|
||||
* and first get the gps coordinates (reading a V2 file) and happen to get back "the other" name,
|
||||
* this function allows us to verify if a very specific name/GPS combination already exists */
|
||||
dive_site *dive_site_table::get_by_gps_and_name(const std::string &name, const location_t loc) const
|
||||
{
|
||||
return get_by_predicate(*this, [&name, loc](const auto &ds) { return ds->location == loc &&
|
||||
ds->name == name; });
|
||||
}
|
||||
|
||||
/* find the closest one, no more than distance meters away - if more than one at same distance, pick the first */
|
||||
dive_site *dive_site_table::get_by_gps_proximity(location_t loc, int distance) const
|
||||
{
|
||||
struct dive_site *res = nullptr;
|
||||
unsigned int cur_distance, min_distance = distance;
|
||||
for (const auto &ds: *this) {
|
||||
if (ds->has_gps_location() &&
|
||||
(cur_distance = get_distance(ds->location, loc)) < min_distance) {
|
||||
min_distance = cur_distance;
|
||||
res = ds.get();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
dive_site_table::put_result dive_site_table::register_site(std::unique_ptr<dive_site> ds)
|
||||
{
|
||||
/* If the site doesn't yet have an UUID, create a new one.
|
||||
* Make this deterministic for testing. */
|
||||
if (!ds->uuid) {
|
||||
SHA1 sha;
|
||||
if (!ds->name.empty())
|
||||
sha.update(ds->name);
|
||||
if (!ds->description.empty())
|
||||
sha.update(ds->description);
|
||||
if (!ds->notes.empty())
|
||||
sha.update(ds->notes);
|
||||
ds->uuid = sha.hash_uint32();
|
||||
}
|
||||
|
||||
/* Take care to never have the same uuid twice. This could happen on
|
||||
* reimport of a log where the dive sites have diverged */
|
||||
while (ds->uuid == 0 || get_by_uuid(ds->uuid) != NULL)
|
||||
++ds->uuid;
|
||||
|
||||
return put(std::move(ds));
|
||||
}
|
||||
|
||||
dive_site::dive_site()
|
||||
{
|
||||
}
|
||||
|
||||
dive_site::dive_site(const std::string &name) : name(name)
|
||||
{
|
||||
}
|
||||
|
||||
dive_site::dive_site(const std::string &name, const location_t loc) : name(name), location(loc)
|
||||
{
|
||||
}
|
||||
|
||||
dive_site::dive_site(uint32_t uuid) : uuid(uuid)
|
||||
{
|
||||
}
|
||||
|
||||
dive_site::~dive_site()
|
||||
{
|
||||
}
|
||||
|
||||
/* when parsing, dive sites are identified by uuid */
|
||||
dive_site *dive_site_table::alloc_or_get(uint32_t uuid)
|
||||
{
|
||||
struct dive_site *ds;
|
||||
|
||||
if (uuid && (ds = get_by_uuid(uuid)) != NULL)
|
||||
return ds;
|
||||
|
||||
return register_site(std::make_unique<dive_site>(uuid)).ptr;
|
||||
}
|
||||
|
||||
size_t dive_site::nr_of_dives() const
|
||||
{
|
||||
return dives.size();
|
||||
}
|
||||
|
||||
bool dive_site::is_selected() const
|
||||
{
|
||||
return any_of(dives.begin(), dives.end(),
|
||||
[](dive *dive) { return dive->selected; });
|
||||
}
|
||||
|
||||
bool dive_site::has_gps_location() const
|
||||
{
|
||||
return has_location(&location);
|
||||
}
|
||||
|
||||
/* allocate a new site and add it to the table */
|
||||
dive_site *dive_site_table::create(const std::string &name)
|
||||
{
|
||||
return register_site(std::make_unique<dive_site>(name)).ptr;
|
||||
}
|
||||
|
||||
/* same as before, but with GPS data */
|
||||
dive_site *dive_site_table::create(const std::string &name, const location_t loc)
|
||||
{
|
||||
return register_site(std::make_unique<dive_site>(name, loc)).ptr;
|
||||
}
|
||||
|
||||
/* if all fields are empty, the dive site is pointless */
|
||||
bool dive_site::is_empty() const
|
||||
{
|
||||
return name.empty() &&
|
||||
description.empty() &&
|
||||
notes.empty() &&
|
||||
!has_location(&location);
|
||||
}
|
||||
|
||||
static void merge_string(std::string &a, const std::string &b)
|
||||
{
|
||||
if (b.empty())
|
||||
return;
|
||||
|
||||
if (a == b)
|
||||
return;
|
||||
|
||||
if (a.empty()) {
|
||||
a = b;
|
||||
return;
|
||||
}
|
||||
|
||||
a = format_string_std("(%s) or (%s)", a.c_str(), b.c_str());
|
||||
}
|
||||
|
||||
/* Used to check on import if two dive sites are equivalent.
|
||||
* Since currently no merging is performed, be very conservative
|
||||
* and only consider equal dive sites that are exactly the same.
|
||||
* Taxonomy is not compared, as no taxonomy is generated on
|
||||
* import.
|
||||
*/
|
||||
static bool same(const struct dive_site &a, const struct dive_site &b)
|
||||
{
|
||||
return a.name == b.name
|
||||
&& a.location == b.location
|
||||
&& a.description == b.description
|
||||
&& a.notes == b.notes;
|
||||
}
|
||||
|
||||
dive_site *dive_site_table::get_same(const struct dive_site &site) const
|
||||
{
|
||||
return get_by_predicate(*this, [site](const auto &ds) { return same(*ds, site); });
|
||||
}
|
||||
|
||||
void dive_site::merge(dive_site &b)
|
||||
{
|
||||
if (!has_location(&location)) location = b.location;
|
||||
merge_string(name, b.name);
|
||||
merge_string(notes, b.notes);
|
||||
merge_string(description, b.description);
|
||||
|
||||
if (taxonomy.empty())
|
||||
taxonomy = std::move(b.taxonomy);
|
||||
}
|
||||
|
||||
dive_site *dive_site_table::find_or_create(const std::string &name)
|
||||
{
|
||||
struct dive_site *ds = get_by_name(name);
|
||||
if (ds)
|
||||
return ds;
|
||||
return create(name);
|
||||
}
|
||||
|
||||
void dive_site_table::purge_empty()
|
||||
{
|
||||
for (const auto &ds: *this) {
|
||||
if (!ds->is_empty())
|
||||
continue;
|
||||
while (!ds->dives.empty()) {
|
||||
struct dive *d = ds->dives.back();
|
||||
if (d->dive_site != ds.get()) {
|
||||
report_info("Warning: dive %d registered to wrong dive site in %s", d->number, __func__);
|
||||
ds->dives.pop_back();
|
||||
} else {
|
||||
unregister_dive_from_dive_site(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dive_site::add_dive(struct dive *d)
|
||||
{
|
||||
if (!d) {
|
||||
report_info("Warning: dive_site::add_dive() called with NULL dive");
|
||||
return;
|
||||
}
|
||||
if (d->dive_site == this)
|
||||
return;
|
||||
if (d->dive_site) {
|
||||
report_info("Warning: adding dive that already belongs to a dive site to a different site");
|
||||
unregister_dive_from_dive_site(d);
|
||||
}
|
||||
dives.push_back(d);
|
||||
d->dive_site = this;
|
||||
}
|
||||
|
||||
struct dive_site *unregister_dive_from_dive_site(struct dive *d)
|
||||
{
|
||||
struct dive_site *ds = d->dive_site;
|
||||
if (!ds)
|
||||
return nullptr;
|
||||
auto it = std::find(ds->dives.begin(), ds->dives.end(), d);
|
||||
if (it != ds->dives.end())
|
||||
ds->dives.erase(it);
|
||||
else
|
||||
report_info("Warning: dive not found in divesite table, even though it should be registered there.");
|
||||
d->dive_site = nullptr;
|
||||
return ds;
|
||||
}
|
|
@ -2,88 +2,40 @@
|
|||
#ifndef DIVESITE_H
|
||||
#define DIVESITE_H
|
||||
|
||||
#include "units.h"
|
||||
#include "taxonomy.h"
|
||||
#include "divelist.h"
|
||||
#include "taxonomy.h"
|
||||
#include "units.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
extern "C" {
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
struct dive_site
|
||||
{
|
||||
uint32_t uuid;
|
||||
char *name;
|
||||
struct dive_table dives;
|
||||
uint32_t uuid = 0;
|
||||
std::string name;
|
||||
std::vector<dive *> dives;
|
||||
location_t location;
|
||||
char *description;
|
||||
char *notes;
|
||||
struct taxonomy_data taxonomy;
|
||||
std::string description;
|
||||
std::string notes;
|
||||
taxonomy_data taxonomy;
|
||||
|
||||
dive_site();
|
||||
dive_site(const std::string &name);
|
||||
dive_site(const std::string &name, const location_t loc);
|
||||
dive_site(uint32_t uuid);
|
||||
~dive_site();
|
||||
|
||||
size_t nr_of_dives() const;
|
||||
bool is_selected() const;
|
||||
bool is_empty() const;
|
||||
bool has_gps_location() const;
|
||||
void merge(struct dive_site &b); // Note: b is consumed
|
||||
void add_dive(struct dive *d);
|
||||
};
|
||||
|
||||
typedef struct dive_site_table {
|
||||
int nr, allocated;
|
||||
struct dive_site **dive_sites;
|
||||
} dive_site_table_t;
|
||||
|
||||
static const dive_site_table_t empty_dive_site_table = { 0, 0, (struct dive_site **)0 };
|
||||
|
||||
static inline struct dive_site *get_dive_site(int nr, struct dive_site_table *ds_table)
|
||||
{
|
||||
if (nr >= ds_table->nr || nr < 0)
|
||||
return NULL;
|
||||
return ds_table->dive_sites[nr];
|
||||
}
|
||||
|
||||
/* iterate over each dive site */
|
||||
#define for_each_dive_site(_i, _x, _ds_table) \
|
||||
for ((_i) = 0; ((_x) = get_dive_site(_i, _ds_table)) != NULL; (_i)++)
|
||||
|
||||
int get_divesite_idx(const struct dive_site *ds, struct dive_site_table *ds_table);
|
||||
struct dive_site *get_dive_site_by_uuid(uint32_t uuid, struct dive_site_table *ds_table);
|
||||
void sort_dive_site_table(struct dive_site_table *ds_table);
|
||||
int add_dive_site_to_table(struct dive_site *ds, struct dive_site_table *ds_table);
|
||||
struct dive_site *alloc_or_get_dive_site(uint32_t uuid, struct dive_site_table *ds_table);
|
||||
struct dive_site *alloc_dive_site();
|
||||
struct dive_site *alloc_dive_site_with_name(const char *name);
|
||||
struct dive_site *alloc_dive_site_with_gps(const char *name, const location_t *loc);
|
||||
int nr_of_dives_at_dive_site(struct dive_site *ds);
|
||||
bool is_dive_site_selected(struct dive_site *ds);
|
||||
void free_dive_site(struct dive_site *ds);
|
||||
int unregister_dive_site(struct dive_site *ds);
|
||||
int register_dive_site(struct dive_site *ds);
|
||||
void delete_dive_site(struct dive_site *ds, struct dive_site_table *ds_table);
|
||||
struct dive_site *create_dive_site(const char *name, struct dive_site_table *ds_table);
|
||||
struct dive_site *create_dive_site_with_gps(const char *name, const location_t *, struct dive_site_table *ds_table);
|
||||
struct dive_site *get_dive_site_by_name(const char *name, struct dive_site_table *ds_table);
|
||||
struct dive_site *get_dive_site_by_gps(const location_t *, struct dive_site_table *ds_table);
|
||||
struct dive_site *get_dive_site_by_gps_and_name(const char *name, const location_t *, struct dive_site_table *ds_table);
|
||||
struct dive_site *get_dive_site_by_gps_proximity(const location_t *, int distance, struct dive_site_table *ds_table);
|
||||
struct dive_site *get_same_dive_site(const struct dive_site *);
|
||||
bool dive_site_is_empty(struct dive_site *ds);
|
||||
void copy_dive_site_taxonomy(struct dive_site *orig, struct dive_site *copy);
|
||||
void copy_dive_site(struct dive_site *orig, struct dive_site *copy);
|
||||
void merge_dive_site(struct dive_site *a, struct dive_site *b);
|
||||
unsigned int get_distance(const location_t *loc1, const location_t *loc2);
|
||||
struct dive_site *find_or_create_dive_site_with_name(const char *name, struct dive_site_table *ds_table);
|
||||
void purge_empty_dive_sites(struct dive_site_table *ds_table);
|
||||
void clear_dive_site_table(struct dive_site_table *ds_table);
|
||||
void move_dive_site_table(struct dive_site_table *src, struct dive_site_table *dst);
|
||||
void add_dive_to_dive_site(struct dive *d, struct dive_site *ds);
|
||||
struct dive_site *unregister_dive_from_dive_site(struct dive *d);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
QString constructLocationTags(struct taxonomy_data *taxonomy, bool for_maintab);
|
||||
int divesite_comp_uuid(const dive_site &ds1, const dive_site &ds2);
|
||||
|
||||
/* Make pointer-to-dive_site a "Qt metatype" so that we can pass it through QVariants */
|
||||
#include <QObject>
|
||||
Q_DECLARE_METATYPE(dive_site *);
|
||||
|
||||
#endif
|
||||
|
||||
#endif // DIVESITE_H
|
||||
|
|
|
@ -84,14 +84,14 @@ taxonomy_data reverseGeoLookup(degrees_t latitude, degrees_t longitude)
|
|||
|
||||
QString url;
|
||||
QJsonObject obj;
|
||||
taxonomy_data taxonomy = { 0, 0 };
|
||||
taxonomy_data taxonomy;
|
||||
|
||||
// check the oceans API to figure out the body of water
|
||||
url = geonamesOceanURL.arg(getUiLanguage().section(QRegularExpression("[-_ ]"), 0, 0)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0);
|
||||
obj = doAsyncRESTGetRequest(url, 5000); // 5 secs. timeout
|
||||
QVariantMap oceanName = obj.value("ocean").toVariant().toMap();
|
||||
if (oceanName["name"].isValid())
|
||||
taxonomy_set_category(&taxonomy, TC_OCEAN, qPrintable(oceanName["name"].toString()), taxonomy_origin::GEOCODED);
|
||||
taxonomy_set_category(taxonomy, TC_OCEAN, oceanName["name"].toString().toStdString(), taxonomy_origin::GEOCODED);
|
||||
|
||||
// check the findNearbyPlaces API from geonames - that should give us country, state, city
|
||||
url = geonamesNearbyPlaceNameURL.arg(getUiLanguage().section(QRegularExpression("[-_ ]"), 0, 0)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0);
|
||||
|
@ -110,16 +110,16 @@ taxonomy_data reverseGeoLookup(degrees_t latitude, degrees_t longitude)
|
|||
for (int idx = TC_COUNTRY; idx < TC_NR_CATEGORIES; idx++) {
|
||||
if (firstData[taxonomy_api_names[idx]].isValid()) {
|
||||
QString value = firstData[taxonomy_api_names[idx]].toString();
|
||||
taxonomy_set_category(&taxonomy, (taxonomy_category)idx, qPrintable(value), taxonomy_origin::GEOCODED);
|
||||
taxonomy_set_category(taxonomy, (taxonomy_category)idx, value.toStdString(), taxonomy_origin::GEOCODED);
|
||||
}
|
||||
}
|
||||
const char *l3 = taxonomy_get_value(&taxonomy, TC_ADMIN_L3);
|
||||
const char *lt = taxonomy_get_value(&taxonomy, TC_LOCALNAME);
|
||||
if (empty_string(l3) && !empty_string(lt)) {
|
||||
std::string l3 = taxonomy_get_value(taxonomy, TC_ADMIN_L3);
|
||||
std::string lt = taxonomy_get_value(taxonomy, TC_LOCALNAME);
|
||||
if (!l3.empty() && !lt.empty()) {
|
||||
// basically this means we did get a local name (what we call town), but just like most places
|
||||
// we didn't get an adminName_3 - which in some regions is the actual city that town belongs to,
|
||||
// then we copy the town into the city
|
||||
taxonomy_set_category(&taxonomy, TC_ADMIN_L3, lt, taxonomy_origin::GEOCOPIED);
|
||||
taxonomy_set_category(taxonomy, TC_ADMIN_L3, lt, taxonomy_origin::GEOCOPIED);
|
||||
}
|
||||
} else {
|
||||
report_error("geonames.org did not provide reverse lookup information");
|
||||
|
|
27
core/divesitetable.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef DIVESITETABLE_H
|
||||
#define DIVESITETABLE_H
|
||||
|
||||
#include "owning_table.h"
|
||||
#include "units.h"
|
||||
|
||||
struct dive_site;
|
||||
int divesite_comp_uuid(const dive_site &ds1, const dive_site &ds2);
|
||||
|
||||
class dive_site_table : public sorted_owning_table<dive_site, &divesite_comp_uuid> {
|
||||
public:
|
||||
put_result register_site(std::unique_ptr<dive_site> site); // Creates or changes UUID if duplicate
|
||||
dive_site *get_by_uuid(uint32_t uuid) const;
|
||||
dive_site *alloc_or_get(uint32_t uuid);
|
||||
dive_site *create(const std::string &name);
|
||||
dive_site *create(const std::string &name, const location_t);
|
||||
dive_site *find_or_create(const std::string &name);
|
||||
dive_site *get_by_name(const std::string &name) const;
|
||||
dive_site *get_by_gps(const location_t *) const;
|
||||
dive_site *get_by_gps_and_name(const std::string &name, const location_t) const;
|
||||
dive_site *get_by_gps_proximity(location_t, int distance) const;
|
||||
dive_site *get_same(const struct dive_site &) const;
|
||||
void purge_empty();
|
||||
};
|
||||
|
||||
#endif // DIVESITETABLE_H
|