diff --git a/.clang-format b/.clang-format index bd73416d4..30d424e2c 100644 --- a/.clang-format +++ b/.clang-format @@ -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 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..03ca6fc58 --- /dev/null +++ b/.github/dependabot.yml @@ -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" diff --git a/.github/workflows/android-dockerimage.yml b/.github/workflows/android-dockerimage.yml index 73919e551..1c05f86ee 100644 --- a/.github/workflows/android-dockerimage.yml +++ b/.github/workflows/android-dockerimage.yml @@ -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 diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 07399ddb9..2458489c4 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -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 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bfdd46958..c783acd66 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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 diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml index d066dd924..f9adbeaa0 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity-scan.yml @@ -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 diff --git a/.github/workflows/linux-debian-generic.yml b/.github/workflows/linux-debian-generic.yml index d92fe19b6..741896134 100644 --- a/.github/workflows/linux-debian-generic.yml +++ b/.github/workflows/linux-debian-generic.yml @@ -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 diff --git a/.github/workflows/linux-fedora-35-qt6.yml b/.github/workflows/linux-fedora-35-qt6.yml index 10473fdef..8c2780472 100644 --- a/.github/workflows/linux-fedora-35-qt6.yml +++ b/.github/workflows/linux-fedora-35-qt6.yml @@ -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 diff --git a/.github/workflows/linux-snap.yml b/.github/workflows/linux-snap.yml index 318a687b7..d456db986 100644 --- a/.github/workflows/linux-snap.yml +++ b/.github/workflows/linux-snap.yml @@ -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: | diff --git a/.github/workflows/linux-ubuntu-20.04-qt5-appimage.yml b/.github/workflows/linux-ubuntu-20.04-qt5-appimage.yml index 8f5424739..7fdbb71d0 100644 --- a/.github/workflows/linux-ubuntu-20.04-qt5-appimage.yml +++ b/.github/workflows/linux-ubuntu-20.04-qt5-appimage.yml @@ -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" diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index cbd6cc462..632606a56 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -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 diff --git a/.github/workflows/snap_usns.yml b/.github/workflows/snap_usns.yml index af82d97c5..64c5331a4 100644 --- a/.github/workflows/snap_usns.yml +++ b/.github/workflows/snap_usns.yml @@ -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 }} diff --git a/.github/workflows/windows-mxe-dockerimage.yml b/.github/workflows/windows-mxe-dockerimage.yml index a0478d1c5..5b5c05616 100644 --- a/.github/workflows/windows-mxe-dockerimage.yml +++ b/.github/workflows/windows-mxe-dockerimage.yml @@ -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 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index cc4b03d53..e96ac186b 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -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 diff --git a/.gitignore b/.gitignore index 8603cc7b7..ccf7a70a1 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 28cf83916..031f1f2f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index dfbf4e233..0b53f2741 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/CODINGSTYLE.md b/CODINGSTYLE.md index 884c0cfbd..0551f75fb 100644 --- a/CODINGSTYLE.md +++ b/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 + 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::iterator it = m_trackers.find(when); + std::map::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(sender()); + auto service = std::make_unique(sender()); ``` is easier to read than and conveys the same information as ``` - QLowEnergyService *service = qobject_cast(sender()); + std::unique_ptr service = std::make_unique(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 - ``` - const auto serviceUuids = device.serviceUuids(); - for (QBluetoothUuid id: serviceUuids) { - ``` - 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. + +* 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 + ``` + for (auto [idx, v]: container) { + ... + } + ``` * 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 diff --git a/Documentation/images/CCR_b1.jpg b/Documentation/images/CCR_b1.jpg deleted file mode 100644 index 6c6e477a9..000000000 Binary files a/Documentation/images/CCR_b1.jpg and /dev/null differ diff --git a/Documentation/images/CCR_b1.png b/Documentation/images/CCR_b1.png new file mode 100644 index 000000000..dd94443bf Binary files /dev/null and b/Documentation/images/CCR_b1.png differ diff --git a/Documentation/images/CCR_b2.jpg b/Documentation/images/CCR_b2.jpg deleted file mode 100644 index 5261c832c..000000000 Binary files a/Documentation/images/CCR_b2.jpg and /dev/null differ diff --git a/Documentation/images/CCR_b2.png b/Documentation/images/CCR_b2.png new file mode 100644 index 000000000..4f410c517 Binary files /dev/null and b/Documentation/images/CCR_b2.png differ diff --git a/Documentation/images/CCR_b3.jpg b/Documentation/images/CCR_b3.jpg deleted file mode 100644 index 1cad5fe8f..000000000 Binary files a/Documentation/images/CCR_b3.jpg and /dev/null differ diff --git a/Documentation/images/CCR_b3.png b/Documentation/images/CCR_b3.png new file mode 100644 index 000000000..7ffe7512b Binary files /dev/null and b/Documentation/images/CCR_b3.png differ diff --git a/Documentation/images/PlannerWindow1.jpg b/Documentation/images/PlannerWindow1.jpg deleted file mode 100644 index e029e4c96..000000000 Binary files a/Documentation/images/PlannerWindow1.jpg and /dev/null differ diff --git a/Documentation/images/PlannerWindow1.png b/Documentation/images/PlannerWindow1.png new file mode 100644 index 000000000..08968ea09 Binary files /dev/null and b/Documentation/images/PlannerWindow1.png differ diff --git a/Documentation/images/Planner_CCR.jpg b/Documentation/images/Planner_CCR.jpg deleted file mode 100644 index 17c852c5b..000000000 Binary files a/Documentation/images/Planner_CCR.jpg and /dev/null differ diff --git a/Documentation/images/Planner_CCR.png b/Documentation/images/Planner_CCR.png new file mode 100644 index 000000000..d2346ad06 Binary files /dev/null and b/Documentation/images/Planner_CCR.png differ diff --git a/Documentation/images/Planner_OC_deco.jpg b/Documentation/images/Planner_OC_deco.jpg deleted file mode 100644 index a201514f0..000000000 Binary files a/Documentation/images/Planner_OC_deco.jpg and /dev/null differ diff --git a/Documentation/images/Planner_OC_deco.png b/Documentation/images/Planner_OC_deco.png new file mode 100644 index 000000000..d3c52b8b3 Binary files /dev/null and b/Documentation/images/Planner_OC_deco.png differ diff --git a/Documentation/images/Planner_OC_gas_for_CCR.png b/Documentation/images/Planner_OC_gas_for_CCR.png new file mode 100644 index 000000000..92defc5cb Binary files /dev/null and b/Documentation/images/Planner_OC_gas_for_CCR.png differ diff --git a/Documentation/images/Planner_OC_rec1.jpg b/Documentation/images/Planner_OC_rec1.jpg deleted file mode 100644 index 818c0a32c..000000000 Binary files a/Documentation/images/Planner_OC_rec1.jpg and /dev/null differ diff --git a/Documentation/images/Planner_OC_rec1.png b/Documentation/images/Planner_OC_rec1.png new file mode 100644 index 000000000..168f372d9 Binary files /dev/null and b/Documentation/images/Planner_OC_rec1.png differ diff --git a/Documentation/images/Planner_OC_rec2.jpg b/Documentation/images/Planner_OC_rec2.jpg deleted file mode 100644 index 0ff476849..000000000 Binary files a/Documentation/images/Planner_OC_rec2.jpg and /dev/null differ diff --git a/Documentation/images/Planner_OC_rec2.png b/Documentation/images/Planner_OC_rec2.png new file mode 100644 index 000000000..3d3af5151 Binary files /dev/null and b/Documentation/images/Planner_OC_rec2.png differ diff --git a/Documentation/images/Planner_issues.png b/Documentation/images/Planner_issues.png new file mode 100644 index 000000000..56a9a78e6 Binary files /dev/null and b/Documentation/images/Planner_issues.png differ diff --git a/Documentation/images/Planner_pSCR.jpg b/Documentation/images/Planner_pSCR.jpg deleted file mode 100644 index 308ed8489..000000000 Binary files a/Documentation/images/Planner_pSCR.jpg and /dev/null differ diff --git a/Documentation/images/Planner_pSCR.png b/Documentation/images/Planner_pSCR.png new file mode 100644 index 000000000..8b0124d6d Binary files /dev/null and b/Documentation/images/Planner_pSCR.png differ diff --git a/Documentation/images/planner1.jpg b/Documentation/images/planner1.jpg deleted file mode 100755 index 2d03fa267..000000000 Binary files a/Documentation/images/planner1.jpg and /dev/null differ diff --git a/Documentation/images/planner1.png b/Documentation/images/planner1.png new file mode 100644 index 000000000..942569fc9 Binary files /dev/null and b/Documentation/images/planner1.png differ diff --git a/Documentation/images/planner2.png b/Documentation/images/planner2.png new file mode 100644 index 000000000..73a622633 Binary files /dev/null and b/Documentation/images/planner2.png differ diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index ef82d8000..8feea6b7f 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -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 @@ -3870,7 +3864,7 @@ image::images/PlannerWindow1.jpg["FIGURE: Dive planner startup window",align="ce O~2~% according to the depth set. Set to ''*'' to calculate the best O~2~% for the dive maximum depth. ** MND: the gas Maximum Narcotic Depth (MND). Automatically calculated based on the Best Mix END preference (default 30m / 98 ft). Editing this field will modify the He% according to the depth set. - Set to ''*'' to calculate the best He% for the dive maximum depth. Depending on the checkbox, oxygen + Set to ''*'' to calculate the best He% for the dive maximum depth. Depending on the checkbox, oxygen is considered narcotic (the END is used) or not (the EAD is used). - The profile of the planned dive can be created in two ways: @@ -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,14 +4296,9 @@ 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 +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 a different number) and set the "Deco switch value" to the depth at which you want to set the new setpoint. This will make the planner stop at the specified depth and use the new setpoint from @@ -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 diff --git a/INSTALL.md b/INSTALL.md index 89f314f4f..30ba828fb 100644 --- a/INSTALL.md +++ b/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 diff --git a/Subsurface-mobile.pro b/Subsurface-mobile.pro index 88e1d3ae2..e09a7a61d 100644 --- a/Subsurface-mobile.pro +++ b/Subsurface-mobile.pro @@ -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 \ diff --git a/android-mobile/build.gradle b/android-mobile/build.gradle index 64555299c..bc4bb27bc 100644 --- a/android-mobile/build.gradle +++ b/android-mobile/build.gradle @@ -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 { diff --git a/backend-shared/exportfuncs.cpp b/backend-shared/exportfuncs.cpp index 7bc545ae5..c6afa6dc3 100644 --- a/backend-shared/exportfuncs.cpp +++ b/backend-shared/exportfuncs.cpp @@ -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 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(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(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 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 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; } diff --git a/cli-downloader.cpp b/cli-downloader.cpp index 3e38a2e9c..0cf624d4e 100644 --- a/cli-downloader.cpp +++ b/cli-downloader.cpp @@ -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 diff --git a/cmake/Modules/pkgconfig_helper.cmake b/cmake/Modules/pkgconfig_helper.cmake index f53a1064d..443505e81 100644 --- a/cmake/Modules/pkgconfig_helper.cmake +++ b/cmake/Modules/pkgconfig_helper.cmake @@ -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}) diff --git a/commands/command.cpp b/commands/command.cpp index 589bb9599..162247d75 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -13,9 +13,9 @@ namespace Command { // Dive-list related commands -void addDive(dive *d, bool autogroup, bool newNumber) +void addDive(std::unique_ptr 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 &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) diff --git a/commands/command.h b/commands/command.h index e633d3a18..826557f3b 100644 --- a/commands/command.h +++ b/commands/command.h @@ -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 #include @@ -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 d, bool autogroup, bool newNumber); void importDives(struct divelog *log, int flags, const QString &source); // The tables are consumed! void deleteDive(const QVector &divesToDelete); void shiftTime(const std::vector &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 &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 pics; + std::vector pics; }; void setPictureOffset(dive *d, const QString &filename, offset_t offset); void removePictures(const std::vector &pictures); diff --git a/commands/command_base.cpp b/commands/command_base.cpp index a33c31b86..447ffc9b9 100644 --- a/commands/command_base.cpp +++ b/commands/command_base.cpp @@ -66,7 +66,7 @@ QString diveNumberOrDate(struct dive *d) QString getListOfDives(const std::vector &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) { diff --git a/commands/command_base.h b/commands/command_base.h index dacaee89f..d8bfaf8e8 100644 --- a/commands/command_base.h +++ b/commands/command_base.h @@ -7,7 +7,6 @@ #include "core/divesite.h" #include "core/trip.h" #include "core/dive.h" -#include "core/owning_ptrs.h" #include #include // 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 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 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 dPtr; // Initialize to null-state: not owning any dive. +// std::unique_ptr 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 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 v: // Define an empty vector of owning pointers. +// std::vector> 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 stdToQt(const std::vector &v) #endif } +template +std::vector qtToStd(const QVector &v) +{ + return std::vector(v.begin(), v.end()); +} + // We put everything in a namespace, so that we can shorten names without polluting the global namespace namespace Command { diff --git a/commands/command_device.cpp b/commands/command_device.cpp index 540e80adf..b481f5bd2 100644 --- a/commands/command_device.cpp +++ b/commands/command_device.cpp @@ -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(index) >= divelog.devices.size()) return; - std::swap(dev->nickName, nickname); + std::swap(divelog.devices[index].nickName, nickname); emit diveListNotifier.deviceEdited(); } diff --git a/commands/command_divelist.cpp b/commands/command_divelist.cpp index 6ca13f5f9..c7ac90d1d 100644 --- a/commands/command_divelist.cpp +++ b/commands/command_divelist.cpp @@ -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 +#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 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 &tripsToAdd) +DiveToAdd DiveListBase::removeDive(struct dive *d, std::vector> &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 &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> &dives, Function // Sort lexicographically by trip then according to the dive_less_than() function. std::sort(dives.begin(), dives.end(), [](const std::pair &e1, const std::pair &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> &dives, Function DivesAndTripsToAdd DiveListBase::removeDives(DivesAndSitesToRemove &divesAndSitesToDelete) { std::vector divesToAdd; - std::vector tripsToAdd; - std::vector sitesToAdd; + std::vector> tripsToAdd; + std::vector> 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 &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 &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 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 &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 &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> &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> &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 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 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 &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 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 &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 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. - // We have to clear the flag, as selections will be managed - // on dive-addition. + 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 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 dives = stdToQt(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 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 &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 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 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 newDives) +SplitDivesBase::SplitDivesBase(dive *d, std::array, 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 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 doSplitDives(const dive *d, duration_t time) +static std::array, 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 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 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 &dives) +MergeDives::MergeDives(const QVector &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 &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 &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 &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 &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(divelog.dives->dives[i], newnr)); + divesToRenumber.append(QPair(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(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); diff --git a/commands/command_divelist.h b/commands/command_divelist.h index 43713fd58..0ce5f7284 100644 --- a/commands/command_divelist.h +++ b/commands/command_divelist.h @@ -15,16 +15,16 @@ namespace Command { // This helper structure describes a dive that we want to add. struct DiveToAdd { - OwningDivePtr 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 + std::unique_ptr 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 }; // Multiple trips, dives and dive sites that have to be added for a command struct DivesAndTripsToAdd { std::vector dives; - std::vector trips; - std::vector sites; + std::vector> trips; + std::vector> sites; }; // Dives and sites that have to be removed for a command @@ -48,7 +48,7 @@ struct DiveToTrip struct DivesToTrip { std::vector divesToMove; // If dive_trip is null, remove from trip - std::vector tripsToAdd; + std::vector> 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 &tripsToAdd); + DiveToAdd removeDive(struct dive *d, std::vector> &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 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 sitesToAdd; + std::vector> sitesToAdd; std::vector> filterPresetsToAdd; @@ -133,7 +133,7 @@ private: // For redo DivesAndSitesToRemove divesToDelete; - std::vector tripsToAdd; + std::vector> tripsToAdd; DivesAndTripsToAdd divesToAdd; }; @@ -196,7 +196,7 @@ struct MergeTrips : public TripBase { class SplitDivesBase : public DiveListBase { protected: - SplitDivesBase(dive *old, std::array newDives); + SplitDivesBase(dive *old, std::array, 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 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> divesToRenumber; + dive_site *site; + location_t location; }; } // namespace Command diff --git a/commands/command_divesite.cpp b/commands/command_divesite.cpp index b89bb0b86..fd706007c 100644 --- a/commands/command_divesite.cpp +++ b/commands/command_divesite.cpp @@ -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 addDiveSites(std::vector &sites) +static std::vector addDiveSites(std::vector> &sites) { std::vector res; QVector changedDives; res.reserve(sites.size()); - for (OwningDiveSitePtr &ds: sites) { + for (std::unique_ptr &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 addDiveSites(std::vector &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 removeDiveSites(std::vector &sites) +static std::vector> removeDiveSites(std::vector &sites) { - std::vector res; + std::vector> res; QVector 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 removeDiveSites(std::vector & 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()); + 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.emplace_back(new_ds); + sitesToAdd.push_back(std::move(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 &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 &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 &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); } diff --git a/commands/command_divesite.h b/commands/command_divesite.h index c81397410..86ca1568d 100644 --- a/commands/command_divesite.h +++ b/commands/command_divesite.h @@ -31,13 +31,13 @@ private: std::vector sitesToRemove; // For redo - std::vector sitesToAdd; + std::vector> 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 sitesToRemove; // For redo - std::vector sitesToAdd; + std::vector> sitesToAdd; }; class DeleteDiveSites : public Base { @@ -62,7 +62,7 @@ private: std::vector sitesToRemove; // For undo - std::vector sitesToAdd; + std::vector> sitesToAdd; }; class PurgeUnusedDiveSites : public Base { @@ -77,7 +77,7 @@ private: std::vector sitesToRemove; // For undo - std::vector sitesToAdd; + std::vector> 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 sitesToRemove; // For undo - std::vector sitesToAdd; + std::vector> sitesToAdd; }; class ApplyGPSFixes : public Base { @@ -183,7 +183,7 @@ private: std::vector sitesToRemove; // For redo - std::vector sitesToAdd; + std::vector> sitesToAdd; // For redo and undo struct SiteAndLocation { diff --git a/commands/command_edit.cpp b/commands/command_edit.cpp index 0fb4b5992..d4d8a1c5c 100644 --- a/commands/command_edit.cpp +++ b/commands/command_edit.cpp @@ -3,8 +3,10 @@ #include "command_edit.h" #include "core/divelist.h" #include "core/divelog.h" +#include "core/event.h" #include "core/fulltext.h" -#include "core/qthelper.h" // for copy_qstring +#include "core/qthelper.h" +#include "core/range.h" #include "core/sample.h" #include "core/selection.h" #include "core/subsurface-string.h" @@ -41,17 +43,16 @@ T EditDefaultSetter::data(struct dive *d) const return d->*PTR; } -template +template void EditStringSetter::set(struct dive *d, QString v) const { - free(d->*PTR); - d->*PTR = copy_qstring(v); + d->*PTR = v.toStdString(); } -template +template QString EditStringSetter::data(struct dive *d) const { - return QString(d->*PTR); + return QString::fromStdString(d->*PTR); } static std::vector getDives(bool currentDiveOnly) @@ -61,11 +62,9 @@ static std::vector getDives(bool currentDiveOnly) : std::vector { }; std::vector res; - struct dive *d; - int i; - for_each_dive (i, d) { + for (auto &d: divelog.dives) { if (d->selected) - res.push_back(d); + res.push_back(d.get()); } return res; } @@ -150,7 +149,7 @@ void EditBase::undo() for (dive *d: dives) { set(d, value); fulltext_register(d); // Update the fulltext cache - invalidate_dive_cache(d); // Ensure that dive is written in git_save() + d->invalidate_cache(); // Ensure that dive is written in git_save() } std::swap(old, value); @@ -257,7 +256,7 @@ void EditWaterTemp::set(struct dive *d, int value) const d->watertemp.mkelvin = value > 0 ? (uint32_t)value : 0u; // re-populate the temperatures - easiest way to do this is by calling fixup_dive - fixup_dive(d); + divelog.dives.fixup_dive(*d); } int EditWaterTemp::data(struct dive *d) const @@ -305,11 +304,11 @@ QString EditAtmPress::fieldName() const // ***** Duration ***** void EditDuration::set(struct dive *d, int value) const { - d->dc.duration.seconds = value; - d->duration = d->dc.duration; - d->dc.meandepth.mm = 0; - d->dc.samples = 0; - fake_dc(&d->dc); + d->dcs[0].duration.seconds = value; + d->duration = d->dcs[0].duration; + d->dcs[0].meandepth = 0_m; + d->dcs[0].samples.clear(); + fake_dc(&d->dcs[0]); } int EditDuration::data(struct dive *d) const @@ -325,11 +324,11 @@ QString EditDuration::fieldName() const // ***** Depth ***** void EditDepth::set(struct dive *d, int value) const { - d->dc.maxdepth.mm = value; - d->maxdepth = d->dc.maxdepth; - d->dc.meandepth.mm = 0; - d->dc.samples = 0; - fake_dc(&d->dc); + d->dcs[0].maxdepth.mm = value; + d->maxdepth = d->dcs[0].maxdepth; + d->dcs[0].meandepth = 0_m; + d->dcs[0].samples.clear(); + fake_dc(&d->dcs[0]); } int EditDepth::data(struct dive *d) const @@ -346,7 +345,7 @@ QString EditDepth::fieldName() const void EditDiveSite::set(struct dive *d, struct dive_site *dive_site) const { unregister_dive_from_dive_site(d); - add_dive_to_dive_site(d, dive_site); + dive_site->add_dive(d); } struct dive_site *EditDiveSite::data(struct dive *d) const @@ -374,28 +373,25 @@ void EditDiveSite::redo() EditDiveSite::undo(); // Undo and redo do the same } -static struct dive_site *createDiveSite(const QString &name) +static struct dive_site *createDiveSite(const std::string &name) { - struct dive_site *ds = alloc_dive_site(); - struct dive_site *old = current_dive ? current_dive->dive_site : nullptr; - if (old) { - copy_dive_site(old, ds); - free(ds->name); // Free name, as we will overwrite it with our own version - } + struct dive_site *ds = new dive_site; + if (current_dive && current_dive->dive_site) + *ds = *current_dive->dive_site; // If the current dive has a location, use that as location for the new dive site if (current_dive) { - location_t loc = dive_get_gps_location(current_dive); + location_t loc = current_dive->get_gps_location(); if (has_location(&loc)) ds->location = loc; } - ds->name = copy_qstring(name); + ds->name = name; return ds; } EditDiveSiteNew::EditDiveSiteNew(const QString &newName, bool currentDiveOnly) : - EditDiveSite(createDiveSite(newName), currentDiveOnly), + EditDiveSite(createDiveSite(newName.toStdString()), currentDiveOnly), diveSiteToAdd(value), diveSiteToRemove(nullptr) { @@ -404,17 +400,17 @@ EditDiveSiteNew::EditDiveSiteNew(const QString &newName, bool currentDiveOnly) : void EditDiveSiteNew::undo() { EditDiveSite::undo(); - int idx = unregister_dive_site(diveSiteToRemove); - diveSiteToAdd.reset(diveSiteToRemove); - emit diveListNotifier.diveSiteDeleted(diveSiteToRemove, idx); // Inform frontend of removed dive site. + auto res = divelog.sites.pull(diveSiteToRemove); + diveSiteToAdd = std::move(res.ptr); + emit diveListNotifier.diveSiteDeleted(diveSiteToRemove, res.idx); // Inform frontend of removed dive site. diveSiteToRemove = nullptr; } void EditDiveSiteNew::redo() { - diveSiteToRemove = diveSiteToAdd.get(); - int idx = register_dive_site(diveSiteToAdd.release()); // Return ownership to backend. - emit diveListNotifier.diveSiteAdded(diveSiteToRemove, idx); // Inform frontend of new dive site. + auto res = divelog.sites.register_site(std::move(diveSiteToAdd)); // Return ownership to backend. + diveSiteToRemove = res.ptr; + emit diveListNotifier.diveSiteAdded(diveSiteToRemove, res.idx); // Inform frontend of new dive site. EditDiveSite::redo(); } @@ -438,13 +434,13 @@ EditMode::EditMode(int indexIn, int newValue, bool currentDiveOnly) void EditMode::set(struct dive *d, int i) const { - get_dive_dc(d, index)->divemode = (enum divemode_t)i; - update_setpoint_events(d, get_dive_dc(d, index)); + d->get_dc(index)->divemode = (enum divemode_t)i; + update_setpoint_events(d, d->get_dc(index)); } int EditMode::data(struct dive *d) const { - return get_dive_dc(d, index)->divemode; + return d->get_dc(index)->divemode; } QString EditMode::fieldName() const @@ -544,7 +540,7 @@ void EditTagsBase::undo() } set(d, tags); fulltext_register(d); // Update the fulltext cache - invalidate_dive_cache(d); // Ensure that dive is written in git_save() + d->invalidate_cache(); // Ensure that dive is written in git_save() } std::swap(tagsToAdd, tagsToRemove); @@ -565,18 +561,17 @@ void EditTagsBase::redo() QStringList EditTags::data(struct dive *d) const { QStringList res; - for (const struct tag_entry *tag = d->tag_list; tag; tag = tag->next) - res.push_back(QString::fromStdString(tag->tag->name)); + for (const divetag *tag: d->tags) + res.push_back(QString::fromStdString(tag->name)); return res; } void EditTags::set(struct dive *d, const QStringList &v) const { - taglist_free(d->tag_list); - d->tag_list = NULL; + d->tags.clear(); for (const QString &tag: v) - taglist_add_tag(&d->tag_list, qPrintable(tag)); - taglist_cleanup(&d->tag_list); + taglist_add_tag(d->tags, tag.toStdString()); + taglist_cleanup(d->tags); } QString EditTags::fieldName() const @@ -587,14 +582,13 @@ QString EditTags::fieldName() const // ***** Buddies ***** QStringList EditBuddies::data(struct dive *d) const { - return stringToList(d->buddy); + return stringToList(QString::fromStdString(d->buddy)); } void EditBuddies::set(struct dive *d, const QStringList &v) const { QString text = v.join(", "); - free(d->buddy); - d->buddy = copy_qstring(text); + d->buddy = text.toStdString(); } QString EditBuddies::fieldName() const @@ -605,14 +599,13 @@ QString EditBuddies::fieldName() const // ***** DiveGuide ***** QStringList EditDiveGuide::data(struct dive *d) const { - return stringToList(d->diveguide); + return stringToList(QString::fromStdString(d->diveguide)); } void EditDiveGuide::set(struct dive *d, const QStringList &v) const { QString text = v.join(", "); - free(d->diveguide); - d->diveguide = copy_qstring(text); + d->diveguide = text.toStdString(); } QString EditDiveGuide::fieldName() const @@ -620,52 +613,51 @@ QString EditDiveGuide::fieldName() const return Command::Base::tr("dive guide"); } -static void swapCandQString(QString &q, char *&c) +template +ptrdiff_t comp_ptr(T *v1, T *v2) { - QString tmp(c); - free(c); - c = copy_qstring(q); - q = std::move(tmp); + return v1 - v2; } -PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dIn), - tags(nullptr) +PasteState::PasteState(dive &d, const dive_paste_data &data, std::vector &dive_sites_changed) : d(d) { - memset(&cylinders, 0, sizeof(cylinders)); - memset(&weightsystems, 0, sizeof(weightsystems)); - if (what.notes) - notes = data->notes; - if (what.diveguide) - diveguide = data->diveguide; - if (what.buddy) - buddy = data->buddy; - if (what.suit) - suit = data->suit; - if (what.rating) - rating = data->rating; - if (what.visibility) - visibility = data->visibility; - if (what.wavesize) - wavesize = data->wavesize; - if (what.current) - current = data->current; - if (what.surge) - surge = data->surge; - if (what.chill) - chill = data->chill; - if (what.divesite) - divesite = data->dive_site; - if (what.tags) - tags = taglist_copy(data->tag_list); - if (what.cylinders) { - copy_cylinders(&data->cylinders, &cylinders); + notes = data.notes; + diveguide = data.diveguide; + buddy = data.buddy; + suit = data.suit; + rating = data.rating; + visibility = data.visibility; + wavesize = data.wavesize; + current = data.current; + surge = data.surge; + chill = data.chill; + if (data.divesite.has_value()) { + if (data.divesite) { + // In the undo system, we can turn the uuid into a pointer, + // because everything is serialized. + dive_site *ds = divelog.sites.get_by_uuid(*data.divesite); + if (ds) + divesite = ds; + } else { + divesite = nullptr; + } + } + if (divesite.has_value() && *divesite != d.dive_site) { + if (d.dive_site) + range_insert_sorted_unique(dive_sites_changed, d.dive_site, comp_ptr); // Use <=> once on C++20 + if (*divesite) + range_insert_sorted_unique(dive_sites_changed, *divesite, comp_ptr); // Use <=> once on C++20 + } + tags = data.tags; + if (data.cylinders.has_value()) { + cylinders = data.cylinders; // Paste cylinders is "special": // 1) For cylinders that exist in the destination dive we keep the gas-mix and pressures. // 2) For cylinders that do not yet exist in the destination dive, we set the pressures to 0, i.e. unset. // Moreover, for these we set the manually_added flag, because they weren't downloaded from a DC. - for (int i = 0; i < d->cylinders.nr && i < cylinders.nr; ++i) { - const cylinder_t &src = *get_cylinder(d, i); - cylinder_t &dst = cylinders.cylinders[i]; + for (size_t i = 0; i < data.cylinders->size() && i < cylinders->size(); ++i) { + const cylinder_t &src = (*data.cylinders)[i]; + cylinder_t &dst = (*cylinders)[i]; dst.gasmix = src.gasmix; dst.start = src.start; dst.end = src.end; @@ -679,74 +671,71 @@ PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dI dst.bestmix_o2 = src.bestmix_o2; dst.bestmix_he = src.bestmix_he; } - for (int i = d->cylinders.nr; i < cylinders.nr; ++i) { - cylinder_t &cyl = cylinders.cylinders[i]; - cyl.start.mbar = 0; - cyl.end.mbar = 0; - cyl.sample_start.mbar = 0; - cyl.sample_end.mbar = 0; + for (size_t i = data.cylinders->size(); i < cylinders->size(); ++i) { + cylinder_t &cyl = (*cylinders)[i]; + cyl.start = 0_bar; + cyl.end = 0_bar; + cyl.sample_start = 0_bar; + cyl.sample_end = 0_bar; cyl.manually_added = true; } } - if (what.weights) - copy_weights(&data->weightsystems, &weightsystems); - if (what.number) - number = data->number; - if (what.when) - when = data->when; + weightsystems = data.weights; + number = data.number; + when = data.when; } PasteState::~PasteState() { - taglist_free(tags); - clear_cylinder_table(&cylinders); - clear_weightsystem_table(&weightsystems); - free(weightsystems.weightsystems); } -void PasteState::swap(dive_components what) +void PasteState::swap() { - if (what.notes) - swapCandQString(notes, d->notes); - if (what.diveguide) - swapCandQString(diveguide, d->diveguide); - if (what.buddy) - swapCandQString(buddy, d->buddy); - if (what.suit) - swapCandQString(suit, d->suit); - if (what.rating) - std::swap(rating, d->rating); - if (what.visibility) - std::swap(visibility, d->visibility); - if (what.wavesize) - std::swap(wavesize, d->wavesize); - if (what.current) - std::swap(current, d->current); - if (what.surge) - std::swap(surge, d->surge); - if (what.chill) - std::swap(chill, d->chill); - if (what.divesite) - std::swap(divesite, d->dive_site); - if (what.tags) - std::swap(tags, d->tag_list); - if (what.cylinders) - std::swap(cylinders, d->cylinders); - if (what.weights) - std::swap(weightsystems, d->weightsystems); - if (what.number) - std::swap(number, d->number); - if (what.when) - std::swap(when, d->when); + if (notes.has_value()) + std::swap(*notes, d.notes); + if (diveguide.has_value()) + std::swap(*diveguide, d.diveguide); + if (buddy.has_value()) + std::swap(*buddy, d.buddy); + if (suit.has_value()) + std::swap(*suit, d.suit); + if (rating.has_value()) + std::swap(*rating, d.rating); + if (visibility.has_value()) + std::swap(*visibility, d.visibility); + if (wavesize.has_value()) + std::swap(*wavesize, d.wavesize); + if (current.has_value()) + std::swap(*current, d.current); + if (surge.has_value()) + std::swap(*surge, d.surge); + if (chill.has_value()) + std::swap(*chill, d.chill); + if (divesite.has_value() && *divesite != d.dive_site) { + auto old_ds = unregister_dive_from_dive_site(&d); + if (*divesite) + (*divesite)->add_dive(&d); + divesite = old_ds; + } + if (tags.has_value()) + std::swap(*tags, d.tags); + if (cylinders.has_value()) + std::swap(*cylinders, d.cylinders); + if (weightsystems.has_value()) + std::swap(*weightsystems, d.weightsystems); + if (number.has_value()) + std::swap(*number, d.number); + if (when.has_value()) + std::swap(*when, d.when); } // ***** Paste ***** -PasteDives::PasteDives(const dive *data, dive_components whatIn) : what(whatIn) +PasteDives::PasteDives(const dive_paste_data &data) { std::vector selection = getDiveSelection(); dives.reserve(selection.size()); for (dive *d: selection) - dives.emplace_back(d, data, what); + dives.emplace_back(*d, data, dive_sites_changed); setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Paste onto %n dive(s)", "", dives.size())).arg(getListOfDives(selection))); } @@ -761,32 +750,35 @@ void PasteDives::undo() QVector divesToNotify; // Remember dives so that we can send signals later divesToNotify.reserve(dives.size()); for (PasteState &state: dives) { - divesToNotify.push_back(state.d); - state.swap(what); - invalidate_dive_cache(state.d); // Ensure that dive is written in git_save() + divesToNotify.push_back(&state.d); + state.swap(); + state.d.invalidate_cache(); // Ensure that dive is written in git_save() } - // Send signals. + // Send signals. We use the first data item to determine what changed DiveField fields(DiveField::NONE); - fields.notes = what.notes; - fields.diveguide = what.diveguide; - fields.buddy = what.buddy; - fields.suit = what.suit; - fields.rating = what.rating; - fields.visibility = what.visibility; - fields.wavesize = what.wavesize; - fields.current = what.current; - fields.surge = what.surge; - fields.chill = what.chill; - fields.divesite = what.divesite; - fields.tags = what.tags; - fields.datetime = what.when; - fields.nr = what.number; + const PasteState &state = dives[0]; + fields.notes = state.notes.has_value(); + fields.diveguide = state.diveguide.has_value(); + fields.buddy = state.buddy.has_value(); + fields.suit = state.suit.has_value(); + fields.rating = state.rating.has_value(); + fields.visibility = state.visibility.has_value(); + fields.wavesize = state.wavesize.has_value(); + fields.current = state.current.has_value(); + fields.surge = state.surge.has_value(); + fields.chill = state.chill.has_value(); + fields.divesite = !dive_sites_changed.empty(); + fields.tags = state.tags.has_value(); + fields.datetime = state.when.has_value(); + fields.nr = state.number.has_value(); emit diveListNotifier.divesChanged(divesToNotify, fields); - if (what.cylinders) + if (state.cylinders.has_value()) emit diveListNotifier.cylindersReset(divesToNotify); - if (what.weights) + if (state.weightsystems.has_value()) emit diveListNotifier.weightsystemsReset(divesToNotify); + for (dive_site *ds: dive_sites_changed) + emit diveListNotifier.diveSiteDivesChanged(ds); } // Redo and undo do the same @@ -798,42 +790,32 @@ void PasteDives::redo() // ***** ReplanDive ***** ReplanDive::ReplanDive(dive *source) : d(current_dive), when(0), - maxdepth({0}), - meandepth({0}), - dc({ 0 }), - notes(nullptr), - surface_pressure({0}), - duration({0}), salinity(0) { - memset(&cylinders, 0, sizeof(cylinders)); if (!d) return; // Fix source. Things might be inconsistent after modifying the profile. - source->maxdepth.mm = source->dc.maxdepth.mm = 0; - fixup_dive(source); + source->maxdepth = source->dcs[0].maxdepth = 0_m; + divelog.dives.fixup_dive(*source); when = source->when; maxdepth = source->maxdepth; meandepth = source->meandepth; - notes = copy_string(source->notes); + notes = source->notes; duration = source->duration; salinity = source->salinity; surface_pressure = source->surface_pressure; // This resets the dive computers and cylinders of the source dive, avoiding deep copies. std::swap(source->cylinders, cylinders); - std::swap(source->dc, dc); + std::swap(source->dcs[0], dc); setText(Command::Base::tr("Replan dive")); } ReplanDive::~ReplanDive() { - clear_cylinder_table(&cylinders); - free_dive_dcs(&dc); - free(notes); } bool ReplanDive::workToBeDone() @@ -847,13 +829,13 @@ void ReplanDive::undo() std::swap(d->maxdepth, maxdepth); std::swap(d->meandepth, meandepth); std::swap(d->cylinders, cylinders); - std::swap(d->dc, dc); + std::swap(d->dcs[0], dc); std::swap(d->notes, notes); std::swap(d->surface_pressure, surface_pressure); std::swap(d->duration, duration); std::swap(d->salinity, salinity); - fixup_dive(d); - invalidate_dive_cache(d); // Ensure that dive is written in git_save() + divelog.dives.fixup_dive(*d); + d->invalidate_cache(); // Ensure that dive is written in git_save() QVector divesToNotify = { d }; // Note that we have to emit cylindersReset before divesChanged, because the divesChanged @@ -884,14 +866,9 @@ QString editProfileTypeToString(EditProfileType type, int count) } EditProfile::EditProfile(const dive *source, int dcNr, EditProfileType type, int count) : d(current_dive), - dcNr(dcNr), - maxdepth({0}), - meandepth({0}), - dcmaxdepth({0}), - duration({0}), - dc({ 0 }) + dcNr(dcNr) { - const struct divecomputer *sdc = get_dive_dc_const(source, dcNr); + const struct divecomputer *sdc = source->get_dc(dcNr); if (!sdc) d = nullptr; // Signal that we refuse to do anything. if (!d) @@ -902,15 +879,14 @@ EditProfile::EditProfile(const dive *source, int dcNr, EditProfileType type, int meandepth = source->meandepth; duration = source->duration; - copy_samples(sdc, &dc); - copy_events(sdc, &dc); + dc.samples = sdc->samples; + dc.events = sdc->events; setText(editProfileTypeToString(type, count) + " " + diveNumberOrDate(d)); } EditProfile::~EditProfile() { - free_dive_dcs(&dc); } bool EditProfile::workToBeDone() @@ -920,19 +896,17 @@ bool EditProfile::workToBeDone() void EditProfile::undo() { - struct divecomputer *sdc = get_dive_dc(d, dcNr); + struct divecomputer *sdc = d->get_dc(dcNr); if (!sdc) return; std::swap(sdc->samples, dc.samples); - std::swap(sdc->alloc_samples, dc.alloc_samples); - std::swap(sdc->sample, dc.sample); std::swap(sdc->events, dc.events); std::swap(sdc->maxdepth, dc.maxdepth); std::swap(d->maxdepth, maxdepth); std::swap(d->meandepth, meandepth); std::swap(d->duration, duration); - fixup_dive(d); - invalidate_dive_cache(d); // Ensure that dive is written in git_save() + divelog.dives.fixup_dive(*d); + d->invalidate_cache(); // Ensure that dive is written in git_save() QVector divesToNotify = { d }; emit diveListNotifier.divesChanged(divesToNotify, DiveField::DURATION | DiveField::DEPTH); @@ -964,42 +938,37 @@ bool AddWeight::workToBeDone() void AddWeight::undo() { for (dive *d: dives) { - if (d->weightsystems.nr <= 0) + if (d->weightsystems.empty()) continue; - remove_weightsystem(d, d->weightsystems.nr - 1); - emit diveListNotifier.weightRemoved(d, d->weightsystems.nr); - invalidate_dive_cache(d); // Ensure that dive is written in git_save() + d->weightsystems.pop_back(); + emit diveListNotifier.weightRemoved(d, d->weightsystems.size()); + d->invalidate_cache(); // Ensure that dive is written in git_save() } } void AddWeight::redo() { for (dive *d: dives) { - add_cloned_weightsystem(&d->weightsystems, empty_weightsystem); - emit diveListNotifier.weightAdded(d, d->weightsystems.nr - 1); - invalidate_dive_cache(d); // Ensure that dive is written in git_save() + d->weightsystems.emplace_back(); + emit diveListNotifier.weightAdded(d, d->weightsystems.size() - 1); + d->invalidate_cache(); // Ensure that dive is written in git_save() } } -static int find_weightsystem_index(const struct dive *d, weightsystem_t ws) +static int find_weightsystem_index(const struct dive *d, const weightsystem_t &ws) { - for (int idx = 0; idx < d->weightsystems.nr; ++idx) { - if (same_weightsystem(d->weightsystems.weightsystems[idx], ws)) - return idx; - } - return -1; + return index_of_if(d->weightsystems, [&ws](auto &ws2) { return ws == ws2; }); } EditWeightBase::EditWeightBase(int index, bool currentDiveOnly) : - EditDivesBase(currentDiveOnly), - ws(empty_weightsystem) + EditDivesBase(currentDiveOnly) { // Get the old weightsystem, bail if index is invalid - if (!current || index < 0 || index >= current->weightsystems.nr) { + if (!current || index < 0 || static_cast(index) >= current->weightsystems.size()) { dives.clear(); return; } - ws = clone_weightsystem(current->weightsystems.weightsystems[index]); + ws = current->weightsystems[index]; // Deleting a weightsystem from multiple dives is semantically ill-defined. // What we will do is trying to delete the same weightsystem if it exists. @@ -1025,7 +994,6 @@ EditWeightBase::EditWeightBase(int index, bool currentDiveOnly) : EditWeightBase::~EditWeightBase() { - free_weightsystem(ws); } bool EditWeightBase::workToBeDone() @@ -1047,25 +1015,24 @@ RemoveWeight::RemoveWeight(int index, bool currentDiveOnly) : void RemoveWeight::undo() { for (size_t i = 0; i < dives.size(); ++i) { - add_to_weightsystem_table(&dives[i]->weightsystems, indices[i], clone_weightsystem(ws)); + dives[i]->weightsystems.add(indices[i], ws); emit diveListNotifier.weightAdded(dives[i], indices[i]); - invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() + dives[i]->invalidate_cache(); // Ensure that dive is written in git_save() } } void RemoveWeight::redo() { for (size_t i = 0; i < dives.size(); ++i) { - remove_weightsystem(dives[i], indices[i]); + dives[i]->weightsystems.remove(indices[i]); emit diveListNotifier.weightRemoved(dives[i], indices[i]); - invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() + dives[i]->invalidate_cache(); // Ensure that dive is written in git_save() } } // ***** Edit Weight ***** EditWeight::EditWeight(int index, weightsystem_t wsIn, bool currentDiveOnly) : - EditWeightBase(index, currentDiveOnly), - new_ws(empty_weightsystem) + EditWeightBase(index, currentDiveOnly) { if (dives.empty()) return; @@ -1077,18 +1044,16 @@ EditWeight::EditWeight(int index, weightsystem_t wsIn, bool currentDiveOnly) : setText(QStringLiteral("%1 [%2]").arg(Command::Base::tr("Edit weight (%n dive(s))", "", num_dives)).arg(getListOfDives(dives))); // Try to untranslate the weightsystem name - new_ws = clone_weightsystem(wsIn); - QString vString(new_ws.description); - for (int i = 0; i < MAX_WS_INFO && ws_info[i].name; ++i) { - if (gettextFromC::tr(ws_info[i].name) == vString) { - free_weightsystem(new_ws); - new_ws.description = copy_string(ws_info[i].name); - break; - } - } + new_ws = std::move(wsIn); + QString vString = QString::fromStdString(new_ws.description); + auto it = std::find_if(ws_info_table.begin(), ws_info_table.end(), + [&vString](const ws_info &info) + { return gettextFromC::tr(info.name.c_str()) == vString; }); + if (it != ws_info_table.end()) + new_ws.description = it->name; // If that doesn't change anything, do nothing - if (same_weightsystem(ws, new_ws)) { + if (ws == new_ws) { dives.clear(); return; } @@ -1096,16 +1061,15 @@ EditWeight::EditWeight(int index, weightsystem_t wsIn, bool currentDiveOnly) : EditWeight::~EditWeight() { - free_weightsystem(new_ws); } void EditWeight::redo() { for (size_t i = 0; i < dives.size(); ++i) { - add_weightsystem_description(&new_ws); // This updates the weightsystem info table - set_weightsystem(dives[i], indices[i], new_ws); + add_weightsystem_description(new_ws); // This updates the weightsystem info table + dives[i]->weightsystems.set(indices[i], new_ws); emit diveListNotifier.weightEdited(dives[i], indices[i]); - invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() + dives[i]->invalidate_cache(); // Ensure that dive is written in git_save() } std::swap(ws, new_ws); } @@ -1118,8 +1082,7 @@ void EditWeight::undo() // ***** Add Cylinder ***** AddCylinder::AddCylinder(bool currentDiveOnly) : - EditDivesBase(currentDiveOnly), - cyl(empty_cylinder) + EditDivesBase(currentDiveOnly) { if (dives.empty()) return; @@ -1133,7 +1096,6 @@ AddCylinder::AddCylinder(bool currentDiveOnly) : AddCylinder::~AddCylinder() { - free_cylinder(cyl); } bool AddCylinder::workToBeDone() @@ -1145,9 +1107,9 @@ void AddCylinder::undo() { for (size_t i = 0; i < dives.size(); ++i) { remove_cylinder(dives[i], indexes[i]); - update_cylinder_related_info(dives[i]); + divelog.dives.update_cylinder_related_info(*dives[i]); emit diveListNotifier.cylinderRemoved(dives[i], indexes[i]); - invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() + dives[i]->invalidate_cache(); // Ensure that dive is written in git_save() } } @@ -1157,23 +1119,23 @@ void AddCylinder::redo() for (dive *d: dives) { int index = first_hidden_cylinder(d); indexes.push_back(index); - add_cylinder(&d->cylinders, index, clone_cylinder(cyl)); - update_cylinder_related_info(d); + d->cylinders.add(index, cyl); + divelog.dives.update_cylinder_related_info(*d); emit diveListNotifier.cylinderAdded(d, index); - invalidate_dive_cache(d); // Ensure that dive is written in git_save() + d->invalidate_cache(); // Ensure that dive is written in git_save() } } static bool same_cylinder_type(const cylinder_t &cyl1, const cylinder_t &cyl2) { - return same_string(cyl1.type.description, cyl2.type.description) && - cyl1.cylinder_use == cyl2.cylinder_use; + return std::tie(cyl1.cylinder_use, cyl1.type.description) == + std::tie(cyl2.cylinder_use, cyl2.type.description); } static bool same_cylinder_size(const cylinder_t &cyl1, const cylinder_t &cyl2) { - return cyl1.type.size.mliter == cyl2.type.size.mliter && - cyl1.type.workingpressure.mbar == cyl2.type.workingpressure.mbar; + return std::tie(cyl1.type.size.mliter, cyl1.type.workingpressure.mbar) == + std::tie(cyl2.type.size.mliter, cyl2.type.workingpressure.mbar); } // Flags for comparing cylinders @@ -1186,11 +1148,11 @@ EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProt EditDivesBase(currentDiveOnly) { // Get the old cylinder, bail if index is invalid - if (!current || index < 0 || index >= current->cylinders.nr) { + if (!current || index < 0 || index >= static_cast(current->cylinders.size())) { dives.clear(); return; } - const cylinder_t &orig = *get_cylinder(current, index); + const cylinder_t &orig = *current->get_cylinder(index); std::vector divesNew; divesNew.reserve(dives.size()); @@ -1198,12 +1160,12 @@ EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProt cyl.reserve(dives.size()); for (dive *d: dives) { - if (index >= d->cylinders.nr) + if (index >= static_cast(d->cylinders.size())) continue; - if (nonProtectedOnly && is_cylinder_prot(d, index)) + if (nonProtectedOnly && d->is_cylinder_prot(index)) continue; // We checked that the cylinder exists above. - const cylinder_t &cylinder = *get_cylinder(d, index); + const cylinder_t &cylinder = d->cylinders[index]; if (d != current && (!same_cylinder_size(orig, cylinder) || !same_cylinder_type(orig, cylinder))) { // when editing cylinders, we assume that the user wanted to edit the 'n-th' cylinder @@ -1215,15 +1177,13 @@ EditCylinderBase::EditCylinderBase(int index, bool currentDiveOnly, bool nonProt // that's silly as it's always the same value - but we need this vector of indices in the case where we add // a cylinder to several dives as the spot will potentially be different in different dives indexes.push_back(index); - cyl.push_back(clone_cylinder(cylinder)); + cyl.push_back(cylinder); } dives = std::move(divesNew); } EditCylinderBase::~EditCylinderBase() { - for (cylinder_t c: cyl) - free_cylinder(c); } bool EditCylinderBase::workToBeDone() @@ -1244,24 +1204,24 @@ RemoveCylinder::RemoveCylinder(int index, bool currentDiveOnly) : void RemoveCylinder::undo() { for (size_t i = 0; i < dives.size(); ++i) { - std::vector mapping = get_cylinder_map_for_add(dives[i]->cylinders.nr, indexes[i]); - add_cylinder(&dives[i]->cylinders, indexes[i], clone_cylinder(cyl[i])); - cylinder_renumber(dives[i], &mapping[0]); - update_cylinder_related_info(dives[i]); + std::vector mapping = get_cylinder_map_for_add(dives[i]->cylinders.size(), indexes[i]); + dives[i]->cylinders.add(indexes[i], cyl[i]); + cylinder_renumber(*dives[i], &mapping[0]); + divelog.dives.update_cylinder_related_info(*dives[i]); emit diveListNotifier.cylinderAdded(dives[i], indexes[i]); - invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() + dives[i]->invalidate_cache(); // Ensure that dive is written in git_save() } } void RemoveCylinder::redo() { for (size_t i = 0; i < dives.size(); ++i) { - std::vector mapping = get_cylinder_map_for_remove(dives[i]->cylinders.nr, indexes[i]); + std::vector mapping = get_cylinder_map_for_remove(dives[i]->cylinders.size(), indexes[i]); remove_cylinder(dives[i], indexes[i]); - cylinder_renumber(dives[i], &mapping[0]); - update_cylinder_related_info(dives[i]); + cylinder_renumber(*dives[i], &mapping[0]); + divelog.dives.update_cylinder_related_info(*dives[i]); emit diveListNotifier.cylinderRemoved(dives[i], indexes[i]); - invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() + dives[i]->invalidate_cache(); // Ensure that dive is written in git_save() } } @@ -1291,26 +1251,23 @@ EditCylinder::EditCylinder(int index, cylinder_t cylIn, EditCylinderType typeIn, else setText(Command::Base::tr("Edit cylinder (%n dive(s))", "", dives.size())); - QString description = cylIn.type.description; - // The base class copied the cylinders for us, let's edit them - for (int i = 0; i < (int)indexes.size(); ++i) { + for (int i = 0; i < (int)cyl.size(); ++i) { switch (type) { case EditCylinderType::TYPE: - free((void *)cyl[i].type.description); cyl[i].type = cylIn.type; - cyl[i].type.description = copy_qstring(description); + cyl[i].type.description = cylIn.type.description; cyl[i].cylinder_use = cylIn.cylinder_use; break; case EditCylinderType::PRESSURE: - cyl[i].start.mbar = cylIn.start.mbar; - cyl[i].end.mbar = cylIn.end.mbar; + cyl[i].start = cylIn.start; + cyl[i].end = cylIn.end; break; case EditCylinderType::GASMIX: cyl[i].gasmix = cylIn.gasmix; cyl[i].bestmix_o2 = cylIn.bestmix_o2; cyl[i].bestmix_he = cylIn.bestmix_he; - sanitize_gasmix(&cyl[i].gasmix); + sanitize_gasmix(cyl[i].gasmix); break; } } @@ -1319,11 +1276,12 @@ EditCylinder::EditCylinder(int index, cylinder_t cylIn, EditCylinderType typeIn, void EditCylinder::redo() { for (size_t i = 0; i < dives.size(); ++i) { - set_tank_info_data(&tank_info_table, cyl[i].type.description, cyl[i].type.size, cyl[i].type.workingpressure); - std::swap(*get_cylinder(dives[i], indexes[i]), cyl[i]); - update_cylinder_related_info(dives[i]); + const std::string &name = cyl[i].type.description; + set_tank_info_data(tank_info_table, name, cyl[i].type.size, cyl[i].type.workingpressure); + std::swap(*dives[i]->get_cylinder(indexes[i]), cyl[i]); + divelog.dives.update_cylinder_related_info(*dives[i]); emit diveListNotifier.cylinderEdited(dives[i], indexes[i]); - invalidate_dive_cache(dives[i]); // Ensure that dive is written in git_save() + dives[i]->invalidate_cache(); // Ensure that dive is written in git_save() } } @@ -1334,7 +1292,7 @@ void EditCylinder::undo() } EditSensors::EditSensors(int toCylinderIn, int fromCylinderIn, int dcNr) - : d(current_dive), dc(get_dive_dc(d, dcNr)), toCylinder(toCylinderIn), fromCylinder(fromCylinderIn) + : d(current_dive), dc(d->get_dc(dcNr)), toCylinder(toCylinderIn), fromCylinder(fromCylinderIn) { if (!d || !dc) return; @@ -1344,17 +1302,17 @@ EditSensors::EditSensors(int toCylinderIn, int fromCylinderIn, int dcNr) void EditSensors::mapSensors(int toCyl, int fromCyl) { - for (int i = 0; i < dc->samples; ++i) { + for (auto &sample: dc->samples) { for (int s = 0; s < MAX_SENSORS; ++s) { - if (dc->sample[i].pressure[s].mbar && dc->sample[i].sensor[s] == fromCyl) - dc->sample[i].sensor[s] = toCyl; + if (sample.pressure[s].mbar && sample.sensor[s] == fromCyl) + sample.sensor[s] = toCyl; // In case the cylinder we are moving to has a sensor attached, move it to the other cylinder - else if (dc->sample[i].pressure[s].mbar && dc->sample[i].sensor[s] == toCyl) - dc->sample[i].sensor[s] = fromCyl; + else if (sample.pressure[s].mbar && sample.sensor[s] == toCyl) + sample.sensor[s] = fromCyl; } } emit diveListNotifier.diveComputerEdited(dc); - invalidate_dive_cache(d); // Ensure that dive is written in git_save() + d->invalidate_cache(); // Ensure that dive is written in git_save() } void EditSensors::undo() @@ -1409,9 +1367,9 @@ EditDive::EditDive(dive *oldDiveIn, dive *newDiveIn, dive_site *createDs, dive_s changedFields |= DiveField::ATM_PRESS; if (oldDive->dive_site != newDive->dive_site) changedFields |= DiveField::DIVESITE; - if (!same_string(oldDive->diveguide, newDive->diveguide)) + if (oldDive->diveguide != newDive->diveguide) changedFields |= DiveField::DIVEGUIDE; - if (!same_string(oldDive->buddy, newDive->buddy)) + if (oldDive->buddy != newDive->buddy) changedFields |= DiveField::BUDDY; if (oldDive->rating != newDive->rating) changedFields |= DiveField::RATING; @@ -1425,13 +1383,13 @@ EditDive::EditDive(dive *oldDiveIn, dive *newDiveIn, dive_site *createDs, dive_s changedFields |= DiveField::SURGE; if (oldDive->chill != newDive->chill) changedFields |= DiveField::CHILL; - if (!same_string(oldDive->suit, newDive->suit)) + if (oldDive->suit != newDive->suit) changedFields |= DiveField::SUIT; - if (taglist_get_tagstring(oldDive->tag_list) != taglist_get_tagstring(newDive->tag_list)) // This is cheating. Do we have a taglist comparison function? + if (taglist_get_tagstring(oldDive->tags) != taglist_get_tagstring(newDive->tags)) // This is cheating. Do we have a taglist comparison function? changedFields |= DiveField::TAGS; - if (oldDive->dc.divemode != newDive->dc.divemode) + if (oldDive->dcs[0].divemode != newDive->dcs[0].divemode) changedFields |= DiveField::MODE; - if (!same_string(oldDive->notes, newDive->notes)) + if (oldDive->notes != newDive->notes) changedFields |= DiveField::NOTES; if (oldDive->salinity != newDive->salinity) changedFields |= DiveField::SALINITY; @@ -1442,9 +1400,9 @@ EditDive::EditDive(dive *oldDiveIn, dive *newDiveIn, dive_site *createDs, dive_s void EditDive::undo() { if (siteToRemove) { - int idx = unregister_dive_site(siteToRemove); - siteToAdd.reset(siteToRemove); - emit diveListNotifier.diveSiteDeleted(siteToRemove, idx); // Inform frontend of removed dive site. + auto res = divelog.sites.pull(siteToRemove); + siteToAdd = std::move(res.ptr); + emit diveListNotifier.diveSiteDeleted(siteToRemove, res.idx); // Inform frontend of removed dive site. } exchangeDives(); @@ -1454,9 +1412,9 @@ void EditDive::undo() void EditDive::redo() { if (siteToAdd) { - siteToRemove = siteToAdd.get(); - int idx = register_dive_site(siteToAdd.release()); // Return ownership to backend. - emit diveListNotifier.diveSiteAdded(siteToRemove, idx); // Inform frontend of new dive site. + auto res = divelog.sites.register_site(std::move(siteToAdd)); // Return ownership to backend. + siteToRemove = res.ptr; + emit diveListNotifier.diveSiteAdded(siteToRemove, res.idx); // Inform frontend of new dive site. } exchangeDives(); @@ -1481,20 +1439,20 @@ void EditDive::exchangeDives() std::swap(*newDive, *oldDive); fulltext_register(oldDive); if (newDiveSite) - add_dive_to_dive_site(oldDive, newDiveSite); + newDiveSite->add_dive(oldDive); newDiveSite = oldDiveSite; // remember the previous dive site - invalidate_dive_cache(oldDive); + oldDive->invalidate_cache(); // Changing times may have unsorted the dive and trip tables QVector dives = { oldDive }; timestamp_t delta = oldDive->when - newDive->when; if (delta != 0) { - sort_dive_table(divelog.dives); - sort_trip_table(divelog.trips); + divelog.dives.sort(); + divelog.trips.sort(); if (newDive->divetrip != oldDive->divetrip) qWarning("Command::EditDive::redo(): This command does not support moving between trips!"); if (oldDive->divetrip) - sort_dive_table(&newDive->divetrip->dives); // Keep the trip-table in order + newDive->divetrip->sort_dives(); // Keep the trip-table in order emit diveListNotifier.divesTimeChanged(delta, dives); } diff --git a/commands/command_edit.h b/commands/command_edit.h index 529313037..29e8b5ed2 100644 --- a/commands/command_edit.h +++ b/commands/command_edit.h @@ -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 +template class EditStringSetter : public EditTemplate { private: using EditTemplate::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 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 divesite; + std::optional notes; + std::optional diveguide; + std::optional buddy; + std::optional suit; + std::optional rating; + std::optional wavesize; + std::optional visibility; + std::optional current; + std::optional surge; + std::optional chill; + std::optional tags; + std::optional cylinders; + std::optional weightsystems; + std::optional number; + std::optional 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 &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 dives; + std::vector 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 newDive; // New data dive_site *newDiveSite; int changedFields; dive_site *siteToRemove; - OwningDiveSitePtr siteToAdd; + std::unique_ptr siteToAdd; dive_site *siteToEdit; location_t dsLocation; diff --git a/commands/command_edit_trip.cpp b/commands/command_edit_trip.cpp index 64f5d05a0..e93825922 100644 --- a/commands/command_edit_trip.cpp +++ b/commands/command_edit_trip.cpp @@ -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 diff --git a/commands/command_edit_trip.h b/commands/command_edit_trip.h index 778eb0208..91b7b5e2f 100644 --- a/commands/command_edit_trip.h +++ b/commands/command_edit_trip.h @@ -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; diff --git a/commands/command_event.cpp b/commands/command_event.cpp index b1a2f0e86..39b6b489b 100644 --- a/commands/command_event.cpp +++ b/commands/command_event.cpp @@ -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(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 newEventsToAdd; - std::vector newEventsToRemove; + std::vector newEventsToAdd; + std::vector 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()); - 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); diff --git a/commands/command_event.h b/commands/command_event.h index fbf48c425..9beb361f6 100644 --- a/commands/command_event.h +++ b/commands/command_event.h @@ -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,28 +63,28 @@ 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 - int cylinder; // affected cylinder (if removing gas switch). <0: not a gas switch. + event ev; // for undo + int idx; // for redo + int cylinder; // affected cylinder (if removing gas switch). <0: not a gas switch. }; class AddGasSwitch : public EventBase { @@ -100,8 +96,8 @@ private: void redoit() override; std::vector cylinders; // cylinders that are modified - std::vector eventsToAdd; - std::vector eventsToRemove; + std::vector eventsToAdd; + std::vector eventsToRemove; }; } // namespace Command diff --git a/commands/command_filter.cpp b/commands/command_filter.cpp index 4c60eb947..da5ea45ba 100644 --- a/commands/command_filter.cpp +++ b/commands/command_filter.cpp @@ -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 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() diff --git a/commands/command_pictures.cpp b/commands/command_pictures.cpp index 98f09e3c8..9432b6d27 100644 --- a/commands/command_pictures.cpp +++ b/commands/command_pictures.cpp @@ -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 removePictures(std::vectorpictures, 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 addPictures(std::vector res; for (const PictureListForAddition &list: picturesToAdd) { - QVector picsForSignal; + QVector 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 &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(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{ 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 &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(); diff --git a/commands/command_pictures.h b/commands/command_pictures.h index 472a92565..e85cf0409 100644 --- a/commands/command_pictures.h +++ b/commands/command_pictures.h @@ -48,7 +48,7 @@ private: location_t location; }; std::vector picturesToAdd; // for redo - std::vector sitesToAdd; //for redo + std::vector> sitesToAdd; //for redo std::vector picturesToRemove; // for undo std::vector sitesToRemove; // for undo std::vector sitesToSet; // for redo and undo diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index b4cc55723..f9c39f60d 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -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 diff --git a/core/android.cpp b/core/android.cpp index a8a0dd0e9..ffd2594e0 100644 --- a/core/android.cpp +++ b/core/android.cpp @@ -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 #include #include @@ -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() diff --git a/core/btdiscovery.cpp b/core/btdiscovery.cpp index 5d5153130..6cddd2191 100644 --- a/core/btdiscovery.cpp +++ b/core/btdiscovery.cpp @@ -476,23 +476,22 @@ QString extractBluetoothAddress(const QString &address) return m.captured(0); } -QString extractBluetoothNameAddress(const QString &address, QString &name) +std::pair 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) diff --git a/core/btdiscovery.h b/core/btdiscovery.h index 85f2d4d4a..4f7a14937 100644 --- a/core/btdiscovery.h +++ b/core/btdiscovery.h @@ -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 extractBluetoothNameAddress(const QString &address); // returns address/name pair QBluetoothDeviceInfo getBtDeviceInfo(const QString &devaddr); class BTDiscovery : public QObject { diff --git a/core/checkcloudconnection.cpp b/core/checkcloudconnection.cpp index 384afdb3d..65ecb6903 100644 --- a/core/checkcloudconnection.cpp +++ b/core/checkcloudconnection.cpp @@ -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(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()); } diff --git a/core/cloudstorage.cpp b/core/cloudstorage.cpp index 11d558686..b0eaca129 100644 --- a/core/cloudstorage.cpp +++ b/core/cloudstorage.cpp @@ -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"); diff --git a/core/cochran.cpp b/core/cochran.cpp index ea8be2dc9..4e4c1bf09 100644 --- a/core/cochran.cpp +++ b/core/cochran.cpp @@ -4,7 +4,6 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "ssrf.h" #include #include #include @@ -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(); + 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); } diff --git a/core/color.h b/core/color.h index 3e9999e4e..cff6be72f 100644 --- a/core/color.h +++ b/core/color.h @@ -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); diff --git a/core/configuredivecomputer.cpp b/core/configuredivecomputer.cpp index 0a7eb3d3a..31444a8ca 100644 --- a/core/configuredivecomputer.cpp +++ b/core/configuredivecomputer.cpp @@ -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); diff --git a/core/datatrak.cpp b/core/datatrak.cpp index d2fcdd1f5..231ce7831 100644 --- a/core/datatrak.cpp +++ b/core/datatrak.cpp @@ -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); - if (!ds) - ds = create_dive_site(buffer, log->sites); - add_dive_to_dive_site(dt_dive, ds); + { + std::string buffer2 = std::string((char *)locality) + " " + (char *)dive_point; + struct dive_site *ds = log->sites.get_by_name(buffer2); + if (!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(); - 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; diff --git a/core/deco.cpp b/core/deco.cpp index eb4d4308a..6f6481ede 100644 --- a/core/deco.cpp +++ b/core/deco.cpp @@ -21,7 +21,6 @@ #include #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; diff --git a/core/deco.h b/core/deco.h index f550aefe3..30427b5d8 100644 --- a/core/deco.h +++ b/core/deco.h @@ -5,46 +5,43 @@ #include "units.h" #include "gas.h" #include "divemode.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include 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 struct deco_state_cache { // Test if there is cached data operator bool () { @@ -87,6 +78,4 @@ private: std::unique_ptr data; }; -#endif - #endif // DECO_H diff --git a/core/device.cpp b/core/device.cpp index f72240f6f..1bc2183ee 100644 --- a/core/device.cpp +++ b/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 &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 &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 &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 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, - const unsigned char *raw_data_in, unsigned int fsize, uint32_t fdeviceid, uint32_t fdiveid) +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(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; } diff --git a/core/device.h b/core/device.h index 147f56e1e..5616148c8 100644 --- a/core/device.h +++ b/core/device.h @@ -3,73 +3,13 @@ #define DEVICE_H #include - -#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 #include #include + +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; + +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 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 devices; -}; +using fingerprint_table = std::vector; -struct fingerprint_table { - // Keep the fingerprint records in a vector sorted by (model, serial) - these are uint32_t here - std::vector 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 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 diff --git a/core/dive.cpp b/core/dive.cpp index e5fd35ac4..32093a5f5 100644 --- a/core/dive.cpp +++ b/core/dive.cpp @@ -12,20 +12,21 @@ #include "libdivecomputer.h" #include "device.h" #include "divelist.h" -#include "divelog.h" #include "divesite.h" +#include "equipment.h" #include "errorhelper.h" #include "event.h" #include "extradata.h" +#include "format.h" +#include "fulltext.h" #include "interpolate.h" #include "qthelper.h" #include "membuffer.h" #include "picture.h" +#include "range.h" #include "sample.h" #include "tag.h" #include "trip.h" -#include "structured_list.h" -#include "fulltext.h" // For user visible text but still not translated const char *divemode_text_ui[] = { @@ -38,7 +39,30 @@ const char *divemode_text_ui[] = { // For writing/reading files. const char *divemode_text[] = {"OC", "CCR", "PSCR", "Freedive"}; -static double calculate_depth_to_mbarf(int depth, pressure_t surface_pressure, int salinity); +// Even for dives without divecomputer, we allocate a divecomputer structure. +// It's the "manually added" divecomputer. +dive::dive() : dcs(1) +{ + id = dive_getUniqID(); +} + +dive::dive(const dive &) = default; +dive::dive(dive &&) = default; +dive &dive::operator=(const dive &) = default; +dive::~dive() = default; + +/* get_cylinder_idx_by_use(): Find the index of the first cylinder with a particular CCR use type. + * The index returned corresponds to that of the first cylinder with a cylinder_use that + * equals the appropriate enum value [oxygen, diluent, bailout] given by cylinder_use_type. + * A negative number returned indicates that a match could not be found. + * Call parameters: dive = the dive being processed + * cylinder_use_type = an enum, one of {oxygen, diluent, bailout} */ +static int get_cylinder_idx_by_use(const struct dive &dive, enum cylinderuse cylinder_use_type) +{ + auto it = std::find_if(dive.cylinders.begin(), dive.cylinders.end(), [cylinder_use_type] + (auto &cyl) { return cyl.cylinder_use == cylinder_use_type; }); + return it != dive.cylinders.end() ? it - dive.cylinders.begin() : -1; +} /* * The legacy format for sample pressures has a single pressure @@ -51,18 +75,17 @@ static double calculate_depth_to_mbarf(int depth, pressure_t surface_pressure, i * This function returns a negative number for "no legacy mode", * or a non-negative number that indicates the o2 sensor index. */ -extern "C" int legacy_format_o2pressures(const struct dive *dive, const struct divecomputer *dc) +int legacy_format_o2pressures(const struct dive *dive, const struct divecomputer *dc) { - int i, o2sensor; + int o2sensor; - o2sensor = (dc->divemode == CCR) ? get_cylinder_idx_by_use(dive, OXYGEN) : -1; - for (i = 0; i < dc->samples; i++) { - const struct sample *s = dc->sample + i; + o2sensor = (dc->divemode == CCR) ? get_cylinder_idx_by_use(*dive, OXYGEN) : -1; + for (const auto &s: dc->samples) { int seen_pressure = 0, idx; for (idx = 0; idx < MAX_SENSORS; idx++) { - int sensor = s->sensor[idx]; - pressure_t p = s->pressure[idx]; + int sensor = s.sensor[idx]; + pressure_t p = s.pressure[idx]; if (!p.mbar) continue; @@ -82,307 +105,134 @@ extern "C" int legacy_format_o2pressures(const struct dive *dive, const struct d return o2sensor < 0 ? 256 : o2sensor; } +/* access to cylinders is controlled by two functions: + * - get_cylinder() returns the cylinder of a dive and supposes that + * the cylinder with the given index exists. If it doesn't, an error + * message is printed and the "surface air" cylinder returned. + * (NOTE: this MUST not be written into!). + * - get_or_create_cylinder() creates an empty cylinder if it doesn't exist. + * Multiple cylinders might be created if the index is bigger than the + * number of existing cylinders + */ +cylinder_t *dive::get_cylinder(int idx) +{ + return &cylinders[idx]; +} + +const cylinder_t *dive::get_cylinder(int idx) const +{ + return &cylinders[idx]; +} + /* warning: does not test idx for validity */ -extern "C" struct event *create_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) +struct event create_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) { /* The gas switch event format is insane for historical reasons */ - struct gasmix mix = get_cylinder(dive, idx)->gasmix; + struct gasmix mix = dive->get_cylinder(idx)->gasmix; int o2 = get_o2(mix); int he = get_he(mix); - struct event *ev; - int value; o2 = (o2 + 5) / 10; he = (he + 5) / 10; - value = o2 + (he << 16); + int value = o2 + (he << 16); - ev = create_event(seconds, he ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE, 0, value, "gaschange"); - ev->gas.index = idx; - ev->gas.mix = mix; + struct event ev(seconds, he ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE, 0, value, "gaschange"); + ev.gas.index = idx; + ev.gas.mix = mix; return ev; } -extern "C" void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) +void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) { /* sanity check so we don't crash */ /* FIXME: The planner uses a dummy cylinder one past the official number of cylinders * in the table to mark no-cylinder surface interavals. This is horrendous. Fix ASAP. */ - //if (idx < 0 || idx >= dive->cylinders.nr) { - if (idx < 0 || idx >= dive->cylinders.nr + 1 || idx >= dive->cylinders.allocated) { + //if (idx < 0 || idx >= dive->cylinders.size()) { + if (idx < 0 || static_cast(idx) >= dive->cylinders.size() + 1) { report_error("Unknown cylinder index: %d", idx); return; } - struct event *ev = create_gas_switch_event(dive, dc, seconds, idx); - add_event_to_dc(dc, ev); + struct event ev = create_gas_switch_event(dive, dc, seconds, idx); + add_event_to_dc(dc, std::move(ev)); } -/* since the name is an array as part of the structure (how silly is that?) we - * have to actually remove the existing event and replace it with a new one. - * WARNING, WARNING... this may end up freeing event in case that event is indeed - * WARNING, WARNING... part of this divecomputer on this dive! */ -extern "C" void update_event_name(struct dive *d, int dc_number, struct event *event, const char *name) +struct gasmix dive::get_gasmix_from_event(const struct event &ev) const { - if (!d || !event) - return; - struct divecomputer *dc = get_dive_dc(d, dc_number); - if (!dc) - return; - struct event **removep = &dc->events; - struct event *remove; - while ((*removep)->next && !same_event(*removep, event)) - removep = &(*removep)->next; - if (!same_event(*removep, event)) - return; - remove = *removep; - *removep = (*removep)->next; - add_event(dc, event->time.seconds, event->type, event->flags, event->value, name); - free(remove); - invalidate_dive_cache(d); -} - -extern "C" struct gasmix get_gasmix_from_event(const struct dive *dive, const struct event *ev) -{ - if (ev && event_is_gaschange(ev)) { - int index = ev->gas.index; + if (ev.is_gaschange()) { + int index = ev.gas.index; // FIXME: The planner uses one past cylinder-count to signify "surface air". Remove in due course. - if (index == dive->cylinders.nr) - return gasmix_air; - if (index >= 0 && index < dive->cylinders.nr) - return get_cylinder(dive, index)->gasmix; - return ev->gas.mix; + if (index >= 0 && static_cast(index) < cylinders.size() + 1) + return get_cylinder(index)->gasmix; + return ev.gas.mix; } return gasmix_air; } // we need this to be uniq. oh, and it has no meaning whatsoever // - that's why we have the silly initial number and increment by 3 :-) -extern "C" int dive_getUniqID() +int dive_getUniqID() { static int maxId = 83529; maxId += 3; return maxId; } -extern "C" struct dive *alloc_dive(void) +static void dc_cylinder_renumber(struct dive &dive, struct divecomputer &dc, const int mapping[]); + +/* copy dive computer list and renumber the cylinders */ +static void copy_dc_renumber(struct dive &d, const struct dive &s, const int cylinders_map[]) { - struct dive *dive; - - dive = (struct dive *)malloc(sizeof(*dive)); - if (!dive) - exit(1); - memset(dive, 0, sizeof(*dive)); - dive->id = dive_getUniqID(); - return dive; -} - -/* copy an element in a list of dive computer extra data */ -static void copy_extra_data(struct extra_data *sed, struct extra_data *ded) -{ - ded->key = copy_string(sed->key); - ded->value = copy_string(sed->value); -} - -/* this is very different from the copy_divecomputer later in this file; - * this function actually makes full copies of the content */ -static void copy_dc(const struct divecomputer *sdc, struct divecomputer *ddc) -{ - *ddc = *sdc; - ddc->model = copy_string(sdc->model); - ddc->serial = copy_string(sdc->serial); - ddc->fw_version = copy_string(sdc->fw_version); - copy_samples(sdc, ddc); - copy_events(sdc, ddc); - STRUCTURED_LIST_COPY(struct extra_data, sdc->extra_data, ddc->extra_data, copy_extra_data); -} - -static void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, const int mapping[]); - -/* copy dive computer list and renumber the cylinders - * space for the first divecomputer is provided by the - * caller, the remainder is allocated */ -static void copy_dc_renumber(struct dive *d, const struct divecomputer *sdc, struct divecomputer *ddc, const int cylinders_map[]) -{ - for (;;) { - copy_dc(sdc, ddc); - dc_cylinder_renumber(d, ddc, cylinders_map); - if (!sdc->next) - break; - sdc = sdc->next; - ddc->next = (divecomputer *)calloc(1, sizeof(struct divecomputer)); - ddc = ddc->next; + for (const divecomputer &dc: s.dcs) { + d.dcs.push_back(dc); + dc_cylinder_renumber(d, d.dcs.back(), cylinders_map); } - ddc->next = NULL; } -static void free_dive_structures(struct dive *d) +void dive::clear() { - if (!d) - return; - fulltext_unregister(d); - /* free the strings */ - free(d->buddy); - free(d->diveguide); - free(d->notes); - free(d->suit); - /* free tags, additional dive computers, and pictures */ - taglist_free(d->tag_list); - free_dive_dcs(&d->dc); - clear_cylinder_table(&d->cylinders); - free(d->cylinders.cylinders); - clear_weightsystem_table(&d->weightsystems); - free(d->weightsystems.weightsystems); - clear_picture_table(&d->pictures); - free(d->pictures.pictures); -} - -extern "C" void free_dive(struct dive *d) -{ - free_dive_structures(d); - free(d); -} - -/* copy_dive makes duplicates of many components of a dive; - * in order not to leak memory, we need to free those . - * copy_dive doesn't play with the divetrip and forward/backward pointers - * so we can ignore those */ -extern "C" void clear_dive(struct dive *d) -{ - if (!d) - return; - free_dive_structures(d); - memset(d, 0, sizeof(struct dive)); + *this = dive(); } /* make a true copy that is independent of the source dive; * all data structures are duplicated, so the copy can be modified without * any impact on the source */ -static void copy_dive_nodc(const struct dive *s, struct dive *d) +void copy_dive(const struct dive *s, struct dive *d) { - clear_dive(d); - /* simply copy things over, but then make actual copies of the - * relevant components that are referenced through pointers, - * so all the strings and the structured lists */ + /* simply copy things over, but then clear the dive cache. */ *d = *s; - memset(&d->cylinders, 0, sizeof(d->cylinders)); - memset(&d->weightsystems, 0, sizeof(d->weightsystems)); - memset(&d->pictures, 0, sizeof(d->pictures)); - d->full_text = NULL; - invalidate_dive_cache(d); - d->buddy = copy_string(s->buddy); - d->diveguide = copy_string(s->diveguide); - d->notes = copy_string(s->notes); - d->suit = copy_string(s->suit); - copy_cylinders(&s->cylinders, &d->cylinders); - copy_weights(&s->weightsystems, &d->weightsystems); - copy_pictures(&s->pictures, &d->pictures); - d->tag_list = taglist_copy(s->tag_list); + d->invalidate_cache(); } -extern "C" void copy_dive(const struct dive *s, struct dive *d) -{ - copy_dive_nodc(s, d); - - // Copy the first dc explicitly, then the list of subsequent dc's - copy_dc(&s->dc, &d->dc); - STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc); -} - -static void copy_dive_onedc(const struct dive *s, const struct divecomputer *sdc, struct dive *d) -{ - copy_dive_nodc(s, d); - copy_dc(sdc, &d->dc); - d->dc.next = NULL; -} - -/* make a clone of the source dive and clean out the source dive; - * this allows us to create a dive on the stack and then - * add it to the divelist. */ -extern "C" struct dive *move_dive(struct dive *s) -{ - struct dive *dive = alloc_dive(); - *dive = *s; // so all the pointers in dive point to the things s pointed to - memset(s, 0, sizeof(struct dive)); // and now the pointers in s are gone - return dive; -} - -#define CONDITIONAL_COPY_STRING(_component) \ - if (what._component) \ - d->_component = copy_string(s->_component) - -// copy elements, depending on bits in what that are set -extern "C" void selective_copy_dive(const struct dive *s, struct dive *d, struct dive_components what, bool clear) -{ - if (clear) - clear_dive(d); - CONDITIONAL_COPY_STRING(notes); - CONDITIONAL_COPY_STRING(diveguide); - CONDITIONAL_COPY_STRING(buddy); - CONDITIONAL_COPY_STRING(suit); - if (what.rating) - d->rating = s->rating; - if (what.visibility) - d->visibility = s->visibility; - if (what.divesite) { - unregister_dive_from_dive_site(d); - add_dive_to_dive_site(d, s->dive_site); - } - if (what.tags) - d->tag_list = taglist_copy(s->tag_list); - if (what.cylinders) - copy_cylinder_types(s, d); - if (what.weights) - copy_weights(&s->weightsystems, &d->weightsystems); - if (what.number) - d->number = s->number; - if (what.when) - d->when = s->when; -} -#undef CONDITIONAL_COPY_STRING - /* copies all events from the given dive computer before a given time this is used when editing a dive in the planner to preserve the events of the old dive */ -extern "C" void copy_events_until(const struct dive *sd, struct dive *dd, int dcNr, int time) +void copy_events_until(const struct dive *sd, struct dive *dd, int dcNr, int time) { if (!sd || !dd) return; - const struct divecomputer *s = &sd->dc; - struct divecomputer *d = get_dive_dc(dd, dcNr); + const struct divecomputer *s = &sd->dcs[0]; + struct divecomputer *d = dd->get_dc(dcNr); if (!s || !d) return; - const struct event *ev; - ev = s->events; - while (ev != NULL) { + for (const auto &ev: s->events) { // Don't add events the planner knows about - if (ev->time.seconds < time && !event_is_gaschange(ev) && !event_is_divemodechange(ev)) - add_event(d, ev->time.seconds, ev->type, ev->flags, ev->value, ev->name); - ev = ev->next; + if (ev.time.seconds < time && !ev.is_gaschange() && !ev.is_divemodechange()) + add_event(d, ev.time.seconds, ev.type, ev.flags, ev.value, ev.name); } } -extern "C" int nr_cylinders(const struct dive *dive) +void copy_used_cylinders(const struct dive *s, struct dive *d, bool used_only) { - return dive->cylinders.nr; -} - -extern "C" int nr_weightsystems(const struct dive *dive) -{ - return dive->weightsystems.nr; -} - -extern "C" void copy_used_cylinders(const struct dive *s, struct dive *d, bool used_only) -{ - int i; if (!s || !d) return; - clear_cylinder_table(&d->cylinders); - for (i = 0; i < s->cylinders.nr; i++) { - if (!used_only || is_cylinder_used(s, i) || get_cylinder(s, i)->cylinder_use == NOT_USED) - add_cloned_cylinder(&d->cylinders, *get_cylinder(s, i)); + d->cylinders.clear(); + for (auto [i, cyl]: enumerated_range(s->cylinders)) { + if (!used_only || s->is_cylinder_used(i) || s->get_cylinder(i)->cylinder_use == NOT_USED) + d->cylinders.push_back(cyl); } } @@ -424,14 +274,14 @@ static void update_temperature(temperature_t *temperature, int new_temp) /* Which cylinders had gas used? */ #define SOME_GAS 5000 -static bool cylinder_used(const cylinder_t *cyl) +static bool cylinder_used(const cylinder_t &cyl) { int start_mbar, end_mbar; - start_mbar = cyl->start.mbar ?: cyl->sample_start.mbar; - end_mbar = cyl->end.mbar ?: cyl->sample_end.mbar; + start_mbar = cyl.start.mbar ?: cyl.sample_start.mbar; + end_mbar = cyl.end.mbar ?: cyl.sample_end.mbar; - // More than 5 bar used? This matches statistics.c + // More than 5 bar used? This matches statistics.cpp // heuristics return start_mbar > end_mbar + SOME_GAS; } @@ -439,24 +289,60 @@ static bool cylinder_used(const cylinder_t *cyl) /* Get list of used cylinders. Returns the number of used cylinders. */ static int get_cylinder_used(const struct dive *dive, bool used[]) { - int i, num = 0; + int num = 0; - for (i = 0; i < dive->cylinders.nr; i++) { - used[i] = cylinder_used(get_cylinder(dive, i)); + for (auto [i, cyl]: enumerated_range(dive->cylinders)) { + used[i] = cylinder_used(cyl); if (used[i]) num++; } return num; } +/* + * If the event has an explicit cylinder index, + * we return that. If it doesn't, we return the best + * match based on the gasmix. + * + * Some dive computers give cylinder indices, some + * give just the gas mix. + */ +int dive::get_cylinder_index(const struct event &ev) const +{ + if (ev.gas.index >= 0) + return ev.gas.index; + + /* + * This should no longer happen! + * + * We now match up gas change events with their cylinders at dive + * event fixup time. + */ + report_info("Still looking up cylinder based on gas mix in get_cylinder_index()!"); + + gasmix mix = get_gasmix_from_event(ev); + int best = find_best_gasmix_match(mix, cylinders); + return best < 0 ? 0 : best; +} + +cylinder_t *dive::get_or_create_cylinder(int idx) +{ + if (idx < 0) { + report_info("Warning: accessing invalid cylinder %d", idx); + return NULL; + } + while (static_cast(idx) >= cylinders.size()) + cylinders.emplace_back(); + return &cylinders[idx]; +} + /* Are there any used cylinders which we do not know usage about? */ -static bool has_unknown_used_cylinders(const struct dive *dive, const struct divecomputer *dc, +static bool has_unknown_used_cylinders(const struct dive &dive, const struct divecomputer *dc, const bool used_cylinders[], int num) { int idx; - const struct event *ev; - auto used_and_unknown = std::make_unique(dive->cylinders.nr); - std::copy(used_cylinders, used_cylinders + dive->cylinders.nr, used_and_unknown.get()); + auto used_and_unknown = std::make_unique(dive.cylinders.size()); + std::copy(used_cylinders, used_cylinders + dive.cylinders.size(), used_and_unknown.get()); /* We know about using the O2 cylinder in a CCR dive */ if (dc->divemode == CCR) { @@ -468,38 +354,27 @@ static bool has_unknown_used_cylinders(const struct dive *dive, const struct div } /* We know about the explicit first cylinder (or first) */ - idx = explicit_first_cylinder(dive, dc); - if (idx >= 0 && used_and_unknown[idx]) { - used_and_unknown[idx] = false; - num--; - } - /* And we have possible switches to other gases */ - ev = get_next_event(dc->events, "gaschange"); - while (ev && num > 0) { - idx = get_cylinder_index(dive, ev); + gasmix_loop loop(dive, *dc); + while (loop.has_next() && num > 0) { + idx = loop.next_cylinder_index().first; if (idx >= 0 && used_and_unknown[idx]) { used_and_unknown[idx] = false; num--; } - ev = get_next_event(ev->next, "gaschange"); } return num > 0; } -extern "C" void per_cylinder_mean_depth(const struct dive *dive, struct divecomputer *dc, int *mean, int *duration) +void per_cylinder_mean_depth(const struct dive *dive, struct divecomputer *dc, int *mean, int *duration) { - int i; - int32_t lasttime = 0; - int lastdepth = 0; - int idx = 0; int num_used_cylinders; - if (dive->cylinders.nr <= 0) + if (dive->cylinders.empty()) return; - for (i = 0; i < dive->cylinders.nr; i++) + for (size_t i = 0; i < dive->cylinders.size(); i++) mean[i] = duration[i] = 0; if (!dc) return; @@ -509,9 +384,9 @@ extern "C" void per_cylinder_mean_depth(const struct dive *dive, struct divecomp * if we don't actually know about the usage of all the * used cylinders. */ - auto used_cylinders = std::make_unique(dive->cylinders.nr); + auto used_cylinders = std::make_unique(dive->cylinders.size()); num_used_cylinders = get_cylinder_used(dive, used_cylinders.get()); - if (has_unknown_used_cylinders(dive, dc, used_cylinders.get(), num_used_cylinders)) { + if (has_unknown_used_cylinders(*dive, dc, used_cylinders.get(), num_used_cylinders)) { /* * If we had more than one used cylinder, but * do not know usage of them, we simply cannot @@ -524,7 +399,7 @@ extern "C" void per_cylinder_mean_depth(const struct dive *dive, struct divecomp * For a single cylinder, use the overall mean * and duration */ - for (i = 0; i < dive->cylinders.nr; i++) { + for (size_t i = 0; i < dive->cylinders.size(); i++) { if (used_cylinders[i]) { mean[i] = dc->meandepth.mm; duration[i] = dc->duration.seconds; @@ -533,51 +408,57 @@ extern "C" void per_cylinder_mean_depth(const struct dive *dive, struct divecomp return; } - if (!dc->samples) + if (dc->samples.empty()) fake_dc(dc); - const struct event *ev = get_next_event(dc->events, "gaschange"); - std::vector depthtime(dive->cylinders.nr, 0); - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int32_t time = sample->time.seconds; - int depth = sample->depth.mm; + + gasmix_loop loop(*dive, *dc); + std::vector depthtime(dive->cylinders.size(), 0); + int lasttime = 0; + int lastdepth = 0; + int last_cylinder_index = -1; + std::pair gaschange_event; + for (auto it = dc->samples.begin(); it != dc->samples.end(); ++it) { + int32_t time = it->time.seconds; + int depth = it->depth.mm; /* Make sure to move the event past 'lasttime' */ - while (ev && lasttime >= ev->time.seconds) { - idx = get_cylinder_index(dive, ev); - ev = get_next_event(ev->next, "gaschange"); + gaschange_event = loop.cylinder_index_at(lasttime); + + /* Do we need to fake a midway sample? */ + if (last_cylinder_index >= 0 && last_cylinder_index != gaschange_event.first) { + int newdepth = interpolate(lastdepth, depth, gaschange_event.second - lasttime, time - lasttime); + if (newdepth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { + duration[gaschange_event.first] += gaschange_event.second - lasttime; + depthtime[gaschange_event.first] += (gaschange_event.second - lasttime) * (newdepth + lastdepth) / 2; + } + + lasttime = gaschange_event.second; + lastdepth = newdepth; } - /* Do we need to fake a midway sample at an event? */ - if (ev && time > ev->time.seconds) { - int newtime = ev->time.seconds; - int newdepth = interpolate(lastdepth, depth, newtime - lasttime, time - lasttime); - - time = newtime; - depth = newdepth; - i--; - } /* We ignore segments at the surface */ if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { - duration[idx] += time - lasttime; - depthtime[idx] += (time - lasttime) * (depth + lastdepth) / 2; + duration[gaschange_event.first] += time - lasttime; + depthtime[gaschange_event.first] += (time - lasttime) * (depth + lastdepth) / 2; } + lastdepth = depth; lasttime = time; + last_cylinder_index = gaschange_event.first; } - for (i = 0; i < dive->cylinders.nr; i++) { + for (size_t i = 0; i < dive->cylinders.size(); i++) { if (duration[i]) mean[i] = (depthtime[i] + duration[i] / 2) / duration[i]; } } -static void update_min_max_temperatures(struct dive *dive, temperature_t temperature) +static void update_min_max_temperatures(struct dive &dive, temperature_t temperature) { if (temperature.mkelvin) { - if (!dive->maxtemp.mkelvin || temperature.mkelvin > dive->maxtemp.mkelvin) - dive->maxtemp = temperature; - if (!dive->mintemp.mkelvin || temperature.mkelvin < dive->mintemp.mkelvin) - dive->mintemp = temperature; + if (!dive.maxtemp.mkelvin || temperature.mkelvin > dive.maxtemp.mkelvin) + dive.maxtemp = temperature; + if (!dive.mintemp.mkelvin || temperature.mkelvin < dive.mintemp.mkelvin) + dive.mintemp = temperature; } } @@ -588,71 +469,43 @@ static void update_min_max_temperatures(struct dive *dive, temperature_t tempera */ static int same_rounded_pressure(pressure_t a, pressure_t b) { - return abs(a.mbar - b.mbar) <= 500; + return abs((a - b).mbar) <= 500; } -/* Some dive computers (Cobalt) don't start the dive with cylinder 0 but explicitly - * tell us what the first gas is with a gas change event in the first sample. - * Sneakily we'll use a return value of 0 (or FALSE) when there is no explicit - * first cylinder - in which case cylinder 0 is indeed the first cylinder. - * We likewise return 0 if the event concerns a cylinder that doesn't exist. - * If the dive has no cylinders, -1 is returned. */ -extern "C" int explicit_first_cylinder(const struct dive *dive, const struct divecomputer *dc) -{ - int res = 0; - if (!dive->cylinders.nr) - return -1; - if (dc) { - const struct event *ev = get_next_event(dc->events, "gaschange"); - if (ev && ((dc->sample && ev->time.seconds == dc->sample[0].time.seconds) || ev->time.seconds <= 1)) - res = get_cylinder_index(dive, ev); - else if (dc->divemode == CCR) - res = std::max(get_cylinder_idx_by_use(dive, DILUENT), res); - } - return res < dive->cylinders.nr ? res : 0; -} +static double calculate_depth_to_mbarf(int depth, pressure_t surface_pressure, int salinity); /* this gets called when the dive mode has changed (so OC vs. CC) * there are two places we might have setpoints... events or in the samples */ -extern "C" void update_setpoint_events(const struct dive *dive, struct divecomputer *dc) +void update_setpoint_events(const struct dive *dive, struct divecomputer *dc) { - struct event *ev; int new_setpoint = 0; if (dc->divemode == CCR) new_setpoint = prefs.defaultsetpoint; if (dc->divemode == OC && - (same_string(dc->model, "Shearwater Predator") || - same_string(dc->model, "Shearwater Petrel") || - same_string(dc->model, "Shearwater Nerd"))) { + (dc->model == "Shearwater Predator" || + dc->model == "Shearwater Petrel" || + dc->model == "Shearwater Nerd")) { // make sure there's no setpoint in the samples // this is an irreversible change - so switching a dive to OC // by mistake when it's actually CCR is _bad_ // So we make sure, this comes from a Predator or Petrel and we only remove // pO2 values we would have computed anyway. - const struct event *ev = get_next_event(dc->events, "gaschange"); - struct gasmix gasmix = get_gasmix_from_event(dive, ev); - const struct event *next = get_next_event(ev, "gaschange"); - - for (int i = 0; i < dc->samples; i++) { - struct gas_pressures pressures; - if (next && dc->sample[i].time.seconds >= next->time.seconds) { - ev = next; - gasmix = get_gasmix_from_event(dive, ev); - next = get_next_event(ev, "gaschange"); - } - fill_pressures(&pressures, lrint(calculate_depth_to_mbarf(dc->sample[i].depth.mm, dc->surface_pressure, 0)), gasmix ,0, dc->divemode); - if (abs(dc->sample[i].setpoint.mbar - (int)(1000 * pressures.o2)) <= 50) - dc->sample[i].setpoint.mbar = 0; + gasmix_loop loop(*dive, *dc); + for (auto &sample: dc->samples) { + struct gasmix gasmix = loop.at(sample.time.seconds).first; + gas_pressures pressures = fill_pressures(lrint(calculate_depth_to_mbarf(sample.depth.mm, dc->surface_pressure, 0)), gasmix ,0, dc->divemode); + if (abs(sample.setpoint.mbar - (int)(1000 * pressures.o2)) <= 50) + sample.setpoint = 0_baro2; } } // an "SP change" event at t=0 is currently our marker for OC vs CCR // this will need to change to a saner setup, but for now we can just // check if such an event is there and adjust it, or add that event - ev = get_next_event_mutable(dc->events, "SP change"); + struct event *ev = get_first_event(*dc, "SP change"); if (ev && ev->time.seconds == 0) { ev->value = new_setpoint; } else { @@ -670,22 +523,18 @@ extern "C" void update_setpoint_events(const struct dive *dive, struct divecompu * cylinder name is independent from the gasmix, and different * gasmixes have different compressibility. */ -static void match_standard_cylinder(cylinder_type_t *type) +static void match_standard_cylinder(cylinder_type_t &type) { - double cuft, bar; - int psi, len; - const char *fmt; - char buffer[40], *p; - /* Do we already have a cylinder description? */ - if (type->description) + if (!type.description.empty()) return; - bar = type->workingpressure.mbar / 1000.0; - cuft = ml_to_cuft(type->size.mliter); + double bar = type.workingpressure.mbar / 1000.0; + double cuft = ml_to_cuft(type.size.mliter); cuft *= bar_to_atm(bar); - psi = lrint(to_PSI(type->workingpressure)); + int psi = lrint(to_PSI(type.workingpressure)); + const char *fmt; switch (psi) { case 2300 ... 2500: /* 2400 psi: LP tank */ fmt = "LP%d"; @@ -705,12 +554,7 @@ static void match_standard_cylinder(cylinder_type_t *type) default: return; } - len = snprintf(buffer, sizeof(buffer), fmt, (int)lrint(cuft)); - p = (char *)malloc(len + 1); - if (!p) - return; - memcpy(p, buffer, len + 1); - type->description = p; + type.description = format_string_std(fmt, int_cast(cuft)); } /* @@ -723,67 +567,49 @@ static void match_standard_cylinder(cylinder_type_t *type) * We internally use physical size only. But we save the workingpressure * so that we can do the conversion if required. */ -static void sanitize_cylinder_type(cylinder_type_t *type) +static void sanitize_cylinder_type(cylinder_type_t &type) { /* If we have no working pressure, it had *better* be just a physical size! */ - if (!type->workingpressure.mbar) + if (!type.workingpressure.mbar) return; /* No size either? Nothing to go on */ - if (!type->size.mliter) + if (!type.size.mliter) return; /* Ok, we have both size and pressure: try to match a description */ match_standard_cylinder(type); } -static void sanitize_cylinder_info(struct dive *dive) +static void sanitize_cylinder_info(struct dive &dive) { - int i; - - for (i = 0; i < dive->cylinders.nr; i++) { - sanitize_gasmix(&get_cylinder(dive, i)->gasmix); - sanitize_cylinder_type(&get_cylinder(dive, i)->type); + for (auto &cyl: dive.cylinders) { + sanitize_gasmix(cyl.gasmix); + sanitize_cylinder_type(cyl.type); } } /* some events should never be thrown away */ -static bool is_potentially_redundant(const struct event *event) +static bool is_potentially_redundant(const struct event &event) { - if (!strcmp(event->name, "gaschange")) + if (event.name == "gaschange") return false; - if (!strcmp(event->name, "bookmark")) + if (event.name == "bookmark") return false; - if (!strcmp(event->name, "heading")) + if (event.name == "heading") return false; return true; } -/* match just by name - we compare the details in the code that uses this helper */ -static struct event *find_previous_event(struct divecomputer *dc, struct event *event) +pressure_t dive::calculate_surface_pressure() const { - struct event *ev = dc->events; - struct event *previous = NULL; - - if (empty_string(event->name)) - return NULL; - while (ev && ev != event) { - if (same_string(ev->name, event->name)) - previous = ev; - ev = ev->next; - } - return previous; -} - -extern "C" pressure_t calculate_surface_pressure(const struct dive *dive) -{ - const struct divecomputer *dc; pressure_t res; int sum = 0, nr = 0; - for_each_relevant_dc(dive, dc) { - if (dc->surface_pressure.mbar) { - sum += dc->surface_pressure.mbar; + bool logged = is_logged(); + for (auto &dc: dcs) { + if ((logged || !is_dc_planner(&dc)) && dc.surface_pressure.mbar) { + sum += dc.surface_pressure.mbar; nr++; } } @@ -791,92 +617,89 @@ extern "C" pressure_t calculate_surface_pressure(const struct dive *dive) return res; } -static void fixup_surface_pressure(struct dive *dive) +static void fixup_surface_pressure(struct dive &dive) { - dive->surface_pressure = calculate_surface_pressure(dive); + dive.surface_pressure = dive.calculate_surface_pressure(); } /* if the surface pressure in the dive data is redundant to the calculated * value (i.e., it was added by running fixup on the dive) return 0, * otherwise return the surface pressure given in the dive */ -extern "C" pressure_t un_fixup_surface_pressure(const struct dive *d) +pressure_t dive::un_fixup_surface_pressure() const { - pressure_t res = d->surface_pressure; - if (res.mbar && res.mbar == calculate_surface_pressure(d).mbar) - res.mbar = 0; - return res; + return surface_pressure.mbar == calculate_surface_pressure().mbar ? + pressure_t() : surface_pressure; } -static void fixup_water_salinity(struct dive *dive) +static void fixup_water_salinity(struct dive &dive) { - struct divecomputer *dc; int sum = 0, nr = 0; - for_each_relevant_dc (dive, dc) { - if (dc->salinity) { - if (dc->salinity < 500) - dc->salinity += FRESHWATER_SALINITY; - sum += dc->salinity; + bool logged = dive.is_logged(); + for (auto &dc: dive.dcs) { + if ((logged || !is_dc_planner(&dc)) && dc.salinity) { + if (dc.salinity < 500) + dc.salinity += FRESHWATER_SALINITY; + sum += dc.salinity; nr++; } } if (nr) - dive->salinity = (sum + nr / 2) / nr; + dive.salinity = (sum + nr / 2) / nr; } -extern "C" int get_dive_salinity(const struct dive *dive) +int dive::get_salinity() const { - return dive->user_salinity ? dive->user_salinity : dive->salinity; + return user_salinity ? user_salinity : salinity; } -static void fixup_meandepth(struct dive *dive) +static void fixup_meandepth(struct dive &dive) { - struct divecomputer *dc; int sum = 0, nr = 0; - for_each_relevant_dc (dive, dc) { - if (dc->meandepth.mm) { - sum += dc->meandepth.mm; + bool logged = dive.is_logged(); + for (auto &dc: dive.dcs) { + if ((logged || !is_dc_planner(&dc)) && dc.meandepth.mm) { + sum += dc.meandepth.mm; nr++; } } if (nr) - dive->meandepth.mm = (sum + nr / 2) / nr; + dive.meandepth.mm = (sum + nr / 2) / nr; } -static void fixup_duration(struct dive *dive) +static void fixup_duration(struct dive &dive) { - struct divecomputer *dc; - duration_t duration = { }; + duration_t duration; - for_each_relevant_dc (dive, dc) { - duration.seconds = std::max(duration.seconds, dc->duration.seconds); + bool logged = dive.is_logged(); + for (auto &dc: dive.dcs) { + if (logged || !is_dc_planner(&dc)) + duration.seconds = std::max(duration.seconds, dc.duration.seconds); } - dive->duration.seconds = duration.seconds; + dive.duration = duration; } -static void fixup_watertemp(struct dive *dive) +static void fixup_watertemp(struct dive &dive) { - if (!dive->watertemp.mkelvin) - dive->watertemp.mkelvin = dc_watertemp(&dive->dc); + if (!dive.watertemp.mkelvin) + dive.watertemp = dive.dc_watertemp(); } -static void fixup_airtemp(struct dive *dive) +static void fixup_airtemp(struct dive &dive) { - if (!dive->airtemp.mkelvin) - dive->airtemp.mkelvin = dc_airtemp(&dive->dc); + if (!dive.airtemp.mkelvin) + dive.airtemp = dive.dc_airtemp(); } /* if the air temperature in the dive data is redundant to the one in its * first divecomputer (i.e., it was added by running fixup on the dive) * return 0, otherwise return the air temperature given in the dive */ -static temperature_t un_fixup_airtemp(const struct dive *a) +static temperature_t un_fixup_airtemp(const struct dive &a) { - temperature_t res = a->airtemp; - if (a->airtemp.mkelvin && a->airtemp.mkelvin == dc_airtemp(&a->dc)) - res.mkelvin = 0; - return res; + return a.airtemp.mkelvin == a.dc_airtemp().mkelvin ? + temperature_t() : a.airtemp; } /* @@ -891,69 +714,58 @@ static temperature_t un_fixup_airtemp(const struct dive *a) * is no dive computer with a sample rate of more than 60 * seconds... that would be pretty pointless to plot the * profile with) - * - * We first only mark the events for deletion so that we - * still know when the previous event happened. */ -static void fixup_dc_events(struct divecomputer *dc) +static void fixup_dc_events(struct divecomputer &dc) { - struct event *event; + std::vector to_delete; - event = dc->events; - while (event) { - struct event *prev; - if (is_potentially_redundant(event)) { - prev = find_previous_event(dc, event); - if (prev && prev->value == event->value && - prev->flags == event->flags && - event->time.seconds - prev->time.seconds < 61) - event->deleted = true; - } - event = event->next; - } - event = dc->events; - while (event) { - if (event->next && event->next->deleted) { - struct event *nextnext = event->next->next; - free(event->next); - event->next = nextnext; - } else { - event = event->next; + for (auto [idx, event]: enumerated_range(dc.events)) { + if (!is_potentially_redundant(event)) + continue; + for (int idx2 = idx - 1; idx2 > 0; --idx2) { + const auto &prev = dc.events[idx2]; + if ((event.time - prev.time).seconds > 60) + break; + if (range_contains(to_delete, idx2)) + continue; + if (prev.name == event.name && prev.flags == event.flags) { + to_delete.push_back(idx); + break; + } } } + // Delete from back to not invalidate indexes + for (auto it = to_delete.rbegin(); it != to_delete.rend(); ++it) + dc.events.erase(dc.events.begin() + *it); } -static int interpolate_depth(struct divecomputer *dc, int idx, int lastdepth, int lasttime, int now) +static int interpolate_depth(struct divecomputer &dc, int idx, int lastdepth, int lasttime, int now) { - int i; int nextdepth = lastdepth; int nexttime = now; - for (i = idx+1; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - if (sample->depth.mm < 0) + for (auto it = dc.samples.begin() + idx; it != dc.samples.end(); ++it) { + if (it->depth.mm < 0) continue; - nextdepth = sample->depth.mm; - nexttime = sample->time.seconds; + nextdepth = it->depth.mm; + nexttime = it->time.seconds; break; } return interpolate(lastdepth, nextdepth, now-lasttime, nexttime-lasttime); } -static void fixup_dc_depths(struct dive *dive, struct divecomputer *dc) +static void fixup_dc_depths(struct dive &dive, struct divecomputer &dc) { - int i; - int maxdepth = dc->maxdepth.mm; + int maxdepth = dc.maxdepth.mm; int lasttime = 0, lastdepth = 0; - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int depth = sample->depth.mm; + for (const auto [idx, sample]: enumerated_range(dc.samples)) { + int time = sample.time.seconds; + int depth = sample.depth.mm; - if (depth < 0) { - depth = interpolate_depth(dc, i, lastdepth, lasttime, time); - sample->depth.mm = depth; + if (depth < 0 && idx + 2 < static_cast(dc.samples.size())) { + depth = interpolate_depth(dc, idx, lastdepth, lasttime, time); + sample.depth.mm = depth; } if (depth > SURFACE_THRESHOLD) { @@ -963,37 +775,32 @@ static void fixup_dc_depths(struct dive *dive, struct divecomputer *dc) lastdepth = depth; lasttime = time; - if (sample->cns > dive->maxcns) - dive->maxcns = sample->cns; + if (sample.cns > dive.maxcns) + dive.maxcns = sample.cns; } - update_depth(&dc->maxdepth, maxdepth); - if (!is_logged(dive) || !is_dc_planner(dc)) - if (maxdepth > dive->maxdepth.mm) - dive->maxdepth.mm = maxdepth; + update_depth(&dc.maxdepth, maxdepth); + if (!dive.is_logged() || !is_dc_planner(&dc)) + if (maxdepth > dive.maxdepth.mm) + dive.maxdepth.mm = maxdepth; } -static void fixup_dc_ndl(struct divecomputer *dc) +static void fixup_dc_ndl(struct divecomputer &dc) { - int i; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - if (sample->ndl.seconds != 0) + for (auto &sample: dc.samples) { + if (sample.ndl.seconds != 0) break; - if (sample->ndl.seconds == 0) - sample->ndl.seconds = -1; + if (sample.ndl.seconds == 0) + sample.ndl.seconds = -1; } } -static void fixup_dc_temp(struct dive *dive, struct divecomputer *dc) +static void fixup_dc_temp(struct dive &dive, struct divecomputer &dc) { - int i; int mintemp = 0, lasttemp = 0; - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int temp = sample->temperature.mkelvin; + for (auto &sample: dc.samples) { + int temp = sample.temperature.mkelvin; if (temp) { /* @@ -1002,7 +809,7 @@ static void fixup_dc_temp(struct dive *dive, struct divecomputer *dc) * the redundant ones. */ if (lasttemp == temp) - sample->temperature.mkelvin = 0; + sample.temperature = 0_K; else lasttemp = temp; @@ -1010,31 +817,29 @@ static void fixup_dc_temp(struct dive *dive, struct divecomputer *dc) mintemp = temp; } - update_min_max_temperatures(dive, sample->temperature); + update_min_max_temperatures(dive, sample.temperature); } - update_temperature(&dc->watertemp, mintemp); - update_min_max_temperatures(dive, dc->watertemp); + update_temperature(&dc.watertemp, mintemp); + update_min_max_temperatures(dive, dc.watertemp); } /* Remove redundant pressure information */ -static void simplify_dc_pressures(struct divecomputer *dc) +static void simplify_dc_pressures(struct divecomputer &dc) { - int i; int lastindex[2] = { -1, -1 }; int lastpressure[2] = { 0 }; - for (i = 0; i < dc->samples; i++) { + for (auto &sample: dc.samples) { int j; - struct sample *sample = dc->sample + i; for (j = 0; j < MAX_SENSORS; j++) { - int pressure = sample->pressure[j].mbar; - int index = sample->sensor[j]; + int pressure = sample.pressure[j].mbar; + int index = sample.sensor[j]; if (index == lastindex[j]) { /* Remove duplicate redundant pressure information */ if (pressure == lastpressure[j]) - sample->pressure[j].mbar = 0; + sample.pressure[j] = 0_bar; } lastindex[j] = index; lastpressure[j] = pressure; @@ -1043,21 +848,21 @@ static void simplify_dc_pressures(struct divecomputer *dc) } /* Do we need a sensor -> cylinder mapping? */ -static void fixup_start_pressure(struct dive *dive, int idx, pressure_t p) +static void fixup_start_pressure(struct dive &dive, int idx, pressure_t p) { - if (idx >= 0 && idx < dive->cylinders.nr) { - cylinder_t *cyl = get_cylinder(dive, idx); - if (p.mbar && !cyl->sample_start.mbar) - cyl->sample_start = p; + if (idx >= 0 && static_cast(idx) < dive.cylinders.size()) { + cylinder_t &cyl = dive.cylinders[idx]; + if (p.mbar && !cyl.sample_start.mbar) + cyl.sample_start = p; } } -static void fixup_end_pressure(struct dive *dive, int idx, pressure_t p) +static void fixup_end_pressure(struct dive &dive, int idx, pressure_t p) { - if (idx >= 0 && idx < dive->cylinders.nr) { - cylinder_t *cyl = get_cylinder(dive, idx); - if (p.mbar && !cyl->sample_end.mbar) - cyl->sample_end = p; + if (idx >= 0 && static_cast(idx) < dive.cylinders.size()) { + cylinder_t &cyl = dive.cylinders[idx]; + if (p.mbar && !cyl.sample_end.mbar) + cyl.sample_end = p; } } @@ -1074,32 +879,24 @@ static void fixup_end_pressure(struct dive *dive, int idx, pressure_t p) * for computers like the Uemis Zurich that end up saving * quite a bit of samples after the dive has ended). */ -static void fixup_dive_pressures(struct dive *dive, struct divecomputer *dc) +static void fixup_dive_pressures(struct dive &dive, struct divecomputer &dc) { - int i; - /* Walk the samples from the beginning to find starting pressures.. */ - for (i = 0; i < dc->samples; i++) { - int idx; - struct sample *sample = dc->sample + i; - - if (sample->depth.mm < SURFACE_THRESHOLD) + for (auto &sample: dc.samples) { + if (sample.depth.mm < SURFACE_THRESHOLD) continue; - for (idx = 0; idx < MAX_SENSORS; idx++) - fixup_start_pressure(dive, sample->sensor[idx], sample->pressure[idx]); + for (int idx = 0; idx < MAX_SENSORS; idx++) + fixup_start_pressure(dive, sample.sensor[idx], sample.pressure[idx]); } /* ..and from the end for ending pressures */ - for (i = dc->samples; --i >= 0; ) { - int idx; - struct sample *sample = dc->sample + i; - - if (sample->depth.mm < SURFACE_THRESHOLD) + for (auto it = dc.samples.rbegin(); it != dc.samples.rend(); ++it) { + if (it->depth.mm < SURFACE_THRESHOLD) continue; - for (idx = 0; idx < MAX_SENSORS; idx++) - fixup_end_pressure(dive, sample->sensor[idx], sample->pressure[idx]); + for (int idx = 0; idx < MAX_SENSORS; idx++) + fixup_end_pressure(dive, it->sensor[idx], it->pressure[idx]); } simplify_dc_pressures(dc); @@ -1108,84 +905,76 @@ static void fixup_dive_pressures(struct dive *dive, struct divecomputer *dc) /* * Match a gas change event against the cylinders we have */ -static bool validate_gaschange(struct dive *dive, struct event *event) +static bool validate_gaschange(struct dive &dive, struct event &event) { int index; int o2, he, value; /* We'll get rid of the per-event gasmix, but for now sanitize it */ - if (gasmix_is_air(event->gas.mix)) - event->gas.mix.o2.permille = 0; + if (gasmix_is_air(event.gas.mix)) + event.gas.mix.o2 = 0_percent; /* Do we already have a cylinder index for this gasmix? */ - if (event->gas.index >= 0) + if (event.gas.index >= 0) return true; - index = find_best_gasmix_match(event->gas.mix, &dive->cylinders); - if (index < 0 || index >= dive->cylinders.nr) + index = find_best_gasmix_match(event.gas.mix, dive.cylinders); + if (index < 0 || static_cast(index) >= dive.cylinders.size()) return false; /* Fix up the event to have the right information */ - event->gas.index = index; - event->gas.mix = get_cylinder(dive, index)->gasmix; + event.gas.index = index; + event.gas.mix = dive.cylinders[index].gasmix; /* Convert to odd libdivecomputer format */ - o2 = get_o2(event->gas.mix); - he = get_he(event->gas.mix); + o2 = get_o2(event.gas.mix); + he = get_he(event.gas.mix); o2 = (o2 + 5) / 10; he = (he + 5) / 10; value = o2 + (he << 16); - event->value = value; + event.value = value; if (he) - event->type = SAMPLE_EVENT_GASCHANGE2; + event.type = SAMPLE_EVENT_GASCHANGE2; return true; } /* Clean up event, return true if event is ok, false if it should be dropped as bogus */ -static bool validate_event(struct dive *dive, struct event *event) +static bool validate_event(struct dive &dive, struct event &event) { - if (event_is_gaschange(event)) + if (event.is_gaschange()) return validate_gaschange(dive, event); return true; } -static void fixup_dc_gasswitch(struct dive *dive, struct divecomputer *dc) +static void fixup_dc_gasswitch(struct dive &dive, struct divecomputer &dc) { - struct event **evp, *event; - - evp = &dc->events; - while ((event = *evp) != NULL) { - if (validate_event(dive, event)) { - evp = &event->next; - continue; - } - - /* Delete this event and try the next one */ - *evp = event->next; - } + // erase-remove idiom + auto &events = dc.events; + events.erase(std::remove_if(events.begin(), events.end(), + [&dive](auto &ev) { return !validate_event(dive, ev); }), + events.end()); } -static void fixup_no_o2sensors(struct divecomputer *dc) +static void fixup_no_o2sensors(struct divecomputer &dc) { // Its only relevant to look for sensor values on CCR and PSCR dives without any no_o2sensors recorded. - if (dc->no_o2sensors != 0 || !(dc->divemode == CCR || dc->divemode == PSCR)) + if (dc.no_o2sensors != 0 || !(dc.divemode == CCR || dc.divemode == PSCR)) return; - for (int i = 0; i < dc->samples; i++) { - int nsensor = 0, j; - struct sample *s = dc->sample + i; + for (const auto &sample: dc.samples) { + int nsensor = 0; // How many o2 sensors can we find in this sample? - for (j = 0; j < MAX_O2_SENSORS; j++) - if (s->o2sensor[j].mbar) + for (int j = 0; j < MAX_O2_SENSORS; j++) + if (sample.o2sensor[j].mbar) nsensor++; // If we fond more than the previous found max, record it. - if (nsensor > dc->no_o2sensors) - dc->no_o2sensors = nsensor; + if (nsensor > dc.no_o2sensors) + dc.no_o2sensors = nsensor; // Already found the maximum posible amount. if (nsensor == MAX_O2_SENSORS) @@ -1193,25 +982,24 @@ static void fixup_no_o2sensors(struct divecomputer *dc) } } -static void fixup_dc_sample_sensors(struct dive *dive, struct divecomputer *dc) +static void fixup_dc_sample_sensors(struct dive &dive, struct divecomputer &dc) { unsigned long sensor_mask = 0; - for (int i = 0; i < dc->samples; i++) { - struct sample *s = dc->sample + i; + for (auto &sample: dc.samples) { for (int j = 0; j < MAX_SENSORS; j++) { - int sensor = s->sensor[j]; + int sensor = sample.sensor[j]; // No invalid sensor ID's, please if (sensor < 0 || sensor > MAX_SENSORS) { - s->sensor[j] = NO_SENSOR; - s->pressure[j].mbar = 0; + sample.sensor[j] = NO_SENSOR; + sample.pressure[j] = 0_bar; continue; } // Don't bother tracking sensors with no data - if (!s->pressure[j].mbar) { - s->sensor[j] = NO_SENSOR; + if (!sample.pressure[j].mbar) { + sample.sensor[j] = NO_SENSOR; continue; } @@ -1221,16 +1009,16 @@ static void fixup_dc_sample_sensors(struct dive *dive, struct divecomputer *dc) } // Ignore the sensors we have cylinders for - sensor_mask >>= dive->cylinders.nr; + sensor_mask >>= dive.cylinders.size(); // Do we need to add empty cylinders? while (sensor_mask) { - add_empty_cylinder(&dive->cylinders); + dive.cylinders.emplace_back(); sensor_mask >>= 1; } } -static void fixup_dive_dc(struct dive *dive, struct divecomputer *dc) +static void fixup_dive_dc(struct dive &dive, struct divecomputer &dc) { /* Fixup duration and mean depth */ fixup_dc_duration(dc); @@ -1259,111 +1047,101 @@ static void fixup_dive_dc(struct dive *dive, struct divecomputer *dc) fixup_no_o2sensors(dc); /* If there are no samples, generate a fake profile based on depth and time */ - if (!dc->samples) - fake_dc(dc); + if (dc.samples.empty()) + fake_dc(&dc); } -extern "C" struct dive *fixup_dive(struct dive *dive) +void dive::fixup_no_cylinder() { - int i; - struct divecomputer *dc; - - sanitize_cylinder_info(dive); - dive->maxcns = dive->cns; + sanitize_cylinder_info(*this); + maxcns = cns; /* * Use the dive's temperatures for minimum and maximum in case * we do not have temperatures recorded by DC. */ - update_min_max_temperatures(dive, dive->watertemp); + update_min_max_temperatures(*this, watertemp); - for_each_dc (dive, dc) - fixup_dive_dc(dive, dc); + for (auto &dc: dcs) + fixup_dive_dc(*this, dc); - fixup_water_salinity(dive); - if (!dive->surface_pressure.mbar) - fixup_surface_pressure(dive); - fixup_meandepth(dive); - fixup_duration(dive); - fixup_watertemp(dive); - fixup_airtemp(dive); - for (i = 0; i < dive->cylinders.nr; i++) { - cylinder_t *cyl = get_cylinder(dive, i); - add_cylinder_description(&cyl->type); - if (same_rounded_pressure(cyl->sample_start, cyl->start)) - cyl->start.mbar = 0; - if (same_rounded_pressure(cyl->sample_end, cyl->end)) - cyl->end.mbar = 0; + fixup_water_salinity(*this); + if (!surface_pressure.mbar) + fixup_surface_pressure(*this); + fixup_meandepth(*this); + fixup_duration(*this); + fixup_watertemp(*this); + fixup_airtemp(*this); + for (auto &cyl: cylinders) { + add_cylinder_description(cyl.type); + if (same_rounded_pressure(cyl.sample_start, cyl.start)) + cyl.start = 0_bar; + if (same_rounded_pressure(cyl.sample_end, cyl.end)) + cyl.end = 0_bar; } - update_cylinder_related_info(dive); - for (i = 0; i < dive->weightsystems.nr; i++) { - weightsystem_t *ws = &dive->weightsystems.weightsystems[i]; + + for (auto &ws: weightsystems) add_weightsystem_description(ws); - } - /* we should always have a uniq ID as that gets assigned during alloc_dive(), - * but we want to make sure... */ - if (!dive->id) - dive->id = dive_getUniqID(); - - return dive; } /* Don't pick a zero for MERGE_MIN() */ -#define MERGE_MAX(res, a, b, n) res->n = std::max(a->n, b->n) -#define MERGE_MIN(res, a, b, n) res->n = (a->n) ? (b->n) ? std::min(a->n, b->n) : (a->n) : (b->n) -#define MERGE_TXT(res, a, b, n, sep) res->n = merge_text(a->n, b->n, sep) -#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n +#define MERGE_MAX(res, a, b, n) res->n = std::max(a.n, b.n) +#define MERGE_MIN(res, a, b, n) res->n = (a.n) ? (b.n) ? std::min(a.n, b.n) : (a.n) : (b.n) +#define MERGE_TXT(res, a, b, n, sep) res->n = merge_text(a.n, b.n, sep) +#define MERGE_NONZERO(res, a, b, n) (res)->n = (a).n ? (a).n : (b).n /* - * This is like add_sample(), but if the distance from the last sample + * This is like append_sample(), but if the distance from the last sample * is excessive, we add two surface samples in between. * * This is so that if you merge two non-overlapping dives, we make sure * that the time in between the dives is at the surface, not some "last * sample that happened to be at a depth of 1.2m". */ -static void merge_one_sample(const struct sample *sample, int time, struct divecomputer *dc) +static void merge_one_sample(const struct sample &sample, struct divecomputer &dc) { - int last = dc->samples - 1; - if (last >= 0) { - struct sample *prev = dc->sample + last; - int last_time = prev->time.seconds; - int last_depth = prev->depth.mm; + if (!dc.samples.empty()) { + const struct sample &prev = dc.samples.back(); + int last_time = prev.time.seconds; + int last_depth = prev.depth.mm; /* * Only do surface events if the samples are more than * a minute apart, and shallower than 5m */ - if (time > last_time + 60 && last_depth < 5000) { + if (sample.time.seconds > last_time + 60 && last_depth < 5000) { struct sample surface; /* Init a few values from prev sample to avoid useless info in XML */ - surface.bearing.degrees = prev->bearing.degrees; - surface.ndl.seconds = prev->ndl.seconds; + surface.bearing.degrees = prev.bearing.degrees; + surface.ndl = prev.ndl; + surface.time.seconds = last_time + 20; - add_sample(&surface, last_time + 20, dc); - add_sample(&surface, time - 20, dc); + append_sample(surface, &dc); + + surface.time = sample.time - 20_sec; + append_sample(surface, &dc); } } - add_sample(sample, time, dc); + append_sample(sample, &dc); } -static void renumber_last_sample(struct divecomputer *dc, const int mapping[]); -static void sample_renumber(struct sample *s, int i, const int mapping[]); +static void renumber_last_sample(struct divecomputer &dc, const int mapping[]); +static void sample_renumber(struct sample &s, const struct sample *next, const int mapping[]); /* * Merge samples. Dive 'a' is "offset" seconds before Dive 'b' */ -static void merge_samples(struct divecomputer *res, - const struct divecomputer *a, const struct divecomputer *b, +static void merge_samples(struct divecomputer &res, + const struct divecomputer &a, const struct divecomputer &b, const int *cylinders_map_a, const int *cylinders_map_b, int offset) { - int asamples = a->samples; - int bsamples = b->samples; - struct sample *as = a->sample; - struct sample *bs = b->sample; + auto as = a.samples.begin(); + auto bs = b.samples.begin(); + auto a_end = a.samples.end(); + auto b_end = b.samples.end(); /* * We want a positive sample offset, so that sample @@ -1373,27 +1151,15 @@ static void merge_samples(struct divecomputer *res, * the reverse offset. */ if (offset < 0) { - const int *cylinders_map_tmp; offset = -offset; - asamples = bsamples; - bsamples = a->samples; - as = bs; - bs = a->sample; - cylinders_map_tmp = cylinders_map_a; - cylinders_map_a = cylinders_map_b; - cylinders_map_b = cylinders_map_tmp; + std::swap(as, bs); + std::swap(a_end, b_end); + std::swap(cylinders_map_a, cylinders_map_b); } for (;;) { - int j; - int at, bt; - struct sample sample; - - if (!res) - return; - - at = asamples ? as->time.seconds : -1; - bt = bsamples ? bs->time.seconds + offset : -1; + int at = as != a_end ? as->time.seconds : -1; + int bt = bs != b_end ? bs->time.seconds + offset : -1; /* No samples? All done! */ if (at < 0 && bt < 0) @@ -1402,20 +1168,20 @@ static void merge_samples(struct divecomputer *res, /* Only samples from a? */ if (bt < 0) { add_sample_a: - merge_one_sample(as, at, res); + merge_one_sample(*as, res); renumber_last_sample(res, cylinders_map_a); as++; - asamples--; continue; } /* Only samples from b? */ if (at < 0) { add_sample_b: - merge_one_sample(bs, bt, res); + struct sample sample = *bs; + sample.time.seconds += offset; + merge_one_sample(sample, res); renumber_last_sample(res, cylinders_map_b); bs++; - bsamples--; continue; } @@ -1425,13 +1191,14 @@ static void merge_samples(struct divecomputer *res, goto add_sample_b; /* same-time sample: add a merged sample. Take the non-zero ones */ - sample = *bs; - sample_renumber(&sample, 0, cylinders_map_b); + struct sample sample = *bs; + sample.time.seconds += offset; + sample_renumber(sample, nullptr, cylinders_map_b); if (as->depth.mm) sample.depth = as->depth; if (as->temperature.mkelvin) sample.temperature = as->temperature; - for (j = 0; j < MAX_SENSORS; ++j) { + for (int j = 0; j < MAX_SENSORS; ++j) { int sensor_id; sensor_id = cylinders_map_a[as->sensor[j]]; @@ -1456,35 +1223,16 @@ static void merge_samples(struct divecomputer *res, if (as->in_deco) sample.in_deco = true; - merge_one_sample(&sample, at, res); + merge_one_sample(sample, res); as++; bs++; - asamples--; - bsamples--; } } -/* - * Does the extradata key/value pair already exist in the - * supplied dive computer data? - * - * This is not hugely efficient (with the whole "do this for - * every value you merge" it's O(n**2)) but it's not like we - * have very many extra_data entries per dive computer anyway. - */ -static bool extra_data_exists(const struct extra_data *ed, const struct divecomputer *dc) +static bool operator==(const struct extra_data &e1, const struct extra_data &e2) { - const struct extra_data *p; - - for (p = dc->extra_data; p; p = p->next) { - if (strcmp(p->key, ed->key)) - continue; - if (strcmp(p->value, ed->value)) - continue; - return true; - } - return false; + return std::tie(e1.key, e1.value) == std::tie(e2.key, e2.value); } /* @@ -1492,133 +1240,109 @@ static bool extra_data_exists(const struct extra_data *ed, const struct divecomp * * The extra data from 'a' has already been copied into 'res'. So * we really should just copy over the data from 'b' too. + * + * This is not hugely efficient (with the whole "check this for + * every value you merge" it's O(n**2)) but it's not like we + * have very many extra_data entries per dive computer anyway. */ -static void merge_extra_data(struct divecomputer *res, - const struct divecomputer *a, const struct divecomputer *b) +static void merge_extra_data(struct divecomputer &res, + const struct divecomputer &a, const struct divecomputer &b) { - struct extra_data **ed, *src; - - // Find the place to add things in the result - ed = &res->extra_data; - while (*ed) - ed = &(*ed)->next; - - for (src = b->extra_data; src; src = src->next) { - if (extra_data_exists(src, a)) + for (auto &ed: b.extra_data) { + if (range_contains(a.extra_data, ed)) continue; - *ed = (extra_data *)malloc(sizeof(struct extra_data)); - if (!*ed) - break; - copy_extra_data(src, *ed); - ed = &(*ed)->next; - } - // Terminate the result list - *ed = NULL; + res.extra_data.push_back(ed); + } } -static char *merge_text(const char *a, const char *b, const char *sep) +static std::string merge_text(const std::string &a, const std::string &b, const char *sep) { - char *res; - if (!a && !b) - return NULL; - if (!a || !*a) - return copy_string(b); - if (!b || !*b) - return strdup(a); - if (!strcmp(a, b)) - return copy_string(a); - res = (char *)malloc(strlen(a) + strlen(b) + 32); - if (!res) - return (char *)a; - sprintf(res, "%s%s%s", a, sep, b); - return res; + if (a.empty()) + return b; + if (b.empty()) + return a; + if (a == b) + return a; + return a + sep + b; } #define SORT(a, b) \ if (a != b) \ return a < b ? -1 : 1 -#define SORT_FIELD(a, b, field) SORT(a->field, b->field) +#define SORT_FIELD(a, b, field) SORT(a.field, b.field) -static int sort_event(const struct event *a, const struct event *b, int time_a, int time_b) +static int sort_event(const struct event &a, const struct event &b, int time_a, int time_b) { SORT(time_a, time_b); SORT_FIELD(a, b, type); SORT_FIELD(a, b, flags); SORT_FIELD(a, b, value); - return strcmp(a->name, b->name); + return a.name.compare(b.name); } static int same_gas(const struct event *a, const struct event *b) { - if (a->type == b->type && a->flags == b->flags && a->value == b->value && !strcmp(a->name, b->name) && + if (a->type == b->type && a->flags == b->flags && a->value == b->value && a->name == b->name && same_gasmix(a->gas.mix, b->gas.mix)) { return true; } return false; } -static void event_renumber(struct event *ev, const int mapping[]); -static void add_initial_gaschange(struct dive *dive, struct divecomputer *dc, int offset, int idx); +static void event_renumber(struct event &ev, const int mapping[]); +static void add_initial_gaschange(struct dive &dive, struct divecomputer &dc, int offset, int idx); -static void merge_events(struct dive *d, struct divecomputer *res, - const struct divecomputer *src1, const struct divecomputer *src2, +static void merge_events(struct dive &d, struct divecomputer &res, + const struct divecomputer &src1_in, const struct divecomputer &src2_in, const int *cylinders_map1, const int *cylinders_map2, int offset) { - const struct event *a, *b; - struct event **p = &res->events; const struct event *last_gas = NULL; /* Always use positive offsets */ + auto src1 = &src1_in; + auto src2 = &src2_in; if (offset < 0) { - const struct divecomputer *tmp; - const int *cylinders_map_tmp; - offset = -offset; - tmp = src1; - src1 = src2; - src2 = tmp; - - cylinders_map_tmp = cylinders_map1; - cylinders_map1 = cylinders_map2; - cylinders_map2 = cylinders_map_tmp; + std::swap(src1, src2); + std::swap(cylinders_map1, cylinders_map2); // The pointers, not the contents are swapped. } - a = src1->events; - b = src2->events; + auto a = src1->events.begin(); + auto b = src2->events.begin(); - while (a || b) { - int s; + while (a != src1->events.end() || b != src2->events.end()) { + int s = 0; const struct event *pick; const int *cylinders_map; int event_offset; - if (!b) + if (b == src2->events.end()) goto pick_a; - if (!a) + if (a == src1->events.end()) goto pick_b; - s = sort_event(a, b, a->time.seconds, b->time.seconds + offset); + s = sort_event(*a, *b, a->time.seconds, b->time.seconds + offset); /* Identical events? Just skip one of them (we skip a) */ if (!s) { - a = a->next; + ++a; continue; } /* Otherwise, pick the one that sorts first */ if (s < 0) { pick_a: - pick = a; - a = a->next; + pick = &*a; + ++a; event_offset = 0; cylinders_map = cylinders_map1; } else { pick_b: - pick = b; - b = b->next; + pick = &*b; + ++b; event_offset = offset; cylinders_map = cylinders_map2; } @@ -1627,17 +1351,16 @@ pick_b: * If that's a gas-change that matches the previous * gas change, we'll just skip it */ - if (event_is_gaschange(pick)) { + if (pick->is_gaschange()) { if (last_gas && same_gas(pick, last_gas)) continue; last_gas = pick; } /* Add it to the target list */ - *p = clone_event(pick); - (*p)->time.seconds += event_offset; - event_renumber(*p, cylinders_map); - p = &(*p)->next; + res.events.push_back(*pick); + res.events.back().time.seconds += event_offset; + event_renumber(res.events.back(), cylinders_map); } /* If the initial cylinder of a divecomputer was remapped, add a gas change event to that cylinder */ @@ -1647,94 +1370,71 @@ pick_b: add_initial_gaschange(d, res, offset, cylinders_map2[0]); } -/* get_cylinder_idx_by_use(): Find the index of the first cylinder with a particular CCR use type. - * The index returned corresponds to that of the first cylinder with a cylinder_use that - * equals the appropriate enum value [oxygen, diluent, bailout] given by cylinder_use_type. - * A negative number returned indicates that a match could not be found. - * Call parameters: dive = the dive being processed - * cylinder_use_type = an enum, one of {oxygen, diluent, bailout} */ -extern "C" int get_cylinder_idx_by_use(const struct dive *dive, enum cylinderuse cylinder_use_type) -{ - int cylinder_index; - for (cylinder_index = 0; cylinder_index < dive->cylinders.nr; cylinder_index++) { - if (get_cylinder(dive, cylinder_index)->cylinder_use == cylinder_use_type) - return cylinder_index; // return the index of the cylinder with that cylinder use type - } - return -1; // negative number means cylinder_use_type not found in list of cylinders -} - /* Force an initial gaschange event to the (old) gas #0 */ -static void add_initial_gaschange(struct dive *dive, struct divecomputer *dc, int offset, int idx) +static void add_initial_gaschange(struct dive &dive, struct divecomputer &dc, int offset, int idx) { /* if there is a gaschange event up to 30 sec after the initial event, * refrain from adding the initial event */ - const struct event *ev = dc->events; - while(ev && (ev = get_next_event(ev, "gaschange")) != NULL) { - if (ev->time.seconds > offset + 30) + gasmix_loop loop(dive, dc); + while (loop.has_next()) { + int time = loop.next().second; + if (time > offset + 30) break; - else if (ev->time.seconds > offset) + else if (time > offset) return; - ev = ev->next; } /* Old starting gas mix */ - add_gas_switch_event(dive, dc, offset, idx); + add_gas_switch_event(&dive, &dc, offset, idx); } -static void sample_renumber(struct sample *s, int i, const int mapping[]) +static void sample_renumber(struct sample &s, const struct sample *prev, const int mapping[]) { - int j; - - for (j = 0; j < MAX_SENSORS; j++) { + for (int j = 0; j < MAX_SENSORS; j++) { int sensor = -1; - if (s->sensor[j] != NO_SENSOR) - sensor = mapping[s->sensor[j]]; + if (s.sensor[j] != NO_SENSOR) + sensor = mapping[s.sensor[j]]; if (sensor == -1) { // Remove sensor and gas pressure info - if (i == 0) { - s->sensor[j] = 0; - s->pressure[j].mbar = 0; + if (!prev) { + s.sensor[j] = 0; + s.pressure[j] = 0_bar; } else { - s->sensor[j] = s[-1].sensor[j]; - s->pressure[j].mbar = s[-1].pressure[j].mbar; + s.sensor[j] = prev->sensor[j]; + s.pressure[j] = prev->pressure[j]; } } else { - s->sensor[j] = sensor; + s.sensor[j] = sensor; } } } -static void renumber_last_sample(struct divecomputer *dc, const int mapping[]) +static void renumber_last_sample(struct divecomputer &dc, const int mapping[]) { - int idx; - - if (dc->samples <= 0) + if (dc.samples.empty()) return; - idx = dc->samples - 1; - sample_renumber(dc->sample + idx, idx, mapping); + sample *prev = dc.samples.size() > 1 ? &dc.samples[dc.samples.size() - 2] : nullptr; + sample_renumber(dc.samples.back(), prev, mapping); } -static void event_renumber(struct event *ev, const int mapping[]) +static void event_renumber(struct event &ev, const int mapping[]) { - if (!event_is_gaschange(ev)) + if (!ev.is_gaschange()) return; - if (ev->gas.index < 0) + if (ev.gas.index < 0) return; - ev->gas.index = mapping[ev->gas.index]; + ev.gas.index = mapping[ev.gas.index]; } -static void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, const int mapping[]) +static void dc_cylinder_renumber(struct dive &dive, struct divecomputer &dc, const int mapping[]) { - int i; - struct event *ev; - /* Remap or delete the sensor indices */ - for (i = 0; i < dc->samples; i++) - sample_renumber(dc->sample + i, i, mapping); + for (auto [i, sample]: enumerated_range(dc.samples)) + sample_renumber(sample, i > 0 ? &dc.samples[i-1] : nullptr, mapping); /* Remap the gas change indices */ - for (ev = dc->events; ev; ev = ev->next) + for (auto &ev: dc.events) event_renumber(ev, mapping); /* If the initial cylinder of a dive was remapped, add a gas change event to that cylinder */ @@ -1750,21 +1450,20 @@ static void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, con * Also note that we assume that the initial cylinder is cylinder 0, * so if that got renamed, we need to create a fake gas change event */ -extern "C" void cylinder_renumber(struct dive *dive, int mapping[]) +void cylinder_renumber(struct dive &dive, int mapping[]) { - struct divecomputer *dc; - for_each_dc (dive, dc) + for (auto &dc: dive.dcs) dc_cylinder_renumber(dive, dc, mapping); } -extern "C" int same_gasmix_cylinder(const cylinder_t *cyl, int cylid, const struct dive *dive, bool check_unused) +int same_gasmix_cylinder(const cylinder_t &cyl, int cylid, const struct dive *dive, bool check_unused) { - struct gasmix mygas = cyl->gasmix; - for (int i = 0; i < dive->cylinders.nr; i++) { + struct gasmix mygas = cyl.gasmix; + for (auto [i, cyl]: enumerated_range(dive->cylinders)) { if (i == cylid) continue; - struct gasmix gas2 = get_cylinder(dive, i)->gasmix; - if (gasmix_distance(mygas, gas2) == 0 && (is_cylinder_used(dive, i) || check_unused)) + struct gasmix gas2 = cyl.gasmix; + if (gasmix_distance(mygas, gas2) == 0 && (dive->is_cylinder_used(i) || check_unused)) return i; } return -1; @@ -1789,22 +1488,17 @@ static int different_manual_pressures(const cylinder_t *a, const cylinder_t *b) * same cylinder use (ie OC/Diluent/Oxygen), and if pressures * have been added manually they need to match. */ -static int match_cylinder(const cylinder_t *cyl, const struct dive *dive, const bool try_match[]) +static int match_cylinder(const cylinder_t *cyl, const struct dive &dive, const bool try_match[]) { - int i; - - for (i = 0; i < dive->cylinders.nr; i++) { - const cylinder_t *target; - + for (auto [i, target]: enumerated_range(dive.cylinders)) { if (!try_match[i]) continue; - target = get_cylinder(dive, i); - if (!same_gasmix(cyl->gasmix, target->gasmix)) + if (!same_gasmix(cyl->gasmix, target.gasmix)) continue; - if (cyl->cylinder_use != target->cylinder_use) + if (cyl->cylinder_use != target.cylinder_use) continue; - if (different_manual_pressures(cyl, target)) + if (different_manual_pressures(cyl, &target)) continue; /* open question: Should we check sizes too? */ @@ -1853,11 +1547,11 @@ static pressure_t merge_pressures(pressure_t a, pressure_t sample_a, pressure_t static void merge_one_cylinder(cylinder_t *a, const cylinder_t *b) { if (!a->type.size.mliter) - a->type.size.mliter = b->type.size.mliter; + a->type.size = b->type.size; if (!a->type.workingpressure.mbar) - a->type.workingpressure.mbar = b->type.workingpressure.mbar; - if (empty_string(a->type.description)) - a->type.description = copy_string(b->type.description); + a->type.workingpressure = b->type.workingpressure; + if (a->type.description.empty()) + a->type.description = b->type.description; /* If either cylinder has manually entered pressures, try to merge them. * Use pressures from divecomputer samples if only one cylinder has such a value. @@ -1868,38 +1562,67 @@ static void merge_one_cylinder(cylinder_t *a, const cylinder_t *b) a->end = merge_pressures(a->end, a->sample_end, b->end, b->sample_end, true); /* Really? */ - a->gas_used.mliter += b->gas_used.mliter; - a->deco_gas_used.mliter += b->deco_gas_used.mliter; + a->gas_used += b->gas_used; + a->deco_gas_used += b->deco_gas_used; a->bestmix_o2 = a->bestmix_o2 && b->bestmix_o2; a->bestmix_he = a->bestmix_he && b->bestmix_he; } -static bool cylinder_has_data(const cylinder_t *cyl) +static bool cylinder_has_data(const cylinder_t &cyl) { - return !cyl->type.size.mliter && - !cyl->type.workingpressure.mbar && - !cyl->type.description && - !cyl->gasmix.o2.permille && - !cyl->gasmix.he.permille && - !cyl->start.mbar && - !cyl->end.mbar && - !cyl->sample_start.mbar && - !cyl->sample_end.mbar && - !cyl->gas_used.mliter && - !cyl->deco_gas_used.mliter; + return !cyl.type.size.mliter && + !cyl.type.workingpressure.mbar && + cyl.type.description.empty() && + !cyl.gasmix.o2.permille && + !cyl.gasmix.he.permille && + !cyl.start.mbar && + !cyl.end.mbar && + !cyl.sample_start.mbar && + !cyl.sample_end.mbar && + !cyl.gas_used.mliter && + !cyl.deco_gas_used.mliter; } static bool cylinder_in_use(const struct dive *dive, int idx) { - if (idx < 0 || idx >= dive->cylinders.nr) + if (idx < 0 || static_cast(idx) >= dive->cylinders.size()) return false; /* This tests for gaschange events or pressure changes */ - if (is_cylinder_used(dive, idx) || prefs.include_unused_tanks) + if (dive->is_cylinder_used(idx) || prefs.include_unused_tanks) return true; /* This tests for typenames or gas contents */ - return cylinder_has_data(get_cylinder(dive, idx)); + return cylinder_has_data(dive->cylinders[idx]); +} + +bool is_cylinder_use_appropriate(const struct divecomputer &dc, const cylinder_t &cyl, bool allowNonUsable) +{ + switch (cyl.cylinder_use) { + case OC_GAS: + if (dc.divemode == FREEDIVE) + return false; + + break; + case OXYGEN: + if (!allowNonUsable) + return false; + + case DILUENT: + if (dc.divemode != CCR) + return false; + + break; + case NOT_USED: + if (!allowNonUsable) + return false; + + break; + default: + return false; + } + + return true; } /* @@ -1912,65 +1635,64 @@ static bool cylinder_in_use(const struct dive *dive, int idx) * * For each dive, a cylinder-renumbering table is returned. */ -static void merge_cylinders(struct dive *res, const struct dive *a, const struct dive *b, +static void merge_cylinders(struct dive &res, const struct dive &a, const struct dive &b, int mapping_a[], int mapping_b[]) { - int i; - int max_cylinders = a->cylinders.nr + b->cylinders.nr; + size_t max_cylinders = a.cylinders.size() + b.cylinders.size(); auto used_in_a = std::make_unique(max_cylinders); auto used_in_b = std::make_unique(max_cylinders); auto try_to_match = std::make_unique(max_cylinders); std::fill(try_to_match.get(), try_to_match.get() + max_cylinders, false); /* First, clear all cylinders in destination */ - clear_cylinder_table(&res->cylinders); + res.cylinders.clear(); /* Clear all cylinder mappings */ - std::fill(mapping_a, mapping_a + a->cylinders.nr, -1); - std::fill(mapping_b, mapping_b + b->cylinders.nr, -1); + std::fill(mapping_a, mapping_a + a.cylinders.size(), -1); + std::fill(mapping_b, mapping_b + b.cylinders.size(), -1); /* Calculate usage map of cylinders, clear matching map */ - for (i = 0; i < max_cylinders; i++) { - used_in_a[i] = cylinder_in_use(a, i); - used_in_b[i] = cylinder_in_use(b, i); + for (size_t i = 0; i < max_cylinders; i++) { + used_in_a[i] = cylinder_in_use(&a, i); + used_in_b[i] = cylinder_in_use(&b, i); } /* * For each cylinder in 'a' that is used, copy it to 'res'. * These are also potential matches for 'b' to use. */ - for (i = 0; i < max_cylinders; i++) { - int res_nr = res->cylinders.nr; + for (size_t i = 0; i < max_cylinders; i++) { + size_t res_nr = res.cylinders.size(); if (!used_in_a[i]) continue; - mapping_a[i] = res_nr; + mapping_a[i] = static_cast(res_nr); try_to_match[res_nr] = true; - add_cloned_cylinder(&res->cylinders, *get_cylinder(a, i)); + res.cylinders.push_back(a.cylinders[i]); } /* * For each cylinder in 'b' that is used, try to match it * with an existing cylinder in 'res' from 'a' */ - for (i = 0; i < b->cylinders.nr; i++) { + for (size_t i = 0; i < b.cylinders.size(); i++) { int j; if (!used_in_b[i]) continue; - j = match_cylinder(get_cylinder(b, i), res, try_to_match.get()); + j = match_cylinder(b.get_cylinder(i), res, try_to_match.get()); /* No match? Add it to the result */ if (j < 0) { - int res_nr = res->cylinders.nr; - mapping_b[i] = res_nr; - add_cloned_cylinder(&res->cylinders, *get_cylinder(b, i)); + size_t res_nr = res.cylinders.size(); + mapping_b[i] = static_cast(res_nr); + res.cylinders.push_back(b.cylinders[i]); continue; } /* Otherwise, merge the result to the one we found */ mapping_b[i] = j; - merge_one_cylinder(get_cylinder(res,j), get_cylinder(b, i)); + merge_one_cylinder(res.get_cylinder(j), b.get_cylinder(i)); /* Don't match the same target more than once */ try_to_match[j] = false; @@ -1978,92 +1700,46 @@ static void merge_cylinders(struct dive *res, const struct dive *a, const struct } /* Check whether a weightsystem table contains a given weightsystem */ -static bool has_weightsystem(const struct weightsystem_table *t, const weightsystem_t w) +static bool has_weightsystem(const weightsystem_table &t, const weightsystem_t &w) { - int i; - for (i = 0; i < t->nr; i++) { - if (same_weightsystem(w, t->weightsystems[i])) - return true; - } - return false; + return any_of(t.begin(), t.end(), [&w] (auto &w2) { return w == w2; }); } -static void merge_equipment(struct dive *res, const struct dive *a, const struct dive *b) +static void merge_equipment(struct dive &res, const struct dive &a, const struct dive &b) { - int i; - for (i = 0; i < a->weightsystems.nr; i++) { - if (!has_weightsystem(&res->weightsystems, a->weightsystems.weightsystems[i])) - add_cloned_weightsystem(&res->weightsystems, a->weightsystems.weightsystems[i]); + for (auto &ws: a.weightsystems) { + if (!has_weightsystem(res.weightsystems, ws)) + res.weightsystems.push_back(ws); } - for (i = 0; i < b->weightsystems.nr; i++) { - if (!has_weightsystem(&res->weightsystems, b->weightsystems.weightsystems[i])) - add_cloned_weightsystem(&res->weightsystems, b->weightsystems.weightsystems[i]); + for (auto &ws: b.weightsystems) { + if (!has_weightsystem(res.weightsystems, ws)) + res.weightsystems.push_back(ws); } } -static void merge_temperatures(struct dive *res, const struct dive *a, const struct dive *b) +static void merge_temperatures(struct dive &res, const struct dive &a, const struct dive &b) { temperature_t airtemp_a = un_fixup_airtemp(a); temperature_t airtemp_b = un_fixup_airtemp(b); - res->airtemp = airtemp_a.mkelvin ? airtemp_a : airtemp_b; - MERGE_NONZERO(res, a, b, watertemp.mkelvin); -} - -/* - * Pick a trip for a dive - */ -static struct dive_trip *get_preferred_trip(const struct dive *a, const struct dive *b) -{ - dive_trip_t *atrip, *btrip; - - /* If only one dive has a trip, choose that */ - atrip = a->divetrip; - btrip = b->divetrip; - if (!atrip) - return btrip; - if (!btrip) - return atrip; - - /* Both dives have a trip - prefer the non-autogenerated one */ - if (atrip->autogen && !btrip->autogen) - return btrip; - if (!atrip->autogen && btrip->autogen) - return atrip; - - /* Otherwise, look at the trip data and pick the "better" one */ - if (!atrip->location) - return btrip; - if (!btrip->location) - return atrip; - if (!atrip->notes) - return btrip; - if (!btrip->notes) - return atrip; - - /* - * Ok, so both have location and notes. - * Pick the earlier one. - */ - if (a->when < b->when) - return atrip; - return btrip; + res.airtemp = airtemp_a.mkelvin ? airtemp_a : airtemp_b; + MERGE_NONZERO(&res, a, b, watertemp.mkelvin); } #if CURRENTLY_NOT_USED /* * Sample 's' is between samples 'a' and 'b'. It is 'offset' seconds before 'b'. * - * If 's' and 'a' are at the same time, offset is 0, and b is NULL. + * If 's' and 'a' are at the same time, offset is 0. */ -static int compare_sample(struct sample *s, struct sample *a, struct sample *b, int offset) +static int compare_sample(const struct sample &s, const struct sample &a, const struct sample &b, int offset) { - unsigned int depth = a->depth.mm; + unsigned int depth = a.depth.mm; int diff; if (offset) { - unsigned int interval = b->time.seconds - a->time.seconds; - unsigned int depth_a = a->depth.mm; - unsigned int depth_b = b->depth.mm; + unsigned int interval = (b.time - a.time).seconds; + unsigned int depth_a = a.depth.mm; + unsigned int depth_b = b.depth.mm; if (offset > interval) return -1; @@ -2072,7 +1748,7 @@ static int compare_sample(struct sample *s, struct sample *a, struct sample *b, depth = (depth_a * offset) + (depth_b * (interval - offset)); depth /= interval; } - diff = s->depth.mm - depth; + diff = s.depth.mm - depth; if (diff < 0) diff = -diff; /* cut off at one meter difference */ @@ -2088,10 +1764,9 @@ static int compare_sample(struct sample *s, struct sample *a, struct sample *b, */ static unsigned long sample_difference(struct divecomputer *a, struct divecomputer *b, int offset) { - int asamples = a->samples; - int bsamples = b->samples; - struct sample *as = a->sample; - struct sample *bs = b->sample; + if (a->samples.empty() || b->samples.empty()) + return; + unsigned long error = 0; int start = -1; @@ -2102,45 +1777,36 @@ static unsigned long sample_difference(struct divecomputer *a, struct divecomput * skip the first sample - this way we know can always look at * as/bs[-1] to look at the samples around it in the loop. */ - as++; - bs++; - asamples--; - bsamples--; + auto as = a->samples.begin() + 1; + auto bs = a->samples.begin() + 1; for (;;) { - int at, bt, diff; - - /* If we run out of samples, punt */ - if (!asamples) + if (as == a->samples.end()) return INT_MAX; - if (!bsamples) + if (bs == b->samples.end()) return INT_MAX; - at = as->time.seconds; - bt = bs->time.seconds + offset; + int at = as->time.seconds; + int bt = bs->time.seconds + offset; /* b hasn't started yet? Ignore it */ if (bt < 0) { - bs++; - bsamples--; + ++bs; continue; } + int diff; if (at < bt) { - diff = compare_sample(as, bs - 1, bs, bt - at); - as++; - asamples--; + diff = compare_sample(*as, *std::prev(bs), *bs, bt - at); + ++as; } else if (at > bt) { - diff = compare_sample(bs, as - 1, as, at - bt); - bs++; - bsamples--; + diff = compare_sample(*bs, *std::prev(as), *as, at - bt); + ++bs; } else { - diff = compare_sample(as, bs, NULL, 0); - as++; - bs++; - asamples--; - bsamples--; + diff = compare_sample(*as, *bs, *bs, 0); + ++as; + ++bs; } /* Invalid comparison point? */ @@ -2175,13 +1841,10 @@ static unsigned long sample_difference(struct divecomputer *a, struct divecomput */ static int find_sample_offset(struct divecomputer *a, struct divecomputer *b) { - int offset, best; - unsigned long max; - /* No samples? Merge at any time (0 offset) */ - if (!a->samples) + if (a->samples.empty()) return 0; - if (!b->samples) + if (b->samples.empty()) return 0; /* @@ -2190,8 +1853,8 @@ static int find_sample_offset(struct divecomputer *a, struct divecomputer *b) * Check this first, without wasting time trying to find * some minimal offset case. */ - best = 0; - max = sample_difference(a, b, 0); + int best = 0; + unsigned long max = sample_difference(a, b, 0); if (!max) return 0; @@ -2199,10 +1862,10 @@ static int find_sample_offset(struct divecomputer *a, struct divecomputer *b) * Otherwise, look if we can find anything better within * a thirty second window.. */ - for (offset = -30; offset <= 30; offset++) { + for (int offset = -30; offset <= 30; offset++) { unsigned long diff; - diff = sample_difference(a, b, offset); + int diff = sample_difference(a, b, offset); if (diff > max) continue; best = offset; @@ -2260,18 +1923,15 @@ static int similar(unsigned long a, unsigned long b, unsigned long expected) * 0 for "don't know" * 1 for "is definitely the same dive" */ -static int match_dc_dive(const struct divecomputer *a, const struct divecomputer *b) +static int match_dc_dive(const struct dive &a, const struct dive &b) { - do { - const struct divecomputer *tmp = b; - do { - int match = match_one_dc(a, tmp); + for (auto &dc1: a.dcs) { + for (auto &dc2: b.dcs) { + int match = match_one_dc(dc1, dc2); if (match) return match; - tmp = tmp->next; - } while (tmp); - a = a->next; - } while (a); + } + } return 0; } @@ -2304,171 +1964,113 @@ static int match_dc_dive(const struct divecomputer *a, const struct divecomputer * dives together manually. But this tries to handle the sane * cases. */ -static int likely_same_dive(const struct dive *a, const struct dive *b) +bool dive::likely_same(const struct dive &b) const { - int match, fuzz = 20 * 60; - /* don't merge manually added dives with anything */ - if (is_dc_manually_added_dive(&a->dc) || - is_dc_manually_added_dive(&b->dc)) + if (is_dc_manually_added_dive(&dcs[0]) || + is_dc_manually_added_dive(&b.dcs[0])) return 0; /* * Do some basic sanity testing of the values we * have filled in during 'fixup_dive()' */ - if (!similar(a->maxdepth.mm, b->maxdepth.mm, 1000) || - (a->meandepth.mm && b->meandepth.mm && !similar(a->meandepth.mm, b->meandepth.mm, 1000)) || - !a->duration.seconds || !b->duration.seconds || - !similar(a->duration.seconds, b->duration.seconds, 5 * 60)) + if (!similar(maxdepth.mm, b.maxdepth.mm, 1000) || + (meandepth.mm && b.meandepth.mm && !similar(meandepth.mm, b.meandepth.mm, 1000)) || + !duration.seconds || !b.duration.seconds || + !similar(duration.seconds, b.duration.seconds, 5 * 60)) return 0; /* See if we can get an exact match on the dive computer */ - match = match_dc_dive(&a->dc, &b->dc); - if (match) - return match > 0; + if (match_dc_dive(*this, b)) + return true; /* * Allow a time difference due to dive computer time * setting etc. Check if they overlap. */ - fuzz = std::max(a->duration.seconds, b->duration.seconds) / 2; - if (fuzz < 60) - fuzz = 60; + int fuzz = std::max(duration.seconds, b.duration.seconds) / 2; + fuzz = std::max(fuzz, 60); - return (a->when <= b->when + fuzz) && (a->when >= b->when - fuzz); + return (when <= b.when + fuzz) && (when >= b.when - fuzz); } -/* - * This could do a lot more merging. Right now it really only - * merges almost exact duplicates - something that happens easily - * with overlapping dive downloads. - * - * If new dives are merged into the dive table, dive a is supposed to - * be the old dive and dive b is supposed to be the newly imported - * dive. If the flag "prefer_downloaded" is set, data of the latter - * will take priority over the former. - * - * Attn: The dive_site parameter of the dive will be set, but the caller - * still has to register the dive in the dive site! - */ -extern "C" struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded) +static bool operator==(const sample &a, const sample &b) { - struct dive *res; - struct dive_site *site; - - if (!likely_same_dive(a, b)) - return NULL; - - res = merge_dives(a, b, 0, prefer_downloaded, NULL, &site); - res->dive_site = site; /* Caller has to call add_dive_to_dive_site()! */ - return res; + if (a.time.seconds != b.time.seconds) + return false; + if (a.depth.mm != b.depth.mm) + return false; + if (a.temperature.mkelvin != b.temperature.mkelvin) + return false; + if (a.pressure[0].mbar != b.pressure[0].mbar) + return false; + return a.sensor[0] == b.sensor[0]; } -static int same_sample(struct sample *a, struct sample *b) -{ - if (a->time.seconds != b->time.seconds) - return 0; - if (a->depth.mm != b->depth.mm) - return 0; - if (a->temperature.mkelvin != b->temperature.mkelvin) - return 0; - if (a->pressure[0].mbar != b->pressure[0].mbar) - return 0; - return a->sensor[0] == b->sensor[0]; -} - -static int same_dc(struct divecomputer *a, struct divecomputer *b) +static int same_dc(const struct divecomputer &a, const struct divecomputer &b) { int i; - const struct event *eva, *evb; - i = match_one_dc(a, b); if (i) return i > 0; - if (a->when && b->when && a->when != b->when) + if (a.when && b.when && a.when != b.when) return 0; - if (a->samples != b->samples) + if (a.samples != b.samples) return 0; - for (i = 0; i < a->samples; i++) - if (!same_sample(a->sample + i, b->sample + i)) - return 0; - eva = a->events; - evb = b->events; - while (eva && evb) { - if (!same_event(eva, evb)) - return 0; - eva = eva->next; - evb = evb->next; - } - return eva == evb; + return a.events == b.events; } -static int might_be_same_device(const struct divecomputer *a, const struct divecomputer *b) +static int might_be_same_device(const struct divecomputer &a, const struct divecomputer &b) { /* No dive computer model? That matches anything */ - if (!a->model || !b->model) + if (a.model.empty() || b.model.empty()) return 1; /* Otherwise at least the model names have to match */ - if (strcasecmp(a->model, b->model)) + if (strcasecmp(a.model.c_str(), b.model.c_str())) return 0; /* No device ID? Match */ - if (!a->deviceid || !b->deviceid) + if (!a.deviceid || !b.deviceid) return 1; - return a->deviceid == b->deviceid; + return a.deviceid == b.deviceid; } -static void remove_redundant_dc(struct divecomputer *dc, int prefer_downloaded) +static void remove_redundant_dc(struct dive &d, bool prefer_downloaded) { - do { - struct divecomputer **p = &dc->next; + // Note: since the vector doesn't grow and we only erase + // elements after the iterator, this is fine. + for (auto it = d.dcs.begin(); it != d.dcs.end(); ++it) { + // Remove all following DCs that compare as equal. + // Use the (infamous) erase-remove idiom. + auto it2 = std::remove_if(std::next(it), d.dcs.end(), + [d, prefer_downloaded, &it] (const divecomputer &dc) { + return same_dc(*it, dc) || + (prefer_downloaded && might_be_same_device(*it, dc)); + }); + d.dcs.erase(it2, d.dcs.end()); - /* Check this dc against all the following ones.. */ - while (*p) { - struct divecomputer *check = *p; - if (same_dc(dc, check) || (prefer_downloaded && might_be_same_device(dc, check))) { - *p = check->next; - check->next = NULL; - free_dc(check); - continue; - } - p = &check->next; - } - - /* .. and then continue down the chain, but we */ - prefer_downloaded = 0; - dc = dc->next; - } while (dc); -} - -static const struct divecomputer *find_matching_computer(const struct divecomputer *match, const struct divecomputer *list) -{ - const struct divecomputer *p; - - while ((p = list) != NULL) { - list = list->next; - - if (might_be_same_device(match, p)) - break; + prefer_downloaded = false; } - return p; } -static void copy_dive_computer(struct divecomputer *res, const struct divecomputer *a) +static const struct divecomputer *find_matching_computer(const struct divecomputer &match, const struct dive &d) { - *res = *a; - res->model = copy_string(a->model); - res->serial = copy_string(a->serial); - res->fw_version = copy_string(a->fw_version); - STRUCTURED_LIST_COPY(struct extra_data, a->extra_data, res->extra_data, copy_extra_data); - res->samples = res->alloc_samples = 0; - res->sample = NULL; - res->events = NULL; - res->next = NULL; + for (const auto &dc: d.dcs) { + if (might_be_same_device(match, dc)) + return &dc; + } + return nullptr; +} + +static void copy_dive_computer(struct divecomputer &res, const struct divecomputer &a) +{ + res = a; + res.samples.clear(); + res.events.clear(); } /* @@ -2479,36 +2081,30 @@ static void copy_dive_computer(struct divecomputer *res, const struct divecomput * to match them up. If we find a matching dive computer, we * merge them. If not, we just take the data from 'a'. */ -static void interleave_dive_computers(struct dive *d, struct divecomputer *res, - const struct divecomputer *a, const struct divecomputer *b, - const int cylinders_map_a[], const int cylinders_map_b[], - int offset) +static void interleave_dive_computers(struct dive &res, + const struct dive &a, const struct dive &b, + const int cylinders_map_a[], const int cylinders_map_b[]) { - do { - const struct divecomputer *match; - - copy_dive_computer(res, a); - - match = find_matching_computer(a, b); + res.dcs.clear(); + for (const auto &dc1: a.dcs) { + res.dcs.emplace_back(); + divecomputer &newdc = res.dcs.back(); + copy_dive_computer(newdc, dc1); + const divecomputer *match = find_matching_computer(dc1, b); if (match) { - merge_events(d, res, a, match, cylinders_map_a, cylinders_map_b, offset); - merge_samples(res, a, match, cylinders_map_a, cylinders_map_b, offset); - merge_extra_data(res, a, match); + int offset = match->when - dc1.when; + merge_events(res, newdc, dc1, *match, cylinders_map_a, cylinders_map_b, offset); + merge_samples(newdc, dc1, *match, cylinders_map_a, cylinders_map_b, offset); + merge_extra_data(newdc, dc1, *match); /* Use the diveid of the later dive! */ if (offset > 0) - res->diveid = match->diveid; + newdc.diveid = match->diveid; } else { - copy_dc_renumber(d, a, res, cylinders_map_a); + dc_cylinder_renumber(res, res.dcs.back(), cylinders_map_a); } - a = a->next; - if (!a) - break; - res->next = (divecomputer *)calloc(1, sizeof(struct divecomputer)); - res = res->next; - } while (res); + } } - /* * Join dive computer information. * @@ -2522,88 +2118,48 @@ static void interleave_dive_computers(struct dive *d, struct divecomputer *res, * try to throw out old information that *might* be from * that one. */ -static void join_dive_computers(struct dive *d, struct divecomputer *res, - const struct divecomputer *a, const struct divecomputer *b, +static void join_dive_computers(struct dive &d, + const struct dive &a, const struct dive &b, const int cylinders_map_a[], const int cylinders_map_b[], - int prefer_downloaded) + bool prefer_downloaded) { - struct divecomputer *tmp; - - if (a->model && !b->model) { - copy_dc_renumber(d, a, res, cylinders_map_a); + d.dcs.clear(); + if (!a.dcs[0].model.empty() && b.dcs[0].model.empty()) { + copy_dc_renumber(d, a, cylinders_map_a); return; } - if (b->model && !a->model) { - copy_dc_renumber(d, b, res, cylinders_map_b); + if (!b.dcs[0].model.empty() && a.dcs[0].model.empty()) { + copy_dc_renumber(d, b, cylinders_map_b); return; } - copy_dc_renumber(d, a, res, cylinders_map_a); - tmp = res; - while (tmp->next) - tmp = tmp->next; + copy_dc_renumber(d, a, cylinders_map_a); + copy_dc_renumber(d, b, cylinders_map_b); - tmp->next = (divecomputer *)calloc(1, sizeof(*tmp)); - copy_dc_renumber(d, b, tmp->next, cylinders_map_b); - - remove_redundant_dc(res, prefer_downloaded); + remove_redundant_dc(d, prefer_downloaded); } -static bool has_dc_type(const struct dive *dive, bool dc_is_planner) +static bool has_dc_type(const struct dive &dive, bool dc_is_planner) { - const struct divecomputer *dc = &dive->dc; - - while (dc) { - if (is_dc_planner(dc) == dc_is_planner) - return true; - dc = dc->next; - } - return false; + return std::any_of(dive.dcs.begin(), dive.dcs.end(), + [dc_is_planner] (const divecomputer &dc) + { return is_dc_planner(&dc) == dc_is_planner; }); } // Does this dive have a dive computer for which is_dc_planner has value planned -extern "C" bool is_planned(const struct dive *dive) +bool dive::is_planned() const { - return has_dc_type(dive, true); + return has_dc_type(*this, true); } -extern "C" bool is_logged(const struct dive *dive) +bool dive::is_logged() const { - return has_dc_type(dive, false); + return has_dc_type(*this, false); } -/* - * Merging two dives can be subtle, because there's two different ways - * of merging: - * - * (a) two distinctly _different_ dives that have the same dive computer - * are merged into one longer dive, because the user asked for it - * in the divelist. - * - * Because this case is with the same dive computer, we *know* the - * two must have a different start time, and "offset" is the relative - * time difference between the two. - * - * (b) two different dive computers that we might want to merge into - * one single dive with multiple dive computers. - * - * This is the "try_to_merge()" case, which will have offset == 0, - * even if the dive times might be different. - * - * If new dives are merged into the dive table, dive a is supposed to - * be the old dive and dive b is supposed to be the newly imported - * dive. If the flag "prefer_downloaded" is set, data of the latter - * will take priority over the former. - * - * The trip the new dive should be associated with (if any) is returned - * in the "trip" output parameter. - * - * The dive site the new dive should be added to (if any) is returned - * in the "dive_site" output parameter. - */ -extern "C" 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) +std::unique_ptr dive::create_merged_dive(const struct dive &a, const struct dive &b, int offset, bool prefer_downloaded) { - struct dive *res = alloc_dive(); + auto res = std::make_unique(); if (offset) { /* @@ -2612,19 +2168,12 @@ extern "C" struct dive *merge_dives(const struct dive *a, const struct dive *b, * to try to turn it into a single longer dive. So we'd * join them as two separate dive computers at zero offset. */ - if (likely_same_dive(a, b)) + if (a.likely_same(b)) offset = 0; } - if (is_dc_planner(&a->dc)) { - const struct dive *tmp = a; - a = b; - b = tmp; - } - res->when = prefer_downloaded ? b->when : a->when; - res->selected = a->selected || b->selected; - if (trip) - *trip = get_preferred_trip(a, b); + res->when = prefer_downloaded ? b.when : a.when; + res->selected = a.selected || b.selected; MERGE_TXT(res, a, b, notes, "\n--\n"); MERGE_TXT(res, a, b, buddy, ", "); MERGE_TXT(res, a, b, diveguide, ", "); @@ -2636,302 +2185,27 @@ extern "C" struct dive *merge_dives(const struct dive *a, const struct dive *b, MERGE_NONZERO(res, a, b, current); MERGE_NONZERO(res, a, b, surge); MERGE_NONZERO(res, a, b, chill); - copy_pictures(a->pictures.nr ? &a->pictures : &b->pictures, &res->pictures); - taglist_merge(&res->tag_list, a->tag_list, b->tag_list); + res->pictures = !a.pictures.empty() ? a.pictures : b.pictures; + res->tags = taglist_merge(a.tags, b.tags); /* if we get dives without any gas / cylinder information in an import, make sure * that there is at leatst one entry in the cylinder map for that dive */ - auto cylinders_map_a = std::make_unique(std::max(1, a->cylinders.nr)); - auto cylinders_map_b = std::make_unique(std::max(1, b->cylinders.nr)); - merge_cylinders(res, a, b, cylinders_map_a.get(), cylinders_map_b.get()); - merge_equipment(res, a, b); - merge_temperatures(res, a, b); + auto cylinders_map_a = std::make_unique(std::max(size_t(1), a.cylinders.size())); + auto cylinders_map_b = std::make_unique(std::max(size_t(1), b.cylinders.size())); + merge_cylinders(*res, a, b, cylinders_map_a.get(), cylinders_map_b.get()); + merge_equipment(*res, a, b); + merge_temperatures(*res, a, b); if (prefer_downloaded) { /* If we prefer downloaded, do those first, and get rid of "might be same" computers */ - join_dive_computers(res, &res->dc, &b->dc, &a->dc, cylinders_map_b.get(), cylinders_map_a.get(), 1); - } else if (offset && might_be_same_device(&a->dc, &b->dc)) - interleave_dive_computers(res, &res->dc, &a->dc, &b->dc, cylinders_map_a.get(), cylinders_map_b.get(), offset); - else - join_dive_computers(res, &res->dc, &a->dc, &b->dc, cylinders_map_a.get(), cylinders_map_b.get(), 0); - - /* The CNS values will be recalculated from the sample in fixup_dive() */ - res->cns = res->maxcns = 0; - - /* we take the first dive site, unless it's empty */ - *site = a->dive_site && !dive_site_is_empty(a->dive_site) ? a->dive_site : b->dive_site; - if (!dive_site_has_gps_location(*site) && dive_site_has_gps_location(b->dive_site)) { - /* we picked the first dive site and that didn't have GPS data, but the new dive has - * GPS data (that could be a download from a GPS enabled dive computer). - * Keep the dive site, but add the GPS data */ - (*site)->location = b->dive_site->location; + join_dive_computers(*res, b, a, cylinders_map_b.get(), cylinders_map_a.get(), true); + } else if (offset && might_be_same_device(a.dcs[0], b.dcs[0])) { + interleave_dive_computers(*res, a, b, cylinders_map_a.get(), cylinders_map_b.get()); + } else { + join_dive_computers(*res, a, b, cylinders_map_a.get(), cylinders_map_b.get(), false); } - fixup_dive(res); + return res; } -// copy_dive(), but retaining the new ID for the copied dive -static struct dive *create_new_copy(const struct dive *from) -{ - struct dive *to = alloc_dive(); - int id; - - // alloc_dive() gave us a new ID, we just need to - // make sure it's not overwritten. - id = to->id; - copy_dive(from, to); - to->id = id; - return to; -} - -struct start_end_pressure { - pressure_t start; - pressure_t end; -}; - -static void force_fixup_dive(struct dive *d) -{ - struct divecomputer *dc = &d->dc; - int old_temp = dc->watertemp.mkelvin; - int old_mintemp = d->mintemp.mkelvin; - int old_maxtemp = d->maxtemp.mkelvin; - duration_t old_duration = d->duration; - std::vector old_pressures(d->cylinders.nr); - - d->maxdepth.mm = 0; - dc->maxdepth.mm = 0; - d->watertemp.mkelvin = 0; - dc->watertemp.mkelvin = 0; - d->duration.seconds = 0; - d->maxtemp.mkelvin = 0; - d->mintemp.mkelvin = 0; - for (int i = 0; i < d->cylinders.nr; i++) { - cylinder_t *cyl = get_cylinder(d, i); - old_pressures[i].start = cyl->start; - old_pressures[i].end = cyl->end; - cyl->start.mbar = 0; - cyl->end.mbar = 0; - } - - fixup_dive(d); - - if (!d->watertemp.mkelvin) - d->watertemp.mkelvin = old_temp; - - if (!dc->watertemp.mkelvin) - dc->watertemp.mkelvin = old_temp; - - if (!d->maxtemp.mkelvin) - d->maxtemp.mkelvin = old_maxtemp; - - if (!d->mintemp.mkelvin) - d->mintemp.mkelvin = old_mintemp; - - if (!d->duration.seconds) - d->duration = old_duration; - for (int i = 0; i < d->cylinders.nr; i++) { - if (!get_cylinder(d, i)->start.mbar) - get_cylinder(d, i)->start = old_pressures[i].start; - if (!get_cylinder(d, i)->end.mbar) - get_cylinder(d, i)->end = old_pressures[i].end; - } -} - -/* - * Split a dive that has a surface interval from samples 'a' to 'b' - * into two dives, but don't add them to the log yet. - * Returns the nr of the old dive or <0 on failure. - * Moreover, on failure both output dives are set to NULL. - * On success, the newly allocated dives are returned in out1 and out2. - */ -static int split_dive_at(const struct dive *dive, int a, int b, struct dive **out1, struct dive **out2) -{ - int i, nr; - int32_t t; - struct dive *d1, *d2; - struct divecomputer *dc1, *dc2; - struct event *event, **evp; - - /* if we can't find the dive in the dive list, don't bother */ - if ((nr = get_divenr(dive)) < 0) - return -1; - - /* Splitting should leave at least 3 samples per dive */ - if (a < 3 || b > dive->dc.samples - 4) - return -1; - - /* We're not trying to be efficient here.. */ - d1 = create_new_copy(dive); - d2 = create_new_copy(dive); - d1->divetrip = d2->divetrip = 0; - - /* now unselect the first first segment so we don't keep all - * dives selected by mistake. But do keep the second one selected - * so the algorithm keeps splitting the dive further */ - d1->selected = false; - - dc1 = &d1->dc; - dc2 = &d2->dc; - /* - * Cut off the samples of d1 at the beginning - * of the interval. - */ - dc1->samples = a; - - /* And get rid of the 'b' first samples of d2 */ - dc2->samples -= b; - memmove(dc2->sample, dc2->sample+b, dc2->samples * sizeof(struct sample)); - - /* Now the secondary dive computers */ - t = dc2->sample[0].time.seconds; - while ((dc1 = dc1->next)) { - i = 0; - while (dc1->samples < i && dc1->sample[i].time.seconds <= t) - ++i; - dc1->samples = i; - } - while ((dc2 = dc2->next)) { - i = 0; - while (dc2->samples < i && dc2->sample[i].time.seconds < t) - ++i; - dc2->samples -= i; - memmove(dc2->sample, dc2->sample + i, dc2->samples * sizeof(struct sample)); - } - dc1 = &d1->dc; - dc2 = &d2->dc; - /* - * This is where we cut off events from d1, - * and shift everything in d2 - */ - d2->when += t; - while (dc1 && dc2) { - dc2->when += t; - for (i = 0; i < dc2->samples; i++) - dc2->sample[i].time.seconds -= t; - - /* Remove the events past 't' from d1 */ - evp = &dc1->events; - while ((event = *evp) != NULL && event->time.seconds < t) - evp = &event->next; - *evp = NULL; - while (event) { - struct event *next = event->next; - free(event); - event = next; - } - - /* Remove the events before 't' from d2, and shift the rest */ - evp = &dc2->events; - while ((event = *evp) != NULL) { - if (event->time.seconds < t) { - *evp = event->next; - free(event); - } else { - event->time.seconds -= t; - } - } - dc1 = dc1->next; - dc2 = dc2->next; - } - - force_fixup_dive(d1); - force_fixup_dive(d2); - - /* - * Was the dive numbered? If it was the last dive, then we'll - * increment the dive number for the tail part that we split off. - * Otherwise the tail is unnumbered. - */ - if (d2->number) { - if (divelog.dives->nr == nr + 1) - d2->number++; - else - d2->number = 0; - } - - *out1 = d1; - *out2 = d2; - return nr; -} - -/* in freedive mode we split for as little as 10 seconds on the surface, - * otherwise we use a minute */ -static bool should_split(const struct divecomputer *dc, int t1, int t2) -{ - int threshold = dc->divemode == FREEDIVE ? 10 : 60; - - return t2 - t1 >= threshold; -} - -/* - * Try to split a dive into multiple dives at a surface interval point. - * - * NOTE! We will split when there is at least one surface event that has - * non-surface events on both sides. - * - * The surface interval points are determined using the first dive computer. - * - * In other words, this is a (simplified) reversal of the dive merging. - */ -extern "C" int split_dive(const struct dive *dive, struct dive **new1, struct dive **new2) -{ - int i; - int at_surface, surface_start; - const struct divecomputer *dc; - - *new1 = *new2 = NULL; - if (!dive) - return -1; - - dc = &dive->dc; - surface_start = 0; - at_surface = 1; - for (i = 1; i < dc->samples; i++) { - struct sample *sample = dc->sample+i; - int surface_sample = sample->depth.mm < SURFACE_THRESHOLD; - - /* - * We care about the transition from and to depth 0, - * not about the depth staying similar. - */ - if (at_surface == surface_sample) - continue; - at_surface = surface_sample; - - // Did it become surface after having been non-surface? We found the start - if (at_surface) { - surface_start = i; - continue; - } - - // Going down again? We want at least a minute from - // the surface start. - if (!surface_start) - continue; - if (!should_split(dc, dc->sample[surface_start].time.seconds, sample[-1].time.seconds)) - continue; - - return split_dive_at(dive, surface_start, i-1, new1, new2); - } - return -1; -} - -extern "C" int split_dive_at_time(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2) -{ - int i = 0; - - if (!dive) - return -1; - - struct sample *sample = dive->dc.sample; - *new1 = *new2 = NULL; - while(sample->time.seconds < time.seconds) { - ++sample; - ++i; - if (dive->dc.samples == i) - return -1; - } - return split_dive_at(dive, i, i - 1, new1, new2); -} - /* * "dc_maxtime()" is how much total time this dive computer * has for this dive. Note that it can differ from "duration" @@ -2940,15 +2214,13 @@ extern "C" int split_dive_at_time(const struct dive *dive, duration_t time, stru * Still, we do ignore all but the last surface samples from the * end, because some divecomputers just generate lots of them. */ -static inline int dc_totaltime(const struct divecomputer *dc) +static inline int dc_totaltime(const struct divecomputer &dc) { - int time = dc->duration.seconds; - int nr = dc->samples; + int time = dc.duration.seconds; - while (nr--) { - struct sample *s = dc->sample + nr; - time = s->time.seconds; - if (s->depth.mm >= SURFACE_THRESHOLD) + for (auto it = dc.samples.rbegin(); it != dc.samples.rend(); ++it) { + time = it->time.seconds; + if (it->depth.mm >= SURFACE_THRESHOLD) break; } return time; @@ -2964,28 +2236,30 @@ static inline int dc_totaltime(const struct divecomputer *dc) * time in the samples (and just default to the dive duration if * there are no samples). */ -static inline int dive_totaltime(const struct dive *dive) +duration_t dive::totaltime() const { - int time = dive->duration.seconds; - const struct divecomputer *dc; + int time = duration.seconds; - for_each_relevant_dc(dive, dc) { - int dc_time = dc_totaltime(dc); - if (dc_time > time) - time = dc_time; + bool logged = is_logged(); + for (auto &dc: dcs) { + if (logged || !is_dc_planner(&dc)) { + int dc_time = dc_totaltime(dc); + if (dc_time > time) + time = dc_time; + } } - return time; + return { .seconds = time }; } -extern "C" timestamp_t dive_endtime(const struct dive *dive) +timestamp_t dive::endtime() const { - return dive->when + dive_totaltime(dive); + return when + totaltime().seconds; } -extern "C" bool time_during_dive_with_offset(const struct dive *dive, timestamp_t when, timestamp_t offset) +bool dive::time_during_dive_with_offset(timestamp_t when, timestamp_t offset) const { - timestamp_t start = dive->when; - timestamp_t end = dive_endtime(dive); + timestamp_t start = when; + timestamp_t end = endtime(); return start - offset <= when && when <= end + offset; } @@ -2999,7 +2273,7 @@ extern "C" bool time_during_dive_with_offset(const struct dive *dive, timestamp_ * functionality for the core library that Subsurface itself doesn't * use but that another consumer of the library (like an HTML exporter) * will need */ -extern "C" void set_informational_units(const char *units) +void set_informational_units(const char *units) { if (strstr(units, "METRIC")) { git_prefs.unit_system = METRIC; @@ -3032,181 +2306,69 @@ extern "C" void set_informational_units(const char *units) if (strstr(units, "MINUTES")) git_prefs.units.vertical_speed_time = units::MINUTES; } - -} - -extern "C" void set_git_prefs(const char *prefs) -{ - if (strstr(prefs, "TANKBAR")) - git_prefs.tankbar = 1; - if (strstr(prefs, "SHOW_SETPOINT")) - git_prefs.show_ccr_setpoint = 1; - if (strstr(prefs, "SHOW_SENSORS")) - git_prefs.show_ccr_sensors = 1; - if (strstr(prefs, "PO2_GRAPH")) - git_prefs.pp_graphs.po2 = 1; } /* clones a dive and moves given dive computer to front */ -extern "C" struct dive *make_first_dc(const struct dive *d, int dc_number) +std::unique_ptr clone_make_first_dc(const struct dive &d, int dc_number) { - struct dive *res; - struct divecomputer *dc, *newdc, *old_dc; - /* copy the dive */ - res = alloc_dive(); - copy_dive(d, res); + auto res = std::make_unique(d); /* make a new unique id, since we still can't handle two equal ids */ res->id = dive_getUniqID(); - if (dc_number == 0) - return res; - - dc = &res->dc; - newdc = (divecomputer *)malloc(sizeof(*newdc)); - old_dc = get_dive_dc(res, dc_number); - - /* skip the current DC in the linked list */ - for (dc = &res->dc; dc && dc->next != old_dc; dc = dc->next) - ; - if (!dc) { - free(newdc); - report_info("data inconsistent: can't find the current DC"); - return res; - } - dc->next = old_dc->next; - *newdc = res->dc; - res->dc = *old_dc; - res->dc.next = newdc; - free(old_dc); + if (dc_number != 0) + move_in_range(res->dcs, dc_number, dc_number + 1, 0); return res; } -static void delete_divecomputer(struct dive *d, int num) -{ - int i; - - /* Refuse to delete the last dive computer */ - if (!d->dc.next) - return; - - if (num == 0) { - /* remove the first one, so copy the second one in place of the first and free the second one - * be careful about freeing the no longer needed structures - since we copy things around we can't use free_dc()*/ - struct divecomputer *fdc = d->dc.next; - free_dc_contents(&d->dc); - memcpy(&d->dc, fdc, sizeof(struct divecomputer)); - free(fdc); - } else { - struct divecomputer *pdc = &d->dc; - for (i = 0; i < num - 1 && pdc; i++) - pdc = pdc->next; - if (pdc && pdc->next) { - struct divecomputer *dc = pdc->next; - pdc->next = dc->next; - free_dc(dc); - } - } -} - -/* Clone a dive and delete goven dive computer */ -extern "C" struct dive *clone_delete_divecomputer(const struct dive *d, int dc_number) -{ - struct dive *res; - - /* copy the dive */ - res = alloc_dive(); - copy_dive(d, res); - - /* make a new unique id, since we still can't handle two equal ids */ - res->id = dive_getUniqID(); - - delete_divecomputer(res, dc_number); - force_fixup_dive(res); - - return res; -} - -/* - * This splits the dive src by dive computer. The first output dive has all - * dive computers except num, the second only dive computer num. - * The dives will not be associated with a trip. - * On error, both output parameters are set to NULL. - */ -extern "C" void split_divecomputer(const struct dive *src, int num, struct dive **out1, struct dive **out2) -{ - const struct divecomputer *srcdc = get_dive_dc_const(src, num); - - if (src && srcdc) { - // Copy the dive, but only using the selected dive computer - *out2 = alloc_dive(); - copy_dive_onedc(src, srcdc, *out2); - - // This will also make fixup_dive() to allocate a new dive id... - (*out2)->id = 0; - fixup_dive(*out2); - - // Copy the dive with all dive computers - *out1 = create_new_copy(src); - - // .. and then delete the split-out dive computer - delete_divecomputer(*out1, num); - - (*out1)->divetrip = (*out2)->divetrip = NULL; - } else { - *out1 = *out2 = NULL; - } -} - //Calculate O2 in best mix -extern "C" fraction_t best_o2(depth_t depth, const struct dive *dive, bool in_planner) +fraction_t dive::best_o2(depth_t depth, bool in_planner) const { fraction_t fo2; int po2 = in_planner ? prefs.bottompo2 : (int)(prefs.modpO2 * 1000.0); - fo2.permille = (po2 * 100 / depth_to_mbar(depth.mm, dive)) * 10; //use integer arithmetic to round down to nearest percent + fo2.permille = (po2 * 100 / depth_to_mbar(depth.mm)) * 10; //use integer arithmetic to round down to nearest percent // Don't permit >100% O2 + // TODO: use std::min, once we have comparison if (fo2.permille > 1000) - fo2.permille = 1000; + fo2 = 100_percent; return fo2; } //Calculate He in best mix. O2 is considered narcopic -extern "C" fraction_t best_he(depth_t depth, const struct dive *dive, bool o2narcotic, fraction_t fo2) +fraction_t dive::best_he(depth_t depth, bool o2narcotic, fraction_t fo2) const { fraction_t fhe; int pnarcotic, ambient; - pnarcotic = depth_to_mbar(prefs.bestmixend.mm, dive); - ambient = depth_to_mbar(depth.mm, dive); + pnarcotic = depth_to_mbar(prefs.bestmixend.mm); + ambient = depth_to_mbar(depth.mm); if (o2narcotic) { fhe.permille = (100 - 100 * pnarcotic / ambient) * 10; //use integer arithmetic to round up to nearest percent } else { fhe.permille = 1000 - fo2.permille - N2_IN_AIR * pnarcotic / ambient; } + // TODO: use std::max, once we have comparison if (fhe.permille < 0) - fhe.permille = 0; + fhe = 0_percent; return fhe; } -extern "C" void invalidate_dive_cache(struct dive *dive) +static constexpr std::array null_id = {}; +void dive::invalidate_cache() { - memset(dive->git_id, 0, 20); + git_id = null_id; } -extern "C" bool dive_cache_is_valid(const struct dive *dive) +bool dive::cache_is_valid() const { - static const unsigned char null_id[20] = { 0, }; - return !!memcmp(dive->git_id, null_id, 20); + return git_id != null_id; } -extern "C" int get_surface_pressure_in_mbar(const struct dive *dive, bool non_null) +pressure_t dive::get_surface_pressure() const { - int mbar = dive->surface_pressure.mbar; - if (!mbar && non_null) - mbar = SURFACE_PRESSURE; - return mbar; + return surface_pressure.mbar > 0 ? surface_pressure : 1_atm; } /* This returns the conversion factor that you need to multiply @@ -3224,231 +2386,146 @@ static double salinity_to_specific_weight(int salinity) * and add that to the surface pressure (or to 1013 if that's unknown) */ static double calculate_depth_to_mbarf(int depth, pressure_t surface_pressure, int salinity) { - double specific_weight; - int mbar = surface_pressure.mbar; - - if (!mbar) - mbar = SURFACE_PRESSURE; + if (!surface_pressure.mbar) + surface_pressure = 1_atm; if (!salinity) salinity = SEAWATER_SALINITY; if (salinity < 500) salinity += FRESHWATER_SALINITY; - specific_weight = salinity_to_specific_weight(salinity); - return mbar + depth * specific_weight; + double specific_weight = salinity_to_specific_weight(salinity); + return surface_pressure.mbar + depth * specific_weight; } -extern "C" int depth_to_mbar(int depth, const struct dive *dive) +int dive::depth_to_mbar(int depth) const { - return lrint(depth_to_mbarf(depth, dive)); + return lrint(depth_to_mbarf(depth)); } -extern "C" double depth_to_mbarf(int depth, const struct dive *dive) +double dive::depth_to_mbarf(int depth) const { // For downloaded and planned dives, use DC's values - int salinity = dive->dc.salinity; - pressure_t surface_pressure = dive->dc.surface_pressure; + int salinity = dcs[0].salinity; + pressure_t surface_pressure = dcs[0].surface_pressure; - if (is_dc_manually_added_dive(&dive->dc)) { // For manual dives, salinity and pressure in another place... - surface_pressure = dive->surface_pressure; - salinity = dive->user_salinity; + if (is_dc_manually_added_dive(&dcs[0])) { // For manual dives, salinity and pressure in another place... + surface_pressure = this->surface_pressure; + salinity = user_salinity; } return calculate_depth_to_mbarf(depth, surface_pressure, salinity); } -extern "C" double depth_to_bar(int depth, const struct dive *dive) +double dive::depth_to_bar(int depth) const { - return depth_to_mbar(depth, dive) / 1000.0; + return depth_to_mbar(depth) / 1000.0; } -extern "C" double depth_to_atm(int depth, const struct dive *dive) +double dive::depth_to_atm(int depth) const { - return mbar_to_atm(depth_to_mbar(depth, dive)); + return mbar_to_atm(depth_to_mbar(depth)); } /* for the inverse calculation we use just the relative pressure * (that's the one that some dive computers like the Uemis Zurich * provide - for the other models that do this libdivecomputer has to * take care of this, but the Uemis we support natively */ -extern "C" int rel_mbar_to_depth(int mbar, const struct dive *dive) +int dive::rel_mbar_to_depth(int mbar) const { // For downloaded and planned dives, use DC's salinity. Manual dives, use user's salinity - int salinity = is_dc_manually_added_dive(&dive->dc) ? dive->user_salinity : dive->dc.salinity; + int salinity = is_dc_manually_added_dive(&dcs[0]) ? user_salinity : dcs[0].salinity; if (!salinity) salinity = SEAWATER_SALINITY; /* whole mbar gives us cm precision */ double specific_weight = salinity_to_specific_weight(salinity); - return (int)lrint(mbar / specific_weight); + return int_cast(mbar / specific_weight); } -extern "C" int mbar_to_depth(int mbar, const struct dive *dive) +int dive::mbar_to_depth(int mbar) const { // For downloaded and planned dives, use DC's pressure. Manual dives, use user's pressure - pressure_t surface_pressure = is_dc_manually_added_dive(&dive->dc) - ? dive->surface_pressure - : dive->dc.surface_pressure; + pressure_t surface_pressure = is_dc_manually_added_dive(&dcs[0]) + ? this->surface_pressure + : dcs[0].surface_pressure; if (!surface_pressure.mbar) - surface_pressure.mbar = SURFACE_PRESSURE; - - return rel_mbar_to_depth(mbar - surface_pressure.mbar, dive); + surface_pressure = 1_atm; + + return rel_mbar_to_depth(mbar - surface_pressure.mbar); } /* MOD rounded to multiples of roundto mm */ -extern "C" depth_t gas_mod(struct gasmix mix, pressure_t po2_limit, const struct dive *dive, int roundto) +depth_t dive::gas_mod(struct gasmix mix, pressure_t po2_limit, int roundto) const { - depth_t rounded_depth; - - double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix), dive); - rounded_depth.mm = (int)lrint(depth / roundto) * roundto; - return rounded_depth; + double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix)); + return depth_t { .mm = int_cast(depth / roundto) * roundto }; } /* Maximum narcotic depth rounded to multiples of roundto mm */ -extern "C" depth_t gas_mnd(struct gasmix mix, depth_t end, const struct dive *dive, int roundto) +depth_t dive::gas_mnd(struct gasmix mix, depth_t end, int roundto) const { - depth_t rounded_depth; - pressure_t ppo2n2; - ppo2n2.mbar = depth_to_mbar(end.mm, dive); + pressure_t ppo2n2 { .mbar = depth_to_mbar(end.mm) }; int maxambient = prefs.o2narcotic ? - (int)lrint(ppo2n2.mbar / (1 - get_he(mix) / 1000.0)) + int_cast(ppo2n2.mbar / (1 - get_he(mix) / 1000.0)) : get_n2(mix) > 0 ? - (int)lrint(ppo2n2.mbar * N2_IN_AIR / get_n2(mix)) + int_cast(ppo2n2.mbar * N2_IN_AIR / get_n2(mix)) : // Actually: Infinity 1000000; - rounded_depth.mm = (int)lrint(((double)mbar_to_depth(maxambient, dive)) / roundto) * roundto; - return rounded_depth; + return depth_t { .mm = int_cast(((double)mbar_to_depth(maxambient)) / roundto) * roundto }; } -extern "C" struct dive *get_dive(int nr) +std::string dive::get_country() const { - if (nr >= divelog.dives->nr || nr < 0) + return dive_site ? taxonomy_get_country(dive_site->taxonomy) : std::string(); +} + +std::string dive::get_location() const +{ + return dive_site ? dive_site->name : std::string(); +} + +int dive::number_of_computers() const +{ + return static_cast(dcs.size()); +} + +struct divecomputer *dive::get_dc(int nr) +{ + if (dcs.empty()) // Can't happen! return NULL; - return divelog.dives->dives[nr]; + nr = std::max(0, nr); + return &dcs[static_cast(nr) % dcs.size()]; } -extern "C" struct dive_site *get_dive_site_for_dive(const struct dive *dive) +const struct divecomputer *dive::get_dc(int nr) const { - return dive->dive_site; + return const_cast(*this).get_dc(nr); } -extern "C" const char *get_dive_country(const struct dive *dive) +bool dive::dive_has_gps_location() const { - struct dive_site *ds = dive->dive_site; - return ds ? taxonomy_get_country(&ds->taxonomy) : NULL; -} - -extern "C" const char *get_dive_location(const struct dive *dive) -{ - const struct dive_site *ds = dive->dive_site; - if (ds && ds->name) - return ds->name; - return NULL; -} - -extern "C" unsigned int number_of_computers(const struct dive *dive) -{ - unsigned int total_number = 0; - const struct divecomputer *dc = &dive->dc; - - if (!dive) - return 1; - - do { - total_number++; - dc = dc->next; - } while (dc); - return total_number; -} - -extern "C" struct divecomputer *get_dive_dc(struct dive *dive, int nr) -{ - struct divecomputer *dc; - if (!dive) - return NULL; - dc = &dive->dc; - - while (nr-- > 0) { - dc = dc->next; - if (!dc) - return &dive->dc; - } - return dc; -} - -extern "C" const struct divecomputer *get_dive_dc_const(const struct dive *dive, int nr) -{ - return get_dive_dc((struct dive *)dive, nr); -} - -extern "C" struct dive *get_dive_by_uniq_id(int id) -{ - int i; - struct dive *dive = NULL; - - for_each_dive (i, dive) { - if (dive->id == id) - break; - } -#ifdef DEBUG - if (dive == NULL) { - report_info("Invalid id %x passed to get_dive_by_diveid, try to fix the code", id); - exit(1); - } -#endif - return dive; -} - -extern "C" int get_idx_by_uniq_id(int id) -{ - int i; - struct dive *dive = NULL; - - for_each_dive (i, dive) { - if (dive->id == id) - break; - } -#ifdef DEBUG - if (dive == NULL) { - report_info("Invalid id %x passed to get_dive_by_diveid, try to fix the code", id); - exit(1); - } -#endif - return i; -} - -extern "C" bool dive_site_has_gps_location(const struct dive_site *ds) -{ - return ds && has_location(&ds->location); -} - -extern "C" int dive_has_gps_location(const struct dive *dive) -{ - if (!dive) - return false; - return dive_site_has_gps_location(dive->dive_site); + return dive_site && dive_site->has_gps_location(); } /* Extract GPS location of a dive computer stored in the GPS1 * or GPS2 extra data fields */ -static location_t dc_get_gps_location(const struct divecomputer *dc) +static location_t dc_get_gps_location(const struct divecomputer &dc) { - location_t res = { }; + location_t res; - for (struct extra_data *data = dc->extra_data; data; data = data->next) { - if (!strcmp(data->key, "GPS1")) { - parse_location(data->value, &res); + for (const auto &data: dc.extra_data) { + if (data.key == "GPS1") { + parse_location(data.value.c_str(), &res); /* If we found a valid GPS1 field exit early since * it has priority over GPS2 */ if (has_location(&res)) break; - } else if (!strcmp(data->key, "GPS2")) { + } else if (data.key == "GPS2") { /* For GPS2 fields continue searching, as we might * still find a GPS1 field */ - parse_location(data->value, &res); + parse_location(data.value.c_str(), &res); } } return res; @@ -3460,12 +2537,10 @@ static location_t dc_get_gps_location(const struct divecomputer *dc) * This function is potentially slow, therefore only call sparingly * and remember the result. */ -extern "C" location_t dive_get_gps_location(const struct dive *d) +location_t dive::get_gps_location() const { - location_t res = { }; - - for (const struct divecomputer *dc = &d->dc; dc; dc = dc->next) { - res = dc_get_gps_location(dc); + for (const struct divecomputer &dc: dcs) { + location_t res = dc_get_gps_location(dc); if (has_location(&res)) return res; } @@ -3473,61 +2548,237 @@ extern "C" location_t dive_get_gps_location(const struct dive *d) /* No libdivecomputer generated GPS data found. * Let's use the location of the current dive site. */ - if (d->dive_site) - res = d->dive_site->location; - - return res; + return dive_site ? dive_site->location : location_t(); } -/* When evaluated at the time of a gasswitch, this returns the new gas */ -extern "C" struct gasmix get_gasmix(const struct dive *dive, const struct divecomputer *dc, int time, const struct event **evp, struct gasmix gasmix) +gasmix_loop::gasmix_loop(const struct dive &d, const struct divecomputer &dc) : + dive(d), dc(dc), first_run(true), loop("gaschange", dc) { - const struct event *ev = *evp; - struct gasmix res; +} - /* if there is no cylinder, return air */ - if (dive->cylinders.nr <= 0) - return gasmix_air; +/* Some dive computers (Cobalt) don't start the dive with cylinder 0 but explicitly + * tell us what the first gas is with a gas change event in the first sample. + * Sneakily we'll use a return value of 0 (or FALSE) when there is no explicit + * first cylinder - in which case cylinder 0 is indeed the first cylinder. + * We likewise return 0 if the event concerns a cylinder that doesn't exist. + * If the dive has no cylinders, -1 is returned. */ - if (!ev) { - /* on first invocation, get initial gas mix and first event (if any) */ - int cyl = explicit_first_cylinder(dive, dc); - res = get_cylinder(dive, cyl)->gasmix; - ev = dc ? get_next_event(dc->events, "gaschange") : NULL; +std::pair gasmix_loop::next_cylinder_index() +{ + if (dive.cylinders.empty()) + return std::make_pair(-1, INT_MAX); + + if (first_run) { + next_event = loop.next(); + last_cylinder_index = 0; // default to first cylinder + last_time = 0; + if (next_event && ((!dc.samples.empty() && next_event->time.seconds == dc.samples[0].time.seconds) || next_event->time.seconds <= 1)) { + last_cylinder_index = dive.get_cylinder_index(*next_event); + last_time = next_event->time.seconds; + next_event = loop.next(); + } else if (dc.divemode == CCR) { + last_cylinder_index = std::max(get_cylinder_idx_by_use(dive, DILUENT), last_cylinder_index); + } + + first_run = false; } else { - res = gasmix; + if (next_event) { + last_cylinder_index = dive.get_cylinder_index(*next_event); + last_time = next_event->time.seconds; + next_event = loop.next(); + } else { + last_cylinder_index = -1; + last_time = INT_MAX; + } } - while (ev && ev->time.seconds <= time) { - res = get_gasmix_from_event(dive, ev); - ev = get_next_event(ev->next, "gaschange"); + return std::make_pair(last_cylinder_index, last_time); +} + +std::pair gasmix_loop::next() +{ + if (first_run && dive.cylinders.empty()) { + first_run = false; + + // return one cylinder of air if we don't have any cylinders + return std::make_pair(gasmix_air, 0); } - *evp = ev; - return res; + + next_cylinder_index(); + + return std::make_pair(last_cylinder_index < 0 ? gasmix_invalid : dive.get_cylinder(last_cylinder_index)->gasmix, last_time); +} + +std::pair gasmix_loop::cylinder_index_at(int time) +{ + if (first_run) + next_cylinder_index(); + + while (has_next() && next_event->time.seconds <= time) + next_cylinder_index(); + + return std::make_pair(last_cylinder_index, last_time); +} + +std::pair gasmix_loop::at(int time) +{ + if (dive.cylinders.empty()) + // return air if we don't have any cylinders + return std::make_pair(gasmix_air, 0); + + cylinder_index_at(time); + + return std::make_pair(last_cylinder_index < 0 ? gasmix_invalid : dive.get_cylinder(last_cylinder_index)->gasmix, last_time); +} + +bool gasmix_loop::has_next() const +{ + return first_run || (!dive.cylinders.empty() && next_event); } /* get the gas at a certain time during the dive */ /* If there is a gasswitch at that time, it returns the new gasmix */ -extern "C" struct gasmix get_gasmix_at_time(const struct dive *d, const struct divecomputer *dc, duration_t time) +struct gasmix dive::get_gasmix_at_time(const struct divecomputer &dc, duration_t time) const { - const struct event *ev = NULL; - struct gasmix gasmix = gasmix_air; - return get_gasmix(d, dc, time.seconds, &ev, gasmix); + return gasmix_loop(*this, dc).at(time.seconds).first; } /* Does that cylinder have any pressure readings? */ -extern "C" bool cylinder_with_sensor_sample(const struct dive *dive, int cylinder_id) +bool cylinder_with_sensor_sample(const struct dive *dive, int cylinder_id) { - for (const struct divecomputer *dc = &dive->dc; dc; dc = dc->next) { - for (int i = 0; i < dc->samples; ++i) { - struct sample *sample = dc->sample + i; + for (const auto &dc: dive->dcs) { + for (const auto &sample: dc.samples) { for (int j = 0; j < MAX_SENSORS; ++j) { - if (!sample->pressure[j].mbar) + if (!sample.pressure[j].mbar) continue; - if (sample->sensor[j] == cylinder_id) + if (sample.sensor[j] == cylinder_id) return true; } } } return false; } + +/* + * What do the dive computers say the water temperature is? + * (not in the samples, but as dc property for dcs that support that) + */ +temperature_t dive::dc_watertemp() const +{ + int sum = 0, nr = 0; + + for (auto &dc: dcs) { + if (dc.watertemp.mkelvin) { + sum += dc.watertemp.mkelvin; + nr++; + } + } + if (!nr) + return temperature_t(); + return temperature_t{ .mkelvin = static_cast((sum + nr / 2) / nr) }; +} + +/* + * What do the dive computers say the air temperature is? + */ +temperature_t dive::dc_airtemp() const +{ + int sum = 0, nr = 0; + + for (auto &dc: dcs) { + if (dc.airtemp.mkelvin) { + sum += dc.airtemp.mkelvin; + nr++; + } + } + if (!nr) + return temperature_t(); + return temperature_t{ .mkelvin = static_cast((sum + nr / 2) / nr) }; +} + +/* + * Get "maximal" dive gas for a dive. + * Rules: + * - Trimix trumps nitrox (highest He wins, O2 breaks ties) + * - Nitrox trumps air (even if hypoxic) + * These are the same rules as the inter-dive sorting rules. + */ +dive::get_maximal_gas_result dive::get_maximal_gas() const +{ + int maxo2 = -1, maxhe = -1, mino2 = 1000; + + for (auto [i, cyl]: enumerated_range(cylinders)) { + int o2 = get_o2(cyl.gasmix); + int he = get_he(cyl.gasmix); + + if (!is_cylinder_used(i)) + continue; + if (cyl.cylinder_use == OXYGEN) + continue; + if (cyl.cylinder_use == NOT_USED) + continue; + if (o2 > maxo2) + maxo2 = o2; + if (o2 < mino2 && maxhe <= 0) + mino2 = o2; + if (he > maxhe) { + maxhe = he; + mino2 = o2; + } + } + /* All air? Show/sort as "air"/zero */ + if ((!maxhe && maxo2 == O2_IN_AIR && mino2 == maxo2) || + (maxo2 == -1 && maxhe == -1 && mino2 == 1000)) + maxo2 = mino2 = 0; + return { mino2, maxhe, maxo2 }; +} + +bool dive::has_gaschange_event(const struct divecomputer *dc, int idx) const +{ + gasmix_loop loop(*this, *dc); + while (loop.has_next()) { + if (loop.next_cylinder_index().first == idx) + return true; + } + + return false; +} + +bool dive::is_cylinder_used(int idx) const +{ + if (idx < 0 || static_cast(idx) >= cylinders.size()) + return false; + + const cylinder_t &cyl = cylinders[idx]; + if ((cyl.start.mbar - cyl.end.mbar) > SOME_GAS) + return true; + + if ((cyl.sample_start.mbar - cyl.sample_end.mbar) > SOME_GAS) + return true; + + for (auto &dc: dcs) { + if (has_gaschange_event(&dc, idx)) + return true; + else if (dc.divemode == CCR && idx == get_cylinder_idx_by_use(*this, OXYGEN)) + return true; + } + return false; +} + +bool dive::is_cylinder_prot(int idx) const +{ + if (idx < 0 || static_cast(idx) >= cylinders.size()) + return false; + + return std::any_of(dcs.begin(), dcs.end(), + [this, idx](auto &dc) + { return has_gaschange_event(&dc, idx); }); +} + +weight_t dive::total_weight() const +{ + // TODO: implement addition for units.h types + return std::accumulate(weightsystems.begin(), weightsystems.end(), weight_t(), + [] (weight_t w, const weightsystem_t &ws) + { return weight_t{ .grams = w.grams + ws.weight.grams }; }); +} diff --git a/core/dive.h b/core/dive.h index e85f19bed..a5d79cdb2 100644 --- a/core/dive.h +++ b/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 -#include - -#ifdef __cplusplus -extern "C" { -#endif +#include +#include +#include +#include +#include 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 +struct non_copying_unique_ptr : public std::unique_ptr { + using std::unique_ptr::unique_ptr; + using std::unique_ptr::operator=; + non_copying_unique_ptr(const non_copying_unique_ptr &) { } + void operator=(const non_copying_unique_ptr &) { } +}; + 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 dcs; // Attn: pointers to divecomputers are not stable! + int id = 0; // unique ID for this dive + picture_table pictures; + std::array 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; /* 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 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 divesite; // We save the uuid not a pointer, because the + // user might copy and then delete the dive site. + std::optional notes; + std::optional diveguide; + std::optional buddy; + std::optional suit; + std::optional rating; + std::optional visibility; + std::optional wavesize; + std::optional current; + std::optional surge; + std::optional chill; + std::optional tags; + std::optional cylinders; + std::optional weights; + std::optional number; + std::optional 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 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 -#include Q_DECLARE_METATYPE(struct dive *); extern std::string existing_filename; -#endif - #endif // DIVE_H diff --git a/core/divecomputer.c b/core/divecomputer.c deleted file mode 100644 index 4cac867ff..000000000 --- a/core/divecomputer.c +++ /dev/null @@ -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 -#include - -/* - * 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); -} diff --git a/core/divecomputer.cpp b/core/divecomputer.cpp new file mode 100644 index 000000000..2a5acba21 --- /dev/null +++ b/core/divecomputer.cpp @@ -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 +#include +#include + +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 &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 &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 &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(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(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(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; +} diff --git a/core/divecomputer.h b/core/divecomputer.h index a20cd967c..074b36bcd 100644 --- a/core/divecomputer.h +++ b/core/divecomputer.h @@ -4,12 +4,11 @@ #include "divemode.h" #include "units.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include +#include 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 samples; + std::vector events; + std::vector 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 diff --git a/core/divefilter.cpp b/core/divefilter.cpp index 3bc1ba9fd..e4fe83ae8 100644 --- a/core/divefilter.cpp +++ b/core/divefilter.cpp @@ -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 &dives) const std::vector 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 &dives) const void DiveFilter::reset() { - int i; - dive *d; - shown_dives = divelog.dives->nr; - for_each_dive(i, d) + shown_dives = static_cast(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 selection = getDiveSelection(); std::vector 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(), removeFromSelection); @@ -142,7 +139,7 @@ bool DiveFilter::showDive(const struct dive *d) const } #if !defined(SUBSURFACE_MOBILE) && !defined(SUBSURFACE_DOWNLOADER) -void DiveFilter::startFilterDiveSites(QVector ds) +void DiveFilter::startFilterDiveSites(std::vector ds) { if (++diveSiteRefCount > 1) { setFilterDiveSite(std::move(ds)); @@ -169,7 +166,7 @@ void DiveFilter::stopFilterDiveSites() #endif } -void DiveFilter::setFilterDiveSite(QVector ds) +void DiveFilter::setFilterDiveSite(std::vector 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 ds) MainWindow::instance()->diveList->expandAll(); } -const QVector &DiveFilter::filteredDiveSites() const +const std::vector &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 DiveFilter::visibleDives() const std::vector 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; } diff --git a/core/divefilter.h b/core/divefilter.h index 403e2871a..076b9249f 100644 --- a/core/divefilter.h +++ b/core/divefilter.h @@ -45,9 +45,9 @@ public: bool diveSiteMode() const; // returns true if we're filtering on dive site (on mobile always returns false) std::vector visibleDives() const; #ifndef SUBSURFACE_MOBILE - const QVector &filteredDiveSites() const; - void startFilterDiveSites(QVector ds); - void setFilterDiveSite(QVector ds); + const std::vector &filteredDiveSites() const; + void startFilterDiveSites(std::vector ds); + void setFilterDiveSite(std::vector ds); void stopFilterDiveSites(); #endif void setFilter(const FilterData &data); @@ -62,7 +62,7 @@ private: void updateDiveStatus(dive *d, bool newStatus, ShownChange &change, std::vector &removeFromSelection) const; - QVector dive_sites; + std::vector dive_sites; FilterData filterData; mutable int shown_dives; diff --git a/core/divelist.c b/core/divelist.c deleted file mode 100644 index e772c2d8e..000000000 --- a/core/divelist.c +++ /dev/null @@ -1,1417 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* divelist.c */ - -#include "divelist.h" -#include "subsurface-string.h" -#include "deco.h" -#include "device.h" -#include "dive.h" -#include "divelog.h" -#include "divesite.h" -#include "event.h" -#include "eventtype.h" -#include "filterpreset.h" -#include "fulltext.h" -#include "interpolate.h" -#include "planner.h" -#include "qthelper.h" -#include "gettext.h" -#include "git-access.h" -#include "selection.h" -#include "sample.h" -#include "table.h" -#include "trip.h" - -/* - * Get "maximal" dive gas for a dive. - * Rules: - * - Trimix trumps nitrox (highest He wins, O2 breaks ties) - * - Nitrox trumps air (even if hypoxic) - * These are the same rules as the inter-dive sorting rules. - */ -void get_dive_gas(const struct dive *dive, int *o2_p, int *he_p, int *o2max_p) -{ - int i; - int maxo2 = -1, maxhe = -1, mino2 = 1000; - - for (i = 0; i < dive->cylinders.nr; i++) { - const cylinder_t *cyl = get_cylinder(dive, i); - int o2 = get_o2(cyl->gasmix); - int he = get_he(cyl->gasmix); - - if (!is_cylinder_used(dive, i)) - continue; - if (cyl->cylinder_use == OXYGEN) - continue; - if (cyl->cylinder_use == NOT_USED) - continue; - if (o2 > maxo2) - maxo2 = o2; - if (o2 < mino2 && maxhe <= 0) - mino2 = o2; - if (he > maxhe) { - maxhe = he; - mino2 = o2; - } - } - /* All air? Show/sort as "air"/zero */ - if ((!maxhe && maxo2 == O2_IN_AIR && mino2 == maxo2) || - (maxo2 == -1 && maxhe == -1 && mino2 == 1000)) - maxo2 = mino2 = 0; - *o2_p = mino2; - *he_p = maxhe; - *o2max_p = maxo2; -} - -int total_weight(const struct dive *dive) -{ - int i, total_grams = 0; - - if (dive) - for (i = 0; i < dive->weightsystems.nr; i++) - total_grams += dive->weightsystems.weightsystems[i].weight.grams; - return total_grams; -} - -static int active_o2(const struct dive *dive, const struct divecomputer *dc, duration_t time) -{ - struct gasmix gas = get_gasmix_at_time(dive, dc, time); - return get_o2(gas); -} - -// Do not call on first sample as it acccesses the previous sample -static int get_sample_o2(const struct dive *dive, const struct divecomputer *dc, const struct sample *sample) -{ - int po2i, po2f, po2; - const struct sample *psample = sample - 1; - // Use sensor[0] if available - if ((dc->divemode == CCR || dc->divemode == PSCR) && sample->o2sensor[0].mbar) { - po2i = psample->o2sensor[0].mbar; - po2f = sample->o2sensor[0].mbar; // then use data from the first o2 sensor - po2 = (po2f + po2i) / 2; - } else if (sample->setpoint.mbar > 0) { - po2 = MIN((int) sample->setpoint.mbar, - depth_to_mbar(sample->depth.mm, dive)); - } else { - double amb_presure = depth_to_bar(sample->depth.mm, dive); - double pamb_pressure = depth_to_bar(psample->depth.mm , dive); - if (dc->divemode == PSCR) { - po2i = pscr_o2(pamb_pressure, get_gasmix_at_time(dive, dc, psample->time)); - po2f = pscr_o2(amb_presure, get_gasmix_at_time(dive, dc, sample->time)); - } else { - int o2 = active_o2(dive, dc, psample->time); // ... calculate po2 from depth and FiO2. - po2i = lrint(o2 * pamb_pressure); // (initial) po2 at start of segment - po2f = lrint(o2 * amb_presure); // (final) po2 at end of segment - } - po2 = (po2i + po2f) / 2; - } - return po2; -} - -/* Calculate OTU for a dive - this only takes the first divecomputer into account. - Implement the protocol in Erik Baker's document "Oxygen Toxicity Calculations". This code - implements a third-order continuous approximation of Baker's Eq. 2 and enables OTU - calculation for rebreathers. Baker obtained his information from: - Comroe Jr. JH et al. (1945) Oxygen toxicity. J. Am. Med. Assoc. 128,710-717 - Clark JM & CJ Lambertsen (1970) Pulmonary oxygen tolerance in man and derivation of pulmonary - oxygen tolerance curves. Inst. env. Med. Report 1-70, University of Pennsylvania, Philadelphia, USA. */ -static int calculate_otu(const struct dive *dive) -{ - int i; - double otu = 0.0; - const struct divecomputer *dc = &dive->dc; - for (i = 1; i < dc->samples; i++) { - int t; - int po2i, po2f; - double pm; - struct sample *sample = dc->sample + i; - struct sample *psample = sample - 1; - t = sample->time.seconds - psample->time.seconds; - // if there is sensor data use sensor[0] - if ((dc->divemode == CCR || dc->divemode == PSCR) && sample->o2sensor[0].mbar) { - po2i = psample->o2sensor[0].mbar; - po2f = sample->o2sensor[0].mbar; // ... use data from the first o2 sensor - } else { - if (sample->setpoint.mbar > 0) { - po2f = MIN((int) sample->setpoint.mbar, - depth_to_mbar(sample->depth.mm, dive)); - if (psample->setpoint.mbar > 0) - po2i = MIN((int) psample->setpoint.mbar, - depth_to_mbar(psample->depth.mm, dive)); - else - po2i = po2f; - } else { // For OC and rebreather without o2 sensor/setpoint - double amb_presure = depth_to_bar(sample->depth.mm, dive); - double pamb_pressure = depth_to_bar(psample->depth.mm , dive); - if (dc->divemode == PSCR) { - po2i = pscr_o2(pamb_pressure, get_gasmix_at_time(dive, dc, psample->time)); - po2f = pscr_o2(amb_presure, get_gasmix_at_time(dive, dc, sample->time)); - } else { - int o2 = active_o2(dive, dc, psample->time); // ... calculate po2 from depth and FiO2. - po2i = lrint(o2 * pamb_pressure); // (initial) po2 at start of segment - po2f = lrint(o2 * amb_presure); // (final) po2 at end of segment - } - } - } - if ((po2i > 500) || (po2f > 500)) { // If PO2 in segment is above 500 mbar then calculate otu - if (po2i <= 500) { // For descent segment with po2i <= 500 mbar .. - t = t * (po2f - 500) / (po2f - po2i); // .. only consider part with PO2 > 500 mbar - po2i = 501; // Mostly important for the dive planner with long segments - } else { - if (po2f <= 500){ - t = t * (po2i - 500) / (po2i - po2f); // For ascent segment with po2f <= 500 mbar .. - po2f = 501; // .. only consider part with PO2 > 500 mbar - } - } - pm = (po2f + po2i)/1000.0 - 1.0; - // This is a 3rd order continuous approximation of Baker's eq. 2, therefore Baker's eq. 1 is not used: - otu += t / 60.0 * pow(pm, 5.0/6.0) * (1.0 - 5.0 * (po2f - po2i) * (po2f - po2i) / 216000000.0 / (pm * pm)); - } - } - return lrint(otu); -} - -/* Calculate the CNS for a single dive - this only takes the first divecomputer into account. - The CNS contributions are summed for dive segments defined by samples. The maximum O2 exposure duration for each - segment is calculated based on the mean depth of the two samples (start & end) that define each segment. The CNS - contribution of each segment is found by dividing the time duration of the segment by its maximum exposure duration. - The contributions of all segments of the dive are summed to get the total CNS% value. This is a partial implementation - of the proposals in Erik Baker's document "Oxygen Toxicity Calculations" using fixed-depth calculations for the mean - po2 for each segment. Empirical testing showed that, for large changes in depth, the cns calculation for the mean po2 - value is extremely close, if not identical to the additive calculations for 0.1 bar increments in po2 from the start - to the end of the segment, assuming a constant rate of change in po2 (i.e. depth) with time. */ -static double calculate_cns_dive(const struct dive *dive) -{ - int n; - const struct divecomputer *dc = &dive->dc; - double cns = 0.0; - double rate; - /* Calculate the CNS for each sample in this dive and sum them */ - for (n = 1; n < dc->samples; n++) { - int t; - int po2; - struct sample *sample = dc->sample + n; - struct sample *psample = sample - 1; - t = sample->time.seconds - psample->time.seconds; - po2 = get_sample_o2(dive, dc, sample); - /* Don't increase CNS when po2 below 500 matm */ - if (po2 <= 500) - continue; - - // This formula is the result of fitting two lines to the Log of the NOAA CNS table - rate = po2 <= 1500 ? exp(-11.7853 + 0.00193873 * po2) : exp(-23.6349 + 0.00980829 * po2); - cns += (double) t * rate * 100.0; - } - return cns; -} - -/* this only gets called if dive->maxcns == 0 which means we know that - * none of the divecomputers has tracked any CNS for us - * so we calculated it "by hand" */ -static int calculate_cns(struct dive *dive) -{ - int i, divenr; - double cns = 0.0; - timestamp_t last_starttime, last_endtime = 0; - - /* shortcut */ - if (dive->cns) - return dive->cns; - - divenr = get_divenr(dive); - i = divenr >= 0 ? divenr : divelog.dives->nr; -#if DECO_CALC_DEBUG & 2 - if (i >= 0 && i < dive_table.nr) - printf("\n\n*** CNS for dive #%d %d\n", i, get_dive(i)->number); - else - printf("\n\n*** CNS for dive #%d\n", i); -#endif - /* Look at next dive in dive list table and correct i when needed */ - while (i < divelog.dives->nr - 1) { - struct dive *pdive = get_dive(i); - if (!pdive || pdive->when > dive->when) - break; - i++; - } - /* Look at previous dive in dive list table and correct i when needed */ - while (i > 0) { - struct dive *pdive = get_dive(i - 1); - if (!pdive || pdive->when < dive->when) - break; - i--; - } -#if DECO_CALC_DEBUG & 2 - printf("Dive number corrected to #%d\n", i); -#endif - last_starttime = dive->when; - /* Walk backwards to check previous dives - how far do we need to go back? */ - while (i--) { - if (i == divenr && i > 0) - i--; -#if DECO_CALC_DEBUG & 2 - printf("Check if dive #%d %d has to be considered as prev dive: ", i, get_dive(i)->number); -#endif - struct dive *pdive = get_dive(i); - /* we don't want to mix dives from different trips as we keep looking - * for how far back we need to go */ - if (dive->divetrip && pdive->divetrip != dive->divetrip) { -#if DECO_CALC_DEBUG & 2 - printf("No - other dive trip\n"); -#endif - continue; - } - if (!pdive || pdive->when >= dive->when || dive_endtime(pdive) + 12 * 60 * 60 < last_starttime) { -#if DECO_CALC_DEBUG & 2 - printf("No\n"); -#endif - break; - } - last_starttime = pdive->when; -#if DECO_CALC_DEBUG & 2 - printf("Yes\n"); -#endif - } - /* Walk forward and add dives and surface intervals to CNS */ - while (++i < divelog.dives->nr) { -#if DECO_CALC_DEBUG & 2 - printf("Check if dive #%d %d will be really added to CNS calc: ", i, get_dive(i)->number); -#endif - struct dive *pdive = get_dive(i); - /* again skip dives from different trips */ - if (dive->divetrip && dive->divetrip != pdive->divetrip) { -#if DECO_CALC_DEBUG & 2 - printf("No - other dive trip\n"); -#endif - continue; - } - /* Don't add future dives */ - if (pdive->when >= dive->when) { -#if DECO_CALC_DEBUG & 2 - printf("No - future or same dive\n"); -#endif - break; - } - /* Don't add the copy of the dive itself */ - if (i == divenr) { -#if DECO_CALC_DEBUG & 2 - printf("No - copy of dive\n"); -#endif - continue; - } -#if DECO_CALC_DEBUG & 2 - printf("Yes\n"); -#endif - - /* CNS reduced with 90min halftime during surface interval */ - if (last_endtime) - cns /= pow(2, (pdive->when - last_endtime) / (90.0 * 60.0)); -#if DECO_CALC_DEBUG & 2 - printf("CNS after surface interval: %f\n", cns); -#endif - - cns += calculate_cns_dive(pdive); -#if DECO_CALC_DEBUG & 2 - printf("CNS after previous dive: %f\n", cns); -#endif - - last_starttime = pdive->when; - last_endtime = dive_endtime(pdive); - } - - /* CNS reduced with 90min halftime during surface interval */ - if (last_endtime) - cns /= pow(2, (dive->when - last_endtime) / (90.0 * 60.0)); -#if DECO_CALC_DEBUG & 2 - printf("CNS after last surface interval: %f\n", cns); -#endif - - cns += calculate_cns_dive(dive); -#if DECO_CALC_DEBUG & 2 - printf("CNS after dive: %f\n", cns); -#endif - - /* save calculated cns in dive struct */ - dive->cns = lrint(cns); - return dive->cns; -} -/* - * Return air usage (in liters). - */ -static double calculate_airuse(const struct dive *dive) -{ - int airuse = 0; - int i; - - // SAC for a CCR dive does not make sense. - if (dive->dc.divemode == CCR) - return 0.0; - - for (i = 0; i < dive->cylinders.nr; i++) { - pressure_t start, end; - const cylinder_t *cyl = get_cylinder(dive, i); - - start = cyl->start.mbar ? cyl->start : cyl->sample_start; - end = cyl->end.mbar ? cyl->end : cyl->sample_end; - if (!end.mbar || start.mbar <= end.mbar) { - // If a cylinder is used but we do not have info on amout of gas used - // better not pretend we know the total gas use. - // Eventually, logic should be fixed to compute average depth and total time - // for those segments where cylinders with known pressure drop are breathed from. - if (is_cylinder_used(dive, i)) - return 0.0; - else - continue; - } - - airuse += gas_volume(cyl, start) - gas_volume(cyl, end); - } - return airuse / 1000.0; -} - -/* this only uses the first divecomputer to calculate the SAC rate */ -static int calculate_sac(const struct dive *dive) -{ - const struct divecomputer *dc = &dive->dc; - double airuse, pressure, sac; - int duration, meandepth; - - airuse = calculate_airuse(dive); - if (!airuse) - return 0; - - duration = dc->duration.seconds; - if (!duration) - return 0; - - meandepth = dc->meandepth.mm; - if (!meandepth) - return 0; - - /* Mean pressure in ATM (SAC calculations are in atm*l/min) */ - pressure = depth_to_atm(meandepth, dive); - sac = airuse / pressure * 60 / duration; - - /* milliliters per minute.. */ - return lrint(sac * 1000); -} - -/* for now we do this based on the first divecomputer */ -static void add_dive_to_deco(struct deco_state *ds, struct dive *dive, bool in_planner) -{ - struct divecomputer *dc = &dive->dc; - struct gasmix gasmix = gasmix_air; - int i; - const struct event *ev = NULL, *evd = NULL; - enum divemode_t current_divemode = UNDEF_COMP_TYPE; - - if (!dc) - return; - - for (i = 1; i < dc->samples; i++) { - struct sample *psample = dc->sample + i - 1; - struct sample *sample = dc->sample + i; - int t0 = psample->time.seconds; - int t1 = sample->time.seconds; - int j; - - for (j = t0; j < t1; j++) { - int depth = interpolate(psample->depth.mm, sample->depth.mm, j - t0, t1 - t0); - gasmix = get_gasmix(dive, dc, j, &ev, gasmix); - add_segment(ds, depth_to_bar(depth, dive), gasmix, 1, sample->setpoint.mbar, - get_current_divemode(&dive->dc, j, &evd, ¤t_divemode), dive->sac, - in_planner); - } - } -} - -int get_divenr(const struct dive *dive) -{ - int i; - const struct dive *d; - // tempting as it may be, don't die when called with dive=NULL - if (dive) - for_each_dive(i, d) { - if (d->id == dive->id) // don't compare pointers, we could be passing in a copy of the dive - return i; - } - return -1; -} - -static struct gasmix air = { .o2.permille = O2_IN_AIR, .he.permille = 0 }; - -/* take into account previous dives until there is a 48h gap between dives */ -/* return last surface time before this dive or dummy value of 48h */ -/* return negative surface time if dives are overlapping */ -/* The place you call this function is likely the place where you want - * to create the deco_state */ -int init_decompression(struct deco_state *ds, const struct dive *dive, bool in_planner) -{ - int i, divenr = -1; - int surface_time = 48 * 60 * 60; - timestamp_t last_endtime = 0, last_starttime = 0; - bool deco_init = false; - double surface_pressure; - - if (!dive) - return false; - - divenr = get_divenr(dive); - i = divenr >= 0 ? divenr : divelog.dives->nr; -#if DECO_CALC_DEBUG & 2 - if (i >= 0 && i < dive_table.nr) - printf("\n\n*** Init deco for dive #%d %d\n", i, get_dive(i)->number); - else - printf("\n\n*** Init deco for dive #%d\n", i); -#endif - /* Look at next dive in dive list table and correct i when needed */ - while (i < divelog.dives->nr - 1) { - struct dive *pdive = get_dive(i); - if (!pdive || pdive->when > dive->when) - break; - i++; - } - /* Look at previous dive in dive list table and correct i when needed */ - while (i > 0) { - struct dive *pdive = get_dive(i - 1); - if (!pdive || pdive->when < dive->when) - break; - i--; - } -#if DECO_CALC_DEBUG & 2 - printf("Dive number corrected to #%d\n", i); -#endif - last_starttime = dive->when; - /* Walk backwards to check previous dives - how far do we need to go back? */ - while (i--) { - if (i == divenr && i > 0) - i--; -#if DECO_CALC_DEBUG & 2 - printf("Check if dive #%d %d has to be considered as prev dive: ", i, get_dive(i)->number); -#endif - struct dive *pdive = get_dive(i); - /* we don't want to mix dives from different trips as we keep looking - * for how far back we need to go */ - if (dive->divetrip && pdive->divetrip != dive->divetrip) { -#if DECO_CALC_DEBUG & 2 - printf("No - other dive trip\n"); -#endif - continue; - } - if (!pdive || pdive->when >= dive->when || dive_endtime(pdive) + 48 * 60 * 60 < last_starttime) { -#if DECO_CALC_DEBUG & 2 - printf("No\n"); -#endif - break; - } - last_starttime = pdive->when; -#if DECO_CALC_DEBUG & 2 - printf("Yes\n"); -#endif - } - /* Walk forward an add dives and surface intervals to deco */ - while (++i < divelog.dives->nr) { -#if DECO_CALC_DEBUG & 2 - printf("Check if dive #%d %d will be really added to deco calc: ", i, get_dive(i)->number); -#endif - struct dive *pdive = get_dive(i); - /* again skip dives from different trips */ - if (dive->divetrip && dive->divetrip != pdive->divetrip) { -#if DECO_CALC_DEBUG & 2 - printf("No - other dive trip\n"); -#endif - continue; - } - /* Don't add future dives */ - if (pdive->when >= dive->when) { -#if DECO_CALC_DEBUG & 2 - printf("No - future or same dive\n"); -#endif - break; - } - /* Don't add the copy of the dive itself */ - if (i == divenr) { -#if DECO_CALC_DEBUG & 2 - printf("No - copy of dive\n"); -#endif - continue; - } -#if DECO_CALC_DEBUG & 2 - printf("Yes\n"); -#endif - - surface_pressure = get_surface_pressure_in_mbar(pdive, true) / 1000.0; - /* Is it the first dive we add? */ - if (!deco_init) { -#if DECO_CALC_DEBUG & 2 - printf("Init deco\n"); -#endif - clear_deco(ds, surface_pressure, in_planner); - deco_init = true; -#if DECO_CALC_DEBUG & 2 - printf("Tissues after init:\n"); - dump_tissues(ds); -#endif - } else { - surface_time = pdive->when - last_endtime; - if (surface_time < 0) { -#if DECO_CALC_DEBUG & 2 - printf("Exit because surface intervall is %d\n", surface_time); -#endif - return surface_time; - } - add_segment(ds, surface_pressure, air, surface_time, 0, OC, prefs.decosac, in_planner); -#if DECO_CALC_DEBUG & 2 - printf("Tissues after surface intervall of %d:%02u:\n", FRACTION_TUPLE(surface_time, 60)); - dump_tissues(ds); -#endif - } - - add_dive_to_deco(ds, pdive, in_planner); - - last_starttime = pdive->when; - last_endtime = dive_endtime(pdive); - clear_vpmb_state(ds); -#if DECO_CALC_DEBUG & 2 - printf("Tissues after added dive #%d:\n", pdive->number); - dump_tissues(ds); -#endif - } - - surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; - /* We don't have had a previous dive at all? */ - if (!deco_init) { -#if DECO_CALC_DEBUG & 2 - printf("Init deco\n"); -#endif - clear_deco(ds, surface_pressure, in_planner); -#if DECO_CALC_DEBUG & 2 - printf("Tissues after no previous dive, surface time set to 48h:\n"); - dump_tissues(ds); -#endif - } else { - surface_time = dive->when - last_endtime; - if (surface_time < 0) { -#if DECO_CALC_DEBUG & 2 - printf("Exit because surface intervall is %d\n", surface_time); -#endif - return surface_time; - } - add_segment(ds, surface_pressure, air, surface_time, 0, OC, prefs.decosac, in_planner); -#if DECO_CALC_DEBUG & 2 - printf("Tissues after surface intervall of %d:%02u:\n", FRACTION_TUPLE(surface_time, 60)); - dump_tissues(ds); -#endif - } - - // I do not dare to remove this call. We don't need the result but it might have side effects. Bummer. - tissue_tolerance_calc(ds, dive, surface_pressure, in_planner); - return surface_time; -} - -void update_cylinder_related_info(struct dive *dive) -{ - if (dive != NULL) { - dive->sac = calculate_sac(dive); - dive->otu = calculate_otu(dive); - if (dive->maxcns == 0) - dive->maxcns = calculate_cns(dive); - } -} - -/* Like strcmp(), but don't crash on null-pointers */ -static int safe_strcmp(const char *s1, const char *s2) -{ - return strcmp(s1 ? s1 : "", s2 ? s2 : ""); -} - -/* Compare a list of dive computers by model name */ -static int comp_dc(const struct divecomputer *dc1, const struct divecomputer *dc2) -{ - int cmp; - while (dc1 || dc2) { - if (!dc1) - return -1; - if (!dc2) - return 1; - if ((cmp = safe_strcmp(dc1->model, dc2->model)) != 0) - return cmp; - dc1 = dc1->next; - dc2 = dc2->next; - } - return 0; -} - -/* This function defines the sort ordering of dives. The core - * and the UI models should use the same sort function, which - * should be stable. This is not crucial at the moment, as the - * indices in core and UI are independent, but ultimately we - * probably want to unify the models. - * After editing a key used in this sort-function, the order of - * the dives must be re-astablished. - * Currently, this does a lexicographic sort on the - * (start-time, trip-time, number, id) tuple. - * trip-time is defined such that dives that do not belong to - * a trip are sorted *after* dives that do. Thus, in the default - * chronologically-descending sort order, they are shown *before*. - * "id" is a stable, strictly increasing unique number, that - * is handed out when a dive is added to the system. - * We might also consider sorting by end-time and other criteria, - * but see the caveat above (editing means rearrangement of the dives). - */ -int comp_dives(const struct dive *a, const struct dive *b) -{ - int cmp; - if (a->when < b->when) - return -1; - if (a->when > b->when) - return 1; - if (a->divetrip != b->divetrip) { - if (!b->divetrip) - return -1; - if (!a->divetrip) - return 1; - if (trip_date(a->divetrip) < trip_date(b->divetrip)) - return -1; - if (trip_date(a->divetrip) > trip_date(b->divetrip)) - return 1; - } - if (a->number < b->number) - return -1; - if (a->number > b->number) - return 1; - if ((cmp = comp_dc(&a->dc, &b->dc)) != 0) - return cmp; - if (a->id < b->id) - return -1; - if (a->id > b->id) - return 1; - return 0; /* this should not happen for a != b */ -} - -/* Dive table functions */ -static MAKE_GROW_TABLE(dive_table, struct dive *, dives) -MAKE_GET_INSERTION_INDEX(dive_table, struct dive *, dives, dive_less_than) -MAKE_ADD_TO(dive_table, struct dive *, dives) -static MAKE_REMOVE_FROM(dive_table, dives) -static MAKE_GET_IDX(dive_table, struct dive *, dives) -MAKE_SORT(dive_table, struct dive *, dives, comp_dives) -MAKE_REMOVE(dive_table, struct dive *, dive) -MAKE_CLEAR_TABLE(dive_table, dives, dive) -MAKE_MOVE_TABLE(dive_table, dives) - -void insert_dive(struct dive_table *table, struct dive *d) -{ - int idx = dive_table_get_insertion_index(table, d); - add_to_dive_table(table, idx, d); -} - -/* - * 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 dive_table *table, struct trip_table *trip_table_arg) -{ - int from, to; - dive_trip_t *trip; - int i, j; - bool alloc; - - if (!divelog.autogroup) - return; - - for (i = 0; (trip = get_dives_to_autogroup(table, i, &from, &to, &alloc)) != NULL; i = to) { - for (j = from; j < to; ++j) - add_dive_to_trip(table->dives[j], trip); - /* If this was newly allocated, add trip to list */ - if (alloc) - insert_trip(trip, trip_table_arg); - } - sort_trip_table(trip_table_arg); -#ifdef DEBUG_TRIP - dump_trip_list(); -#endif -} - -/* Remove a dive from a dive table. This assumes that the - * dive was already removed from any trip and deselected. - * It simply shrinks the table and frees the trip */ -void delete_dive_from_table(struct dive_table *table, int idx) -{ - free_dive(table->dives[idx]); - remove_from_dive_table(table, idx); -} - -struct dive *get_dive_from_table(int nr, const struct dive_table *dt) -{ - if (nr >= dt->nr || nr < 0) - return NULL; - return dt->dives[nr]; -} - -/* This removes a dive from the global dive table but doesn't free the - * resources associated with the dive. The caller must removed the dive - * from the trip-list. Returns a pointer to the unregistered dive. - * The unregistered dive has the selection- and hidden-flags cleared. */ -struct dive *unregister_dive(int idx) -{ - struct dive *dive = get_dive(idx); - if (!dive) - return NULL; /* this should never happen */ - /* When removing a dive from the global dive table, - * we also have to unregister its fulltext cache. */ - fulltext_unregister(dive); - remove_from_dive_table(divelog.dives, idx); - if (dive->selected) - amount_selected--; - dive->selected = false; - return dive; -} - -void process_loaded_dives() -{ - sort_dive_table(divelog.dives); - sort_trip_table(divelog.trips); - - /* Autogroup dives if desired by user. */ - autogroup_dives(divelog.dives, divelog.trips); - - 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 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) -{ - int i; - for (i = 1; i < table->nr; i++) { - struct dive *prev = table->dives[i - 1]; - struct dive *dive = table->dives[i]; - struct dive *merged; - struct dive_site *ds; - - /* 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 && - dive_endtime(prev) < dive->when) - continue; - - merged = try_to_merge(prev, dive, false); - if (!merged) - continue; - - /* Add dive to dive site; try_to_merge() does not do that! */ - ds = merged->dive_site; - if (ds) { - merged->dive_site = NULL; - add_dive_to_dive_site(merged, ds); - } - unregister_dive_from_dive_site(prev); - unregister_dive_from_dive_site(dive); - unregister_dive_from_trip(prev); - unregister_dive_from_trip(dive); - - /* Overwrite the first of the two dives and remove the second */ - free_dive(prev); - table->dives[i - 1] = merged; - delete_dive_from_table(table, i); - - /* Redo the new 'i'th dive */ - i--; - } -} - -/* - * 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, int idx, struct dive_table *table, bool prefer_imported, - /* output parameters: */ - struct dive_table *dives_to_add, struct dive_table *dives_to_remove) -{ - struct dive *old_dive = table->dives[idx]; - struct dive *merged = try_to_merge(old_dive, dive_to_add, prefer_imported); - if (!merged) - return false; - - merged->divetrip = old_dive->divetrip; - insert_dive(dives_to_remove, old_dive); - insert_dive(dives_to_add, merged); - - return true; -} - -/* Check if a dive is ranked after the last dive of the global dive list */ -static bool dive_is_after_last(struct dive *d) -{ - if (divelog.dives->nr == 0) - return true; - return dive_less_than(divelog.dives->dives[divelog.dives->nr - 1], d); -} - -/* Merge dives from "dives_from" into "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 pointed to by "num_merged" will be increased for every - * merged dive that is added to "dives_to_add" */ -static bool merge_dive_tables(struct dive_table *dives_from, struct dive_table *delete_from, - struct dive_table *dives_to, - bool prefer_imported, struct dive_trip *trip, - /* output parameters: */ - struct dive_table *dives_to_add, struct dive_table *dives_to_remove, - int *num_merged) -{ - int i, j; - int last_merged_into = -1; - 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. - */ - j = 0; /* Index in dives_to */ - for (i = 0; i < dives_from->nr; i++) { - struct dive *dive_to_add = dives_from->dives[i]; - - if (delete_from) - remove_dive(dive_to_add, delete_from); - - /* Find insertion point. */ - while (j < dives_to->nr && dive_less_than(dives_to->dives[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 && j - 1 > last_merged_into && - dive_endtime(dives_to->dives[j - 1]) > dive_to_add->when) { - if (try_to_merge_into(dive_to_add, j - 1, dives_to, prefer_imported, - dives_to_add, dives_to_remove)) { - free_dive(dive_to_add); - 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->nr && j > last_merged_into && - dive_endtime(dive_to_add) > dives_to->dives[j]->when) { - if (try_to_merge_into(dive_to_add, j, dives_to, prefer_imported, - dives_to_add, dives_to_remove)) { - free_dive(dive_to_add); - last_merged_into = j; - (*num_merged)++; - continue; - } - } - - /* We couldnt merge dives, simply add to list of dives to-be-added. */ - insert_dive(dives_to_add, dive_to_add); - sequence_changed |= !dive_is_after_last(dive_to_add); - dive_to_add->divetrip = trip; - } - - /* we took care of all dives, clean up the import table */ - dives_from->nr = 0; - - return sequence_changed; -} - -/* 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 add_imported_dives(struct divelog *import_log, int flags) -{ - int i, idx; - 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 dive_sites_to_add = empty_dive_site_table; - struct device_table *devices_to_add = alloc_device_table(); - - /* Process imported dives and generate lists of dives - * to-be-added and to-be-removed */ - process_imported_dives(import_log, flags, &dives_to_add, &dives_to_remove, &trips_to_add, - &dive_sites_to_add, devices_to_add); - - /* 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 (i = 0; i < dives_to_add.nr; i++) { - struct dive *d = dives_to_add.dives[i]; - struct dive_trip *trip = d->divetrip; - struct dive_site *site = d->dive_site; - d->divetrip = NULL; - d->dive_site = NULL; - add_dive_to_trip(d, trip); - add_dive_to_dive_site(d, site); - } - - /* Remove old dives */ - for (i = 0; i < dives_to_remove.nr; i++) { - idx = get_divenr(dives_to_remove.dives[i]); - delete_single_dive(&divelog, idx); - } - dives_to_remove.nr = 0; - - /* Add new dives */ - for (i = 0; i < dives_to_add.nr; i++) - insert_dive(divelog.dives, dives_to_add.dives[i]); - dives_to_add.nr = 0; - - /* Add new trips */ - for (i = 0; i < trips_to_add.nr; i++) - insert_trip(trips_to_add.trips[i], divelog.trips); - trips_to_add.nr = 0; - - /* Add new dive sites */ - for (i = 0; i < dive_sites_to_add.nr; i++) - register_dive_site(dive_sites_to_add.dive_sites[i]); - dive_sites_to_add.nr = 0; - - /* Add new devices */ - for (i = 0; i < nr_devices(devices_to_add); i++) { - const struct device *dev = get_device(devices_to_add, i); - add_to_device_table(divelog.devices, dev); - } - - /* We might have deleted the old selected dive. - * Choose the newest dive as selected (if any) */ - current_dive = divelog.dives->nr > 0 ? divelog.dives->dives[divelog.dives->nr - 1] : NULL; - - free_device_table(devices_to_add); - free(dives_to_add.dives); - free(dives_to_remove.dives); - free(trips_to_add.trips); - free(dive_sites_to_add.dive_sites); - - /* Inform frontend of reset data. This should reset all the models. */ - emit_reset_signal(); -} - -/* 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. - */ -bool try_to_merge_trip(struct dive_trip *trip_import, struct dive_table *import_table, bool prefer_imported, - /* output parameters: */ - struct dive_table *dives_to_add, struct dive_table *dives_to_remove, - bool *sequence_changed, int *start_renumbering_at) -{ - int i; - struct dive_trip *trip_old; - - for (i = 0; i < divelog.trips->nr; i++) { - trip_old = divelog.trips->trips[i]; - if (trips_overlap(trip_import, trip_old)) { - *sequence_changed |= merge_dive_tables(&trip_import->dives, import_table, &trip_old->dives, - prefer_imported, trip_old, - dives_to_add, dives_to_remove, - start_renumbering_at); - free_trip(trip_import); /* All dives in trip have been consumed -> free */ - return true; - } - } - - return false; -} - -/* Process imported dives: take a table of dives to be imported and - * generate five lists: - * 1) Dives to be added - * 2) Dives to be removed - * 3) Trips to be added - * 4) Dive sites to be added - * 5) Devices to be added - * The dives to be added are owning (i.e. the caller is responsible - * for freeing them). - * The dives, trips and sites in "import_table", "import_trip_table" - * and "import_sites_table" are consumed. On return, the tables have - * size 0. "import_trip_table" may be NULL if all dives are not associated - * with a trip. - * The output tables should be empty - if not, their content - * will be cleared! - * - * 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_PREFER_IMPORTED is set, data of the new dives are - * prioritized on merging. - * - If IMPORT_MERGE_ALL_TRIPS is set, all overlapping trips will - * be merged, not only non-autogenerated trips. - * - If IMPORT_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_ADD_TO_NEW_TRIP is true, dives that are not assigned - * to a trip will be added to a newly generated trip. - */ -void process_imported_dives(struct divelog *import_log, int flags, - /* output parameters: */ - 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) -{ - int i, j, nr, start_renumbering_at = 0; - struct dive_trip *trip_import, *new_trip; - bool sequence_changed = false; - bool new_dive_has_number = false; - bool last_old_dive_is_numbered; - - /* Make sure that output parameters don't contain garbage */ - clear_dive_table(dives_to_add); - clear_dive_table(dives_to_remove); - clear_trip_table(trips_to_add); - clear_dive_site_table(sites_to_add); - clear_device_table(devices_to_add); - - /* 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 */ - for (int i = 0; i < import_log->dives->nr; i++) { - if (import_log->dives->dives[i]->number > 0) { - new_dive_has_number = true; - break; - } - } - - /* If no dives were imported, don't bother doing anything */ - if (!import_log->dives->nr) - return; - - /* Add only the devices that we don't know about yet. */ - for (i = 0; i < nr_devices(import_log->devices); i++) { - const struct device *dev = get_device(import_log->devices, i); - if (!device_exists(divelog.devices, dev)) - add_to_device_table(devices_to_add, dev); - } - - /* Sort the table of dives to be imported and combine mergable dives */ - sort_dive_table(import_log->dives); - 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_ADD_TO_NEW_TRIP)) - autogroup_dives(import_log->dives, import_log->trips); - - /* If dive sites already exist, use the existing versions. */ - for (i = 0; i < import_log->sites->nr; i++) { - struct dive_site *new_ds = import_log->sites->dive_sites[i]; - struct dive_site *old_ds = get_same_dive_site(new_ds); - - /* Check if it dive site is actually used by new dives. */ - for (j = 0; j < import_log->dives->nr; j++) { - if (import_log->dives->dives[j]->dive_site == new_ds) - break; - } - - if (j == import_log->dives->nr) { - /* Dive site not even used - free it and go to next. */ - free_dive_site(new_ds); - continue; - } - - if (!old_ds) { - /* Dive site doesn't exist. Add it to list of dive sites to be added. */ - new_ds->dives.nr = 0; /* Caller is responsible for adding dives to site */ - add_dive_site_to_table(new_ds, sites_to_add); - continue; - } - /* Dive site already exists - use the old and free the new. */ - for (j = 0; j < import_log->dives->nr; j++) { - if (import_log->dives->dives[j]->dive_site == new_ds) - import_log->dives->dives[j]->dive_site = old_ds; - } - free_dive_site(new_ds); - } - import_log->sites->nr = 0; /* All dive sites were consumed */ - - /* 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 (i = 0; i < import_log->trips->nr; i++) { - trip_import = import_log->trips->trips[i]; - if ((flags & IMPORT_MERGE_ALL_TRIPS) || trip_import->autogen) { - if (try_to_merge_trip(trip_import, import_log->dives, flags & IMPORT_PREFER_IMPORTED, dives_to_add, 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 (j = 0; j < trip_import->dives.nr; j++) { - struct dive *d = trip_import->dives.dives[j]; - - /* Add dive to list of dives to-be-added. */ - insert_dive(dives_to_add, d); - sequence_changed |= !dive_is_after_last(d); - - remove_dive(d, import_log->dives); - } - - /* Then, add trip to list of trips to add */ - insert_trip(trip_import, trips_to_add); - trip_import->dives.nr = 0; /* Caller is responsible for adding dives to trip */ - } - import_log->trips->nr = 0; /* All trips were consumed */ - - if ((flags & IMPORT_ADD_TO_NEW_TRIP) && import_log->dives->nr > 0) { - /* Create a new trip for unassigned dives, if desired. */ - new_trip = create_trip_from_dive(import_log->dives->dives[0]); - insert_trip(new_trip, trips_to_add); - - /* Add all remaining dives to this trip */ - for (i = 0; i < import_log->dives->nr; i++) { - struct dive *d = import_log->dives->dives[i]; - d->divetrip = new_trip; - insert_dive(dives_to_add, d); - sequence_changed |= !dive_is_after_last(d); - } - - import_log->dives->nr = 0; /* All dives were consumed */ - } else if (import_log->dives->nr > 0) { - /* 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(import_log->dives, NULL, divelog.dives, flags & IMPORT_PREFER_IMPORTED, NULL, - dives_to_add, 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 = divelog.dives->nr == 0 || divelog.dives->dives[divelog.dives->nr - 1]->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) { - nr = divelog.dives->nr > 0 ? divelog.dives->dives[divelog.dives->nr - 1]->number : 0; - for (i = start_renumbering_at; i < dives_to_add->nr; i++) - dives_to_add->dives[i]->number = ++nr; - } -} - -static struct dive *get_last_valid_dive() -{ - int i; - for (i = divelog.dives->nr - 1; i >= 0; i--) { - if (!divelog.dives->dives[i]->invalid) - return divelog.dives->dives[i]; - } - return NULL; -} - -/* return the number a dive gets when inserted at the given index. - * this function is supposed to be called *before* a dive was added. - * this returns: - * - 1 for an empty log - * - last_nr+1 for addition at end of log (if last dive had a number) - * - 0 for all other cases - */ -int get_dive_nr_at_idx(int idx) -{ - if (idx < divelog.dives->nr) - return 0; - struct dive *last_dive = get_last_valid_dive(); - if (!last_dive) - return 1; - return last_dive->number ? last_dive->number + 1 : 0; -} - -static int min_datafile_version; - -int get_min_datafile_version() -{ - return min_datafile_version; -} - -void reset_min_datafile_version() -{ - min_datafile_version = 0; -} - -void report_datafile_version(int version) -{ - if (min_datafile_version == 0 || min_datafile_version > version) - min_datafile_version = version; -} - -void clear_dive_file_data() -{ - fulltext_unregister_all(); - select_single_dive(NULL); // This is propagate up to the UI and clears all the information. - - current_dive = NULL; - clear_divelog(&divelog); - - clear_event_types(); - - reset_min_datafile_version(); - clear_git_id(); - - reset_tank_info_table(&tank_info_table); - - /* Inform frontend of reset data. This should reset all the models. */ - emit_reset_signal(); -} - -bool dive_less_than(const struct dive *a, const struct dive *b) -{ - return comp_dives(a, b) < 0; -} - -/* When comparing a dive to a trip, use the first dive of the trip. */ -static int comp_dive_to_trip(struct dive *a, struct dive_trip *b) -{ - /* This should never happen, nevertheless don't crash on trips - * with no (or worse a negative number of) dives. */ - if (!b || b->dives.nr <= 0) - return -1; - return comp_dives(a, b->dives.dives[0]); -} - -static int comp_dive_or_trip(struct dive_or_trip a, struct dive_or_trip b) -{ - /* we should only be called with both a and b having exactly one of - * dive or trip not NULL. But in an abundance of caution, make sure - * we still give a consistent answer even when called with invalid - * arguments, as otherwise we might be hunting down crashes at a later - * time... - */ - if (!a.dive && !a.trip && !b.dive && !b.trip) - return 0; - if (!a.dive && !a.trip) - return -1; - if (!b.dive && !b.trip) - return 1; - if (a.dive && b.dive) - return comp_dives(a.dive, b.dive); - if (a.trip && b.trip) - return comp_trips(a.trip, b.trip); - if (a.dive) - return comp_dive_to_trip(a.dive, b.trip); - else - return -comp_dive_to_trip(b.dive, a.trip); -} - -bool dive_or_trip_less_than(struct dive_or_trip a, struct dive_or_trip b) -{ - return comp_dive_or_trip(a, b) < 0; -} - -/* - * Calculate surface interval for dive starting at "when". Currently, we - * might display dives which are not yet in the divelist, therefore the - * input parameter is a timestamp. - * If the given dive starts during a different dive, the surface interval - * is 0. If we can't determine a surface interval (first dive), <0 is - * returned. This does *not* consider pathological cases such as dives - * that happened inside other dives. The interval will always be calculated - * with respect to the dive that started previously. - */ -timestamp_t get_surface_interval(timestamp_t when) -{ - int i; - timestamp_t prev_end; - - /* find previous dive. might want to use a binary search. */ - for (i = divelog.dives->nr - 1; i >= 0; --i) { - if (divelog.dives->dives[i]->when < when) - break; - } - if (i < 0) - return -1; - - prev_end = dive_endtime(divelog.dives->dives[i]); - if (prev_end > when) - return 0; - return when - prev_end; -} - -/* Find visible dive close to given date. First search towards older, - * then newer dives. */ -struct dive *find_next_visible_dive(timestamp_t when) -{ - int i, j; - - if (!divelog.dives->nr) - return NULL; - - /* we might want to use binary search here */ - for (i = 0; i < divelog.dives->nr; i++) { - if (when <= get_dive(i)->when) - break; - } - - for (j = i - 1; j > 0; j--) { - if (!get_dive(j)->hidden_by_filter) - return get_dive(j); - } - - for (j = i; j < divelog.dives->nr; j++) { - if (!get_dive(j)->hidden_by_filter) - return get_dive(j); - } - - return NULL; -} - -bool has_dive(unsigned int deviceid, unsigned int diveid) -{ - int i; - struct dive *dive; - - for_each_dive (i, dive) { - struct divecomputer *dc; - - for_each_dc (dive, dc) { - if (dc->deviceid != deviceid) - continue; - if (dc->diveid != diveid) - continue; - return 1; - } - } - return 0; -} diff --git a/core/divelist.cpp b/core/divelist.cpp new file mode 100644 index 000000000..4bb34b09e --- /dev/null +++ b/core/divelist.cpp @@ -0,0 +1,1255 @@ +// SPDX-License-Identifier: GPL-2.0 +/* divelist.cpp */ + +#include "divelist.h" +#include "subsurface-string.h" +#include "deco.h" +#include "device.h" +#include "dive.h" +#include "divelog.h" +#include "divesite.h" +#include "event.h" +#include "eventtype.h" +#include "filterpreset.h" +#include "fulltext.h" +#include "interpolate.h" +#include "planner.h" +#include "qthelper.h" // for emit_reset_signal() -> should be removed +#include "range.h" +#include "gettext.h" +#include "git-access.h" +#include "selection.h" +#include "sample.h" +#include "trip.h" +#include "version.h" + +#include + +void dive_table::record_dive(std::unique_ptr d) +{ + fixup_dive(*d); + put(std::move(d)); +} + +void dive_table::fixup_dive(struct dive &dive) const +{ + dive.fixup_no_cylinder(); + update_cylinder_related_info(dive); +} + +struct start_end_pressure { + pressure_t start; + pressure_t end; +}; + +void dive_table::force_fixup_dive(struct dive &d) const +{ + struct divecomputer *dc = &d.dcs[0]; + int old_temp = dc->watertemp.mkelvin; + int old_mintemp = d.mintemp.mkelvin; + int old_maxtemp = d.maxtemp.mkelvin; + duration_t old_duration = d.duration; + std::vector old_pressures(d.cylinders.size()); + + d.maxdepth = 0_m; + dc->maxdepth = 0_m; + d.watertemp = 0_K; + dc->watertemp = 0_K; + d.duration = 0_sec; + d.maxtemp = 0_K; + d.mintemp = 0_K; + for (auto [i, cyl]: enumerated_range(d.cylinders)) { + old_pressures[i].start = cyl.start; + old_pressures[i].end = cyl.end; + cyl.start = 0_bar; + cyl.end = 0_bar; + } + + fixup_dive(d); + + if (!d.watertemp.mkelvin) + d.watertemp.mkelvin = old_temp; + + if (!dc->watertemp.mkelvin) + dc->watertemp.mkelvin = old_temp; + + if (!d.maxtemp.mkelvin) + d.maxtemp.mkelvin = old_maxtemp; + + if (!d.mintemp.mkelvin) + d.mintemp.mkelvin = old_mintemp; + + if (!d.duration.seconds) + d.duration = old_duration; + for (auto [i, cyl]: enumerated_range(d.cylinders)) { + if (!cyl.start.mbar) + cyl.start = old_pressures[i].start; + if (!cyl.end.mbar) + cyl.end = old_pressures[i].end; + } +} + +// create a dive an hour from now with a default depth (15m/45ft) and duration (40 minutes) +// as a starting point for the user to edit +std::unique_ptr dive_table::default_dive() +{ + auto d = std::make_unique(); + d->when = time(nullptr) + gettimezoneoffset() + 3600; + d->dcs[0].duration = 40_min; + d->dcs[0].maxdepth.mm = M_OR_FT(15, 45); + d->dcs[0].meandepth.mm = M_OR_FT(13, 39); // this creates a resonable looking safety stop + make_manually_added_dive_dc(&d->dcs[0]); + fake_dc(&d->dcs[0]); + add_default_cylinder(d.get()); + fixup_dive(*d); + return d; +} + +static int active_o2(const struct dive &dive, const struct divecomputer *dc, duration_t time) +{ + struct gasmix gas = dive.get_gasmix_at_time(*dc, time); + return get_o2(gas); +} + +// Do not call on first sample as it acccesses the previous sample +static int get_sample_o2(const struct dive &dive, const struct divecomputer *dc, const struct sample &sample, const struct sample &psample) +{ + int po2i, po2f, po2; + // Use sensor[0] if available + if ((dc->divemode == CCR || dc->divemode == PSCR) && sample.o2sensor[0].mbar) { + po2i = psample.o2sensor[0].mbar; + po2f = sample.o2sensor[0].mbar; // then use data from the first o2 sensor + po2 = (po2f + po2i) / 2; + } else if (sample.setpoint.mbar > 0) { + po2 = std::min((int) sample.setpoint.mbar, + dive.depth_to_mbar(sample.depth.mm)); + } else { + double amb_presure = dive.depth_to_bar(sample.depth.mm); + double pamb_pressure = dive.depth_to_bar(psample.depth.mm ); + if (dc->divemode == PSCR) { + po2i = pscr_o2(pamb_pressure, dive.get_gasmix_at_time(*dc, psample.time)); + po2f = pscr_o2(amb_presure, dive.get_gasmix_at_time(*dc, sample.time)); + } else { + int o2 = active_o2(dive, dc, psample.time); // ... calculate po2 from depth and FiO2. + po2i = lrint(o2 * pamb_pressure); // (initial) po2 at start of segment + po2f = lrint(o2 * amb_presure); // (final) po2 at end of segment + } + po2 = (po2i + po2f) / 2; + } + return po2; +} + +/* Calculate OTU for a dive - this only takes the first divecomputer into account. + Implement the protocol in Erik Baker's document "Oxygen Toxicity Calculations". This code + implements a third-order continuous approximation of Baker's Eq. 2 and enables OTU + calculation for rebreathers. Baker obtained his information from: + Comroe Jr. JH et al. (1945) Oxygen toxicity. J. Am. Med. Assoc. 128,710-717 + Clark JM & CJ Lambertsen (1970) Pulmonary oxygen tolerance in man and derivation of pulmonary + oxygen tolerance curves. Inst. env. Med. Report 1-70, University of Pennsylvania, Philadelphia, USA. */ +static int calculate_otu(const struct dive &dive) +{ + double otu = 0.0; + const struct divecomputer *dc = &dive.dcs[0]; + for (auto [psample, sample]: pairwise_range(dc->samples)) { + int po2i, po2f; + double pm; + int t = (sample.time - psample.time).seconds; + // if there is sensor data use sensor[0] + if ((dc->divemode == CCR || dc->divemode == PSCR) && sample.o2sensor[0].mbar) { + po2i = psample.o2sensor[0].mbar; + po2f = sample.o2sensor[0].mbar; // ... use data from the first o2 sensor + } else { + if (sample.setpoint.mbar > 0) { + po2f = std::min((int) sample.setpoint.mbar, + dive.depth_to_mbar(sample.depth.mm)); + if (psample.setpoint.mbar > 0) + po2i = std::min((int) psample.setpoint.mbar, + dive.depth_to_mbar(psample.depth.mm)); + else + po2i = po2f; + } else { // For OC and rebreather without o2 sensor/setpoint + double amb_presure = dive.depth_to_bar(sample.depth.mm); + double pamb_pressure = dive.depth_to_bar(psample.depth.mm); + if (dc->divemode == PSCR) { + po2i = pscr_o2(pamb_pressure, dive.get_gasmix_at_time(*dc, psample.time)); + po2f = pscr_o2(amb_presure, dive.get_gasmix_at_time(*dc, sample.time)); + } else { + int o2 = active_o2(dive, dc, psample.time); // ... calculate po2 from depth and FiO2. + po2i = lrint(o2 * pamb_pressure); // (initial) po2 at start of segment + po2f = lrint(o2 * amb_presure); // (final) po2 at end of segment + } + } + } + if ((po2i > 500) || (po2f > 500)) { // If PO2 in segment is above 500 mbar then calculate otu + if (po2i <= 500) { // For descent segment with po2i <= 500 mbar .. + t = t * (po2f - 500) / (po2f - po2i); // .. only consider part with PO2 > 500 mbar + po2i = 501; // Mostly important for the dive planner with long segments + } else { + if (po2f <= 500){ + t = t * (po2i - 500) / (po2i - po2f); // For ascent segment with po2f <= 500 mbar .. + po2f = 501; // .. only consider part with PO2 > 500 mbar + } + } + pm = (po2f + po2i)/1000.0 - 1.0; + // This is a 3rd order continuous approximation of Baker's eq. 2, therefore Baker's eq. 1 is not used: + otu += t / 60.0 * pow(pm, 5.0/6.0) * (1.0 - 5.0 * (po2f - po2i) * (po2f - po2i) / 216000000.0 / (pm * pm)); + } + } + return lrint(otu); +} + +/* Calculate the CNS for a single dive - this only takes the first divecomputer into account. + The CNS contributions are summed for dive segments defined by samples. The maximum O2 exposure duration for each + segment is calculated based on the mean depth of the two samples (start & end) that define each segment. The CNS + contribution of each segment is found by dividing the time duration of the segment by its maximum exposure duration. + The contributions of all segments of the dive are summed to get the total CNS% value. This is a partial implementation + of the proposals in Erik Baker's document "Oxygen Toxicity Calculations" using fixed-depth calculations for the mean + po2 for each segment. Empirical testing showed that, for large changes in depth, the cns calculation for the mean po2 + value is extremely close, if not identical to the additive calculations for 0.1 bar increments in po2 from the start + to the end of the segment, assuming a constant rate of change in po2 (i.e. depth) with time. */ +static double calculate_cns_dive(const struct dive &dive) +{ + const struct divecomputer *dc = &dive.dcs[0]; + double cns = 0.0; + double rate; + /* Calculate the CNS for each sample in this dive and sum them */ + for (auto [psample, sample]: pairwise_range(dc->samples)) { + int t = (sample.time - psample.time).seconds; + int po2 = get_sample_o2(dive, dc, sample, psample); + /* Don't increase CNS when po2 below 500 matm */ + if (po2 <= 500) + continue; + + // This formula is the result of fitting two lines to the Log of the NOAA CNS table + rate = po2 <= 1500 ? exp(-11.7853 + 0.00193873 * po2) : exp(-23.6349 + 0.00980829 * po2); + cns += (double) t * rate * 100.0; + } + return cns; +} + +/* this only gets called if dive.maxcns == 0 which means we know that + * none of the divecomputers has tracked any CNS for us + * so we calculated it "by hand" */ +int dive_table::calculate_cns(struct dive &dive) const +{ + double cns = 0.0; + timestamp_t last_starttime, last_endtime = 0; + + /* shortcut */ + if (dive.cns) + return dive.cns; + + size_t divenr = get_idx(&dive); + int nr_dives = static_cast(size()); + int i = divenr != std::string::npos ? static_cast(divenr) + : nr_dives; +#if DECO_CALC_DEBUG & 2 + if (static_cast(i) < size()) + printf("\n\n*** CNS for dive #%d %d\n", i, ()[i]->number); + else + printf("\n\n*** CNS for dive #%d\n", i); +#endif + /* Look at next dive in dive list table and correct i when needed */ + while (i < nr_dives - 1) { + if ((*this)[i]->when > dive.when) + break; + i++; + } + /* Look at previous dive in dive list table and correct i when needed */ + while (i > 0) { + if ((*this)[i - 1]->when < dive.when) + break; + i--; + } +#if DECO_CALC_DEBUG & 2 + printf("Dive number corrected to #%d\n", i); +#endif + last_starttime = dive.when; + /* Walk backwards to check previous dives - how far do we need to go back? */ + while (i--) { + if (static_cast(i) == divenr && i > 0) + i--; +#if DECO_CALC_DEBUG & 2 + printf("Check if dive #%d %d has to be considered as prev dive: ", i, (*this)[i]->number); +#endif + const struct dive &pdive = *(*this)[i]; + /* we don't want to mix dives from different trips as we keep looking + * for how far back we need to go */ + if (dive.divetrip && pdive.divetrip != dive.divetrip) { +#if DECO_CALC_DEBUG & 2 + printf("No - other dive trip\n"); +#endif + continue; + } + if (pdive.when >= dive.when || pdive.endtime() + 12 * 60 * 60 < last_starttime) { +#if DECO_CALC_DEBUG & 2 + printf("No\n"); +#endif + break; + } + last_starttime = pdive.when; +#if DECO_CALC_DEBUG & 2 + printf("Yes\n"); +#endif + } + /* Walk forward and add dives and surface intervals to CNS */ + while (++i < nr_dives) { +#if DECO_CALC_DEBUG & 2 + printf("Check if dive #%d %d will be really added to CNS calc: ", i, (*this)[i]->number); +#endif + const struct dive &pdive = *(*this)[i]; + /* again skip dives from different trips */ + if (dive.divetrip && dive.divetrip != pdive.divetrip) { +#if DECO_CALC_DEBUG & 2 + printf("No - other dive trip\n"); +#endif + continue; + } + /* Don't add future dives */ + if (pdive.when >= dive.when) { +#if DECO_CALC_DEBUG & 2 + printf("No - future or same dive\n"); +#endif + break; + } + /* Don't add the copy of the dive itself */ + if (static_cast(i) == divenr) { +#if DECO_CALC_DEBUG & 2 + printf("No - copy of dive\n"); +#endif + continue; + } +#if DECO_CALC_DEBUG & 2 + printf("Yes\n"); +#endif + + /* CNS reduced with 90min halftime during surface interval */ + if (last_endtime) + cns /= pow(2, (pdive.when - last_endtime) / (90.0 * 60.0)); +#if DECO_CALC_DEBUG & 2 + printf("CNS after surface interval: %f\n", cns); +#endif + + cns += calculate_cns_dive(pdive); +#if DECO_CALC_DEBUG & 2 + printf("CNS after previous dive: %f\n", cns); +#endif + + last_starttime = pdive.when; + last_endtime = pdive.endtime(); + } + + /* CNS reduced with 90min halftime during surface interval */ + if (last_endtime) + cns /= pow(2, (dive.when - last_endtime) / (90.0 * 60.0)); +#if DECO_CALC_DEBUG & 2 + printf("CNS after last surface interval: %f\n", cns); +#endif + + cns += calculate_cns_dive(dive); +#if DECO_CALC_DEBUG & 2 + printf("CNS after dive: %f\n", cns); +#endif + + /* save calculated cns in dive struct */ + dive.cns = lrint(cns); + return dive.cns; +} + +/* + * Return air usage (in liters). + */ +static double calculate_airuse(const struct dive &dive) +{ + int airuse = 0; + + // SAC for a CCR dive does not make sense. + if (dive.dcs[0].divemode == CCR) + return 0.0; + + for (auto [i, cyl]: enumerated_range(dive.cylinders)) { + pressure_t start, end; + + start = cyl.start.mbar ? cyl.start : cyl.sample_start; + end = cyl.end.mbar ? cyl.end : cyl.sample_end; + if (!end.mbar || start.mbar <= end.mbar) { + // If a cylinder is used but we do not have info on amout of gas used + // better not pretend we know the total gas use. + // Eventually, logic should be fixed to compute average depth and total time + // for those segments where cylinders with known pressure drop are breathed from. + if (dive.is_cylinder_used(i)) + return 0.0; + else + continue; + } + + // TODO: implement subtraction for units.h types + airuse += cyl.gas_volume(start).mliter - cyl.gas_volume(end).mliter; + } + return airuse / 1000.0; +} + +/* this only uses the first divecomputer to calculate the SAC rate */ +static int calculate_sac(const struct dive &dive) +{ + const struct divecomputer *dc = &dive.dcs[0]; + double airuse, pressure, sac; + int duration, meandepth; + + airuse = calculate_airuse(dive); + if (!airuse) + return 0; + + duration = dc->duration.seconds; + if (!duration) + return 0; + + meandepth = dc->meandepth.mm; + if (!meandepth) + return 0; + + /* Mean pressure in ATM (SAC calculations are in atm*l/min) */ + pressure = dive.depth_to_atm(meandepth); + sac = airuse / pressure * 60 / duration; + + /* milliliters per minute.. */ + return lrint(sac * 1000); +} + +/* for now we do this based on the first divecomputer */ +static void add_dive_to_deco(struct deco_state *ds, const struct dive &dive, bool in_planner) +{ + const struct divecomputer *dc = &dive.dcs[0]; + + gasmix_loop loop(dive, dive.dcs[0]); + divemode_loop loop_d(dive.dcs[0]); + for (auto [psample, sample]: pairwise_range(dc->samples)) { + int t0 = psample.time.seconds; + int t1 = sample.time.seconds; + int j; + + for (j = t0; j < t1; j++) { + int depth = interpolate(psample.depth.mm, sample.depth.mm, j - t0, t1 - t0); + auto gasmix = loop.at(j).first; + add_segment(ds, dive.depth_to_bar(depth), gasmix, 1, sample.setpoint.mbar, + loop_d.at(j), dive.sac, + in_planner); + } + } +} + +/* take into account previous dives until there is a 48h gap between dives */ +/* return last surface time before this dive or dummy value of 48h */ +/* return negative surface time if dives are overlapping */ +/* The place you call this function is likely the place where you want + * to create the deco_state */ +int dive_table::init_decompression(struct deco_state *ds, const struct dive *dive, bool in_planner) const +{ + int surface_time = 48 * 60 * 60; + timestamp_t last_endtime = 0, last_starttime = 0; + bool deco_init = false; + double surface_pressure; + + if (!dive) + return false; + + int nr_dives = static_cast(size()); + size_t divenr = get_idx(dive); + int i = divenr != std::string::npos ? static_cast(divenr) + : nr_dives; +#if DECO_CALC_DEBUG & 2 + if (i < dive_table.nr) + printf("\n\n*** Init deco for dive #%d %d\n", i, (*this)[i]->number); + else + printf("\n\n*** Init deco for dive #%d\n", i); +#endif + /* Look at next dive in dive list table and correct i when needed */ + while (i + 1 < nr_dives) { + if ((*this)[i]->when > dive->when) + break; + i++; + } + /* Look at previous dive in dive list table and correct i when needed */ + while (i > 0) { + if ((*this)[i - 1]->when < dive->when) + break; + i--; + } +#if DECO_CALC_DEBUG & 2 + printf("Dive number corrected to #%d\n", i); +#endif + last_starttime = dive->when; + /* Walk backwards to check previous dives - how far do we need to go back? */ + while (i--) { + if (static_cast(i) == divenr && i > 0) + i--; +#if DECO_CALC_DEBUG & 2 + printf("Check if dive #%d %d has to be considered as prev dive: ", i, (*this)[i]->number); +#endif + const struct dive &pdive = *(*this)[i]; + /* we don't want to mix dives from different trips as we keep looking + * for how far back we need to go */ + if (dive->divetrip && pdive.divetrip != dive->divetrip) { +#if DECO_CALC_DEBUG & 2 + printf("No - other dive trip\n"); +#endif + continue; + } + if (pdive.when >= dive->when || pdive.endtime() + 48 * 60 * 60 < last_starttime) { +#if DECO_CALC_DEBUG & 2 + printf("No\n"); +#endif + break; + } + last_starttime = pdive.when; +#if DECO_CALC_DEBUG & 2 + printf("Yes\n"); +#endif + } + /* Walk forward an add dives and surface intervals to deco */ + while (++i < nr_dives) { +#if DECO_CALC_DEBUG & 2 + printf("Check if dive #%d %d will be really added to deco calc: ", i, (*this)[i]->number); +#endif + const struct dive &pdive = *(*this)[i]; + /* again skip dives from different trips */ + if (dive->divetrip && dive->divetrip != pdive.divetrip) { +#if DECO_CALC_DEBUG & 2 + printf("No - other dive trip\n"); +#endif + continue; + } + /* Don't add future dives */ + if (pdive.when >= dive->when) { +#if DECO_CALC_DEBUG & 2 + printf("No - future or same dive\n"); +#endif + break; + } + /* Don't add the copy of the dive itself */ + if (static_cast(i) == divenr) { +#if DECO_CALC_DEBUG & 2 + printf("No - copy of dive\n"); +#endif + continue; + } +#if DECO_CALC_DEBUG & 2 + printf("Yes\n"); +#endif + + surface_pressure = pdive.get_surface_pressure().mbar / 1000.0; + /* Is it the first dive we add? */ + if (!deco_init) { +#if DECO_CALC_DEBUG & 2 + printf("Init deco\n"); +#endif + clear_deco(ds, surface_pressure, in_planner); + deco_init = true; +#if DECO_CALC_DEBUG & 2 + printf("Tissues after init:\n"); + dump_tissues(ds); +#endif + } else { + surface_time = pdive.when - last_endtime; + if (surface_time < 0) { +#if DECO_CALC_DEBUG & 2 + printf("Exit because surface intervall is %d\n", surface_time); +#endif + return surface_time; + } + add_segment(ds, surface_pressure, gasmix_air, surface_time, 0, OC, prefs.decosac, in_planner); +#if DECO_CALC_DEBUG & 2 + printf("Tissues after surface intervall of %d:%02u:\n", FRACTION_TUPLE(surface_time, 60)); + dump_tissues(ds); +#endif + } + + add_dive_to_deco(ds, pdive, in_planner); + + last_starttime = pdive.when; + last_endtime = pdive.endtime(); + clear_vpmb_state(ds); +#if DECO_CALC_DEBUG & 2 + printf("Tissues after added dive #%d:\n", pdive.number); + dump_tissues(ds); +#endif + } + + surface_pressure = dive->get_surface_pressure().mbar / 1000.0; + /* We don't have had a previous dive at all? */ + if (!deco_init) { +#if DECO_CALC_DEBUG & 2 + printf("Init deco\n"); +#endif + clear_deco(ds, surface_pressure, in_planner); +#if DECO_CALC_DEBUG & 2 + printf("Tissues after no previous dive, surface time set to 48h:\n"); + dump_tissues(ds); +#endif + } else { + surface_time = dive->when - last_endtime; + if (surface_time < 0) { +#if DECO_CALC_DEBUG & 2 + printf("Exit because surface intervall is %d\n", surface_time); +#endif + return surface_time; + } + add_segment(ds, surface_pressure, gasmix_air, surface_time, 0, OC, prefs.decosac, in_planner); +#if DECO_CALC_DEBUG & 2 + printf("Tissues after surface intervall of %d:%02u:\n", FRACTION_TUPLE(surface_time, 60)); + dump_tissues(ds); +#endif + } + + // I do not dare to remove this call. We don't need the result but it might have side effects. Bummer. + tissue_tolerance_calc(ds, dive, surface_pressure, in_planner); + return surface_time; +} + +void dive_table::update_cylinder_related_info(struct dive &dive) const +{ + dive.sac = calculate_sac(dive); + dive.otu = calculate_otu(dive); + if (dive.maxcns == 0) + dive.maxcns = calculate_cns(dive); +} + +/* Compare list of dive computers by model name */ +static int comp_dc(const struct dive *d1, const struct dive *d2) +{ + auto it1 = d1->dcs.begin(); + auto it2 = d2->dcs.begin(); + while (it1 != d1->dcs.end() || it2 != d2->dcs.end()) { + if (it1 == d1->dcs.end()) + return -1; + if (it2 == d2->dcs.end()) + return 1; + int cmp = it1->model.compare(it2->model); + if (cmp != 0) + return cmp; + ++it1; + ++it2; + } + return 0; +} + +/* This function defines the sort ordering of dives. The core + * and the UI models should use the same sort function, which + * should be stable. This is not crucial at the moment, as the + * indices in core and UI are independent, but ultimately we + * probably want to unify the models. + * After editing a key used in this sort-function, the order of + * the dives must be re-astablished. + * Currently, this does a lexicographic sort on the + * (start-time, trip-time, number, id) tuple. + * trip-time is defined such that dives that do not belong to + * a trip are sorted *after* dives that do. Thus, in the default + * chronologically-descending sort order, they are shown *before*. + * "id" is a stable, strictly increasing unique number, which + * is generated when a dive is added to the system. + * We might also consider sorting by end-time and other criteria, + * but see the caveat above (editing means reordering of the dives). + */ +int comp_dives(const struct dive &a, const struct dive &b) +{ + int cmp; + if (&a == &b) + return 0; /* reflexivity */ + if (a.when < b.when) + return -1; + if (a.when > b.when) + return 1; + if (a.divetrip != b.divetrip) { + if (!b.divetrip) + return -1; + if (!a.divetrip) + return 1; + if (a.divetrip->date() < b.divetrip->date()) + return -1; + if (a.divetrip->date() > b.divetrip->date()) + return 1; + } + if (a.number < b.number) + return -1; + if (a.number > b.number) + return 1; + if ((cmp = comp_dc(&a, &b)) != 0) + return cmp; + if (a.id < b.id) + return -1; + if (a.id > b.id) + return 1; + return &a < &b ? -1 : 1; /* give up. */ +} + +int comp_dives_ptr(const struct dive *a, const struct dive *b) +{ + return comp_dives(*a, *b); +} + +/* This removes a dive from the global dive table but doesn't free the + * resources associated with the dive. The caller must removed the dive + * from the trip-list. Returns a pointer to the unregistered dive. + * The unregistered dive has the selection- and hidden-flags cleared. + * TODO: This makes me unhappy, as it touches global state, viz. + * selection and fulltext. */ +std::unique_ptr dive_table::unregister_dive(int idx) +{ + if (idx < 0 || static_cast(idx) >= size()) + return {}; /* this should never happen */ + + auto dive = pull_at(idx); + + /* When removing a dive from the global dive table, + * we also have to unregister its fulltext cache. */ + fulltext_unregister(dive.get()); + if (dive->selected) + amount_selected--; + dive->selected = false; + return dive; +} + +/* Add a dive to the global dive table. + * Index it in the fulltext cache and make sure that it is written + * in git_save(). + * TODO: This makes me unhappy, as it touches global state, viz. + * selection and fulltext. */ +struct dive *dive_table::register_dive(std::unique_ptr d) +{ + // When we add dives, we start in hidden-by-filter status. Once all + // dives have been added, their status will be updated. + d->hidden_by_filter = true; + + fulltext_register(d.get()); // Register the dive's fulltext cache + d->invalidate_cache(); // Ensure that dive is written in git_save() + auto [res, idx] = put(std::move(d)); + + return res; +} + +/* return the number a dive gets when inserted at the given index. + * this function is supposed to be called *before* a dive was added. + * this returns: + * - 1 for an empty log + * - last_nr+1 for addition at end of log (if last dive had a number) + * - 0 for all other cases + */ +int dive_table::get_dive_nr_at_idx(int idx) const +{ + if (static_cast(idx) < size()) + return 0; + auto it = std::find_if(rbegin(), rend(), [](auto &d) { return !d->invalid; }); + if (it == rend()) + return 1; + return (*it)->number ? (*it)->number + 1 : 0; +} + +/* lookup of trip in main trip_table based on its id */ +dive *dive_table::get_by_uniq_id(int id) const +{ + auto it = std::find_if(begin(), end(), [id](auto &d) { return d->id == id; }); +#ifdef DEBUG + if (it == end()) { + report_info("Invalid id %x passed to get_dive_by_diveid, try to fix the code", id); + exit(1); + } +#endif + return it != end() ? it->get() : nullptr; +} + +void clear_dive_file_data() +{ + fulltext_unregister_all(); + select_single_dive(NULL); // This is propagated up to the UI and clears all the information. + + current_dive = NULL; + divelog.clear(); + + clear_event_types(); + + clear_min_datafile_version(); + clear_git_id(); + + reset_tank_info_table(tank_info_table); + + /* Inform frontend of reset data. This should reset all the models. */ + emit_reset_signal(); +} + +bool dive_less_than(const struct dive &a, const struct dive &b) +{ + return comp_dives(a, b) < 0; +} + +bool dive_less_than_ptr(const struct dive *a, const struct dive *b) +{ + return comp_dives(*a, *b) < 0; +} + +/* When comparing a dive to a trip, use the first dive of the trip. */ +static int comp_dive_to_trip(struct dive *a, struct dive_trip *b) +{ + /* This should never happen, nevertheless don't crash on trips + * with no (or worse a negative number of) dives. */ + if (!b || b->dives.empty()) + return -1; + return comp_dives(*a, *b->dives[0]); +} + +static int comp_dive_or_trip(struct dive_or_trip a, struct dive_or_trip b) +{ + /* we should only be called with both a and b having exactly one of + * dive or trip not NULL. But in an abundance of caution, make sure + * we still give a consistent answer even when called with invalid + * arguments, as otherwise we might be hunting down crashes at a later + * time... + */ + if (!a.dive && !a.trip && !b.dive && !b.trip) + return 0; + if (!a.dive && !a.trip) + return -1; + if (!b.dive && !b.trip) + return 1; + if (a.dive && b.dive) + return comp_dives(*a.dive, *b.dive); + if (a.trip && b.trip) + return comp_trips(*a.trip, *b.trip); + if (a.dive) + return comp_dive_to_trip(a.dive, b.trip); + else + return -comp_dive_to_trip(b.dive, a.trip); +} + +bool dive_or_trip_less_than(struct dive_or_trip a, struct dive_or_trip b) +{ + return comp_dive_or_trip(a, b) < 0; +} + +/* + * Calculate surface interval for dive starting at "when". Currently, we + * might display dives which are not yet in the divelist, therefore the + * input parameter is a timestamp. + * If the given dive starts during a different dive, the surface interval + * is 0. If we can't determine a surface interval (first dive), <0 is + * returned. This does *not* consider pathological cases such as dives + * that happened inside other dives. The interval will always be calculated + * with respect to the dive that started previously. + */ +timestamp_t dive_table::get_surface_interval(timestamp_t when) const +{ + /* find previous dive. might want to use a binary search. */ + auto it = std::find_if(rbegin(), rend(), + [when] (auto &d) { return d->when < when; }); + if (it == rend()) + return -1; + + timestamp_t prev_end = (*it)->endtime(); + if (prev_end > when) + return 0; + return when - prev_end; +} + +/* Find visible dive close to given date. First search towards older, + * then newer dives. */ +struct dive *dive_table::find_next_visible_dive(timestamp_t when) +{ + /* we might want to use binary search here */ + auto it = std::find_if(begin(), end(), + [when] (auto &d) { return d->when <= when; }); + + for (auto it2 = it; it2 != begin(); --it2) { + if (!(*std::prev(it2))->hidden_by_filter) + return it2->get(); + } + + for (auto it2 = it; it2 != end(); ++it2) { + if (!(*it2)->hidden_by_filter) + return it2->get(); + } + + return nullptr; +} + +bool dive_table::has_dive(unsigned int deviceid, unsigned int diveid) const +{ + return std::any_of(begin(), end(), [deviceid,diveid] (auto &d) { + return std::any_of(d->dcs.begin(), d->dcs.end(), [deviceid,diveid] (auto &dc) { + return dc.deviceid == deviceid && dc.diveid == diveid; + }); + }); +} + +/* + * This splits the dive src by dive computer. The first output dive has all + * dive computers except num, the second only dive computer num. + * The dives will not be associated with a trip. + * On error, both output parameters are set to NULL. + */ +std::array, 2> dive_table::split_divecomputer(const struct dive &src, int num) const +{ + if (num < 0 || src.dcs.size() < 2 || static_cast(num) >= src.dcs.size()) + return {}; + + // Copy the dive with full divecomputer list + auto out1 = std::make_unique(src); + + // Remove all DCs, stash them and copy the dive again. + // Then, we have to dives without DCs and a list of DCs. + std::vector dcs; + std::swap(out1->dcs, dcs); + auto out2 = std::make_unique(*out1); + + // Give the dives new unique ids and remove them from the trip. + out1->id = dive_getUniqID(); + out2->id = dive_getUniqID(); + out1->divetrip = out2->divetrip = NULL; + + // Now copy the divecomputers + out1->dcs.reserve(src.dcs.size() - 1); + for (auto [idx, dc]: enumerated_range(dcs)) { + auto &dcs = idx == num ? out2->dcs : out1->dcs; + dcs.push_back(std::move(dc)); + } + + // Recalculate gas data, etc. + fixup_dive(*out1); + fixup_dive(*out2); + + return { std::move(out1), std::move(out2) }; +} + +/* + * Split a dive that has a surface interval from samples 'a' to 'b' + * into two dives, but don't add them to the log yet. + * Returns the nr of the old dive or <0 on failure. + * Moreover, on failure both output dives are set to NULL. + * On success, the newly allocated dives are returned in out1 and out2. + */ +std::array, 2> dive_table::split_dive_at(const struct dive &dive, int a, int b) const +{ + size_t nr = get_idx(&dive); + + /* if we can't find the dive in the dive list, don't bother */ + if (nr == std::string::npos) + return {}; + + bool is_last_dive = size() == nr + 1; + + /* Splitting should leave at least 3 samples per dive */ + if (a < 3 || static_cast(b + 4) > dive.dcs[0].samples.size()) + return {}; + + /* We're not trying to be efficient here.. */ + auto d1 = std::make_unique(dive); + auto d2 = std::make_unique(dive); + d1->id = dive_getUniqID(); + d2->id = dive_getUniqID(); + d1->divetrip = d2->divetrip = nullptr; + + /* now unselect the first first segment so we don't keep all + * dives selected by mistake. But do keep the second one selected + * so the algorithm keeps splitting the dive further */ + d1->selected = false; + + struct divecomputer &dc1 = d1->dcs[0]; + struct divecomputer &dc2 = d2->dcs[0]; + /* + * Cut off the samples of d1 at the beginning + * of the interval. + */ + dc1.samples.resize(a); + + /* And get rid of the 'b' first samples of d2 */ + dc2.samples.erase(dc2.samples.begin(), dc2.samples.begin() + b); + + /* Now the secondary dive computers */ + int32_t t = dc2.samples[0].time.seconds; + for (auto it1 = d1->dcs.begin() + 1; it1 != d1->dcs.end(); ++it1) { + auto it = std::find_if(it1->samples.begin(), it1->samples.end(), + [t](auto &sample) { return sample.time.seconds >= t; }); + it1->samples.erase(it, it1->samples.end()); + } + for (auto it2 = d2->dcs.begin() + 1; it2 != d2->dcs.end(); ++it2) { + auto it = std::find_if(it2->samples.begin(), it2->samples.end(), + [t](auto &sample) { return sample.time.seconds >= t; }); + it2->samples.erase(it2->samples.begin(), it); + } + + /* + * This is where we cut off events from d1, + * and shift everything in d2 + */ + d2->when += t; + auto it1 = d1->dcs.begin(); + auto it2 = d2->dcs.begin(); + while (it1 != d1->dcs.end() && it2 != d2->dcs.end()) { + it2->when += t; + for (auto &sample: it2->samples) + sample.time.seconds -= t; + + /* Remove the events past 't' from d1 */ + auto it = std::lower_bound(it1->events.begin(), it1->events.end(), t, + [] (struct event &ev, int t) + { return ev.time.seconds < t; }); + it1->events.erase(it, it1->events.end()); + + /* Remove the events before 't' from d2, and shift the rest */ + it = std::lower_bound(it2->events.begin(), it2->events.end(), t, + [] (struct event &ev, int t) + { return ev.time.seconds < t; }); + it2->events.erase(it2->events.begin(), it); + for (auto &ev: it2->events) + ev.time.seconds -= t; + + ++it1; + ++it2; + } + + force_fixup_dive(*d1); + force_fixup_dive(*d2); + + /* + * Was the dive numbered? If it was the last dive, then we'll + * increment the dive number for the tail part that we split off. + * Otherwise the tail is unnumbered. + */ + if (d2->number && is_last_dive) + d2->number++; + else + d2->number = 0; + + return { std::move(d1), std::move(d2) }; +} + +/* in freedive mode we split for as little as 10 seconds on the surface, + * otherwise we use a minute */ +static bool should_split(const struct divecomputer *dc, int t1, int t2) +{ + int threshold = dc->divemode == FREEDIVE ? 10 : 60; + + return t2 - t1 >= threshold; +} + +/* + * Try to split a dive into multiple dives at a surface interval point. + * + * NOTE! We will split when there is at least one surface event that has + * non-surface events on both sides. + * + * The surface interval points are determined using the first dive computer. + * + * In other words, this is a (simplified) reversal of the dive merging. + */ +std::array, 2> dive_table::split_dive(const struct dive &dive) const +{ + const struct divecomputer *dc = &dive.dcs[0]; + bool at_surface = true; + if (dc->samples.empty()) + return {}; + auto surface_start = dc->samples.begin(); + for (auto it = dc->samples.begin() + 1; it != dc->samples.end(); ++it) { + bool surface_sample = it->depth.mm < SURFACE_THRESHOLD; + + /* + * We care about the transition from and to depth 0, + * not about the depth staying similar. + */ + if (at_surface == surface_sample) + continue; + at_surface = surface_sample; + + // Did it become surface after having been non-surface? We found the start + if (at_surface) { + surface_start = it; + continue; + } + + // Going down again? We want at least a minute from + // the surface start. + if (surface_start == dc->samples.begin()) + continue; + if (!should_split(dc, surface_start->time.seconds, std::prev(it)->time.seconds)) + continue; + + return split_dive_at(dive, surface_start - dc->samples.begin(), it - dc->samples.begin() - 1); + } + return {}; +} + +std::array, 2> dive_table::split_dive_at_time(const struct dive &dive, duration_t time) const +{ + auto it = std::find_if(dive.dcs[0].samples.begin(), dive.dcs[0].samples.end(), + [time](auto &sample) { return sample.time.seconds >= time.seconds; }); + if (it == dive.dcs[0].samples.end()) + return {}; + size_t idx = it - dive.dcs[0].samples.begin(); + if (idx < 1) + return {}; + return split_dive_at(dive, static_cast(idx), static_cast(idx - 1)); +} + +/* + * Pick a trip for a dive + */ +static struct dive_trip *get_preferred_trip(const struct dive &a, const struct dive &b) +{ + dive_trip *atrip, *btrip; + + /* If only one dive has a trip, choose that */ + atrip = a.divetrip; + btrip = b.divetrip; + if (!atrip) + return btrip; + if (!btrip) + return atrip; + + /* Both dives have a trip - prefer the non-autogenerated one */ + if (atrip->autogen && !btrip->autogen) + return btrip; + if (!atrip->autogen && btrip->autogen) + return atrip; + + /* Otherwise, look at the trip data and pick the "better" one */ + if (atrip->location.empty()) + return btrip; + if (btrip->location.empty()) + return atrip; + if (atrip->notes.empty()) + return btrip; + if (btrip->notes.empty()) + return atrip; + + /* + * Ok, so both have location and notes. + * Pick the earlier one. + */ + if (a.when < b.when) + return atrip; + return btrip; +} + +/* + * Merging two dives can be subtle, because there's two different ways + * of merging: + * + * (a) two distinctly _different_ dives that have the same dive computer + * are merged into one longer dive, because the user asked for it + * in the divelist. + * + * Because this case is with the same dive computer, we *know* the + * two must have a different start time, and "offset" is the relative + * time difference between the two. + * + * (b) two different dive computers that we might want to merge into + * one single dive with multiple dive computers. + * + * This is the "try_to_merge()" case, which will have offset == 0, + * even if the dive times might be different. + * + * If new dives are merged into the dive table, dive a is supposed to + * be the old dive and dive b is supposed to be the newly imported + * dive. If the flag "prefer_downloaded" is set, data of the latter + * will take priority over the former. + * + * The dive site is set, but the caller still has to add it to the + * divelog's dive site manually. + * + */ +std::unique_ptr dive_table::merge_two_dives(const struct dive &a_in, const struct dive &b_in, int offset, bool prefer_downloaded) const +{ + const dive *a = &a_in; + const dive *b = &b_in; + if (is_dc_planner(&a->dcs[0])) + std::swap(a, b); + + auto d = dive::create_merged_dive(*a, *b, offset, prefer_downloaded); + + /* The CNS values will be recalculated from the sample in fixup_dive() */ + d->cns = d->maxcns = 0; + + /* Unselect the new dive if the original dive was selected. */ + d->selected = false; + + /* we take the first dive site, unless it's empty */ + d->dive_site = a->dive_site && !a->dive_site->is_empty() ? a->dive_site : b->dive_site; + + fixup_dive(*d); + + return d; +} + +merge_result dive_table::merge_dives(const std::vector &dives) const +{ + merge_result res; + + // We don't support merging of less than two dives, but + // let's try to treat this gracefully. + if (dives.empty()) + return res; + if (dives.size() == 1) { + res.dive = std::make_unique(*dives[0]); + return res; + } + + auto d = merge_two_dives(*dives[0], *dives[1], dives[1]->when - dives[0]->when, false); + d->divetrip = get_preferred_trip(*dives[0], *dives[1]); + + for (size_t i = 2; i < dives.size(); ++i) { + auto d2 = divelog.dives.merge_two_dives(*d, *dives[i], dives[i]->when - d->when, false); + d2->divetrip = get_preferred_trip(*d, *dives[i]); + d = std::move(d2); + } + + // If the new dive site has no gps location, try to find the first dive with a gps location + if (d->dive_site && !d->dive_site->has_gps_location()) { + auto it = std::find_if(dives.begin(), dives.end(), [](const dive *d) + { return d->dive_site && d->dive_site->has_gps_location(); } ); + if (it != dives.end()) + res.set_location = (*it)->dive_site->location; + } + res.dive = std::move(d); + return res; +} + +/* + * This could do a lot more merging. Right now it really only + * merges almost exact duplicates - something that happens easily + * with overlapping dive downloads. + * + * If new dives are merged into the dive table, dive a is supposed to + * be the old dive and dive b is supposed to be the newly imported + * dive. If the flag "prefer_downloaded" is set, data of the latter + * will take priority over the former. + * + * Attn: The dive_site parameter of the dive will be set, but the caller + * still has to register the dive in the dive site! + */ +struct std::unique_ptr dive_table::try_to_merge(const struct dive &a, const struct dive &b, bool prefer_downloaded) const +{ + if (!a.likely_same(b)) + return {}; + + return merge_two_dives(a, b, 0, prefer_downloaded); +} + +/* Clone a dive and delete given dive computer */ +std::unique_ptr dive_table::clone_delete_divecomputer(const struct dive &d, int dc_number) +{ + /* copy the dive */ + auto res = std::make_unique(d); + + /* make a new unique id, since we still can't handle two equal ids */ + res->id = dive_getUniqID(); + + if (res->dcs.size() <= 1) + return res; + + if (dc_number < 0 || static_cast(dc_number) >= res->dcs.size()) + return res; + + res->dcs.erase(res->dcs.begin() + dc_number); + + force_fixup_dive(*res); + + return res; +} diff --git a/core/divelist.h b/core/divelist.h index 54b7d5444..289b9331d 100644 --- a/core/divelist.h +++ b/core/divelist.h @@ -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 +#include 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 dive; + std::optional 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 *get_by_uniq_id(int id) const; + void record_dive(std::unique_ptr d); // call fixup_dive() before adding dive to table. + struct dive *register_dive(std::unique_ptr d); + std::unique_ptr unregister_dive(int idx); + std::unique_ptr 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, 2> split_divecomputer(const struct dive &src, int num) const; + std::array, 2> split_dive(const struct dive &dive) const; + std::array, 2> split_dive_at_time(const struct dive &dive, duration_t time) const; + merge_result merge_dives(const std::vector &dives) const; + std::unique_ptr 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 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, 2> split_dive_at(const struct dive &dive, int a, int b) const; + std::unique_ptr 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 diff --git a/core/divelog.cpp b/core/divelog.cpp index f5c42b88c..fd7c2c7e8 100644 --- a/core/divelog.cpp +++ b/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(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 &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 &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 &dives_from, struct dive_table &delete_from, + const std::vector &dives_to, + bool prefer_imported, struct dive_trip *trip, + /* output parameters: */ + struct dive_table &dives_to_add, struct std::vector &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 &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_table_to_non_owning(const dive_table &dives) +{ + std::vector 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(); } diff --git a/core/divelog.h b/core/divelog.h index e1eb01770..545afe672 100644 --- a/core/divelog.h +++ b/core/divelog.h @@ -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 +#include + +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 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 &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 dives_to_remove; + trip_table trips_to_add; + dive_site_table sites_to_add; + std::vector 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 diff --git a/core/divelogexportlogic.cpp b/core/divelogexportlogic.cpp index 06690e4d2..f0324af15 100644 --- a/core/divelogexportlogic.cpp +++ b/core/divelogexportlogic.cpp @@ -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); } diff --git a/core/divemode.h b/core/divemode.h index 3ea012fe6..af4a0ca22 100644 --- a/core/divemode.h +++ b/core/divemode.h @@ -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 diff --git a/core/divesite-helper.cpp b/core/divesite-helper.cpp deleted file mode 100644 index bed0297ce..000000000 --- a/core/divesite-helper.cpp +++ /dev/null @@ -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("") + gettextFromC::tr("No dive site layout categories set in preferences!") + - QString(""); - return locationTag; - } - else if (!prefs_set) - return locationTag; - - if (for_maintab) - locationTag = QString("(") + gettextFromC::tr("Tags") + QString(": "); - else - locationTag = QString(""); - 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 += ")"; - else - locationTag += ""; - return locationTag; -} diff --git a/core/divesite.c b/core/divesite.c deleted file mode 100644 index ed83514ab..000000000 --- a/core/divesite.c +++ /dev/null @@ -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 - -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; -} diff --git a/core/divesite.cpp b/core/divesite.cpp new file mode 100644 index 000000000..296d93972 --- /dev/null +++ b/core/divesite.cpp @@ -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 + +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 +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 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(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(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(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; +} diff --git a/core/divesite.h b/core/divesite.h index 29002587f..6bbda62f0 100644 --- a/core/divesite.h +++ b/core/divesite.h @@ -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 -#ifdef __cplusplus -#include -#include -extern "C" { -#else -#include -#endif - struct dive_site { - uint32_t uuid; - char *name; - struct dive_table dives; + uint32_t uuid = 0; + std::string name; + std::vector 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 Q_DECLARE_METATYPE(dive_site *); -#endif - #endif // DIVESITE_H diff --git a/core/divesitehelpers.cpp b/core/divesitehelpers.cpp index aee24d9eb..e4fd4af4e 100644 --- a/core/divesitehelpers.cpp +++ b/core/divesitehelpers.cpp @@ -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"); diff --git a/core/divesitetable.h b/core/divesitetable.h new file mode 100644 index 000000000..e4bbc9bb6 --- /dev/null +++ b/core/divesitetable.h @@ -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 { +public: + put_result register_site(std::unique_ptr 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 diff --git a/core/downloadfromdcthread.cpp b/core/downloadfromdcthread.cpp index 59404b826..671b6cbb0 100644 --- a/core/downloadfromdcthread.cpp +++ b/core/downloadfromdcthread.cpp @@ -1,8 +1,10 @@ #include "downloadfromdcthread.h" #include "core/errorhelper.h" +#include "core/format.h" #include "core/libdivecomputer.h" #include "core/qthelper.h" #include "core/range.h" +#include "core/uemis.h" #include "core/settings/qPrefDiveComputer.h" #include "core/divelist.h" #if defined(Q_OS_ANDROID) @@ -15,16 +17,6 @@ static QHash mobileProductList; // BT, BLE or FTDI support QMap descriptorLookup; ConnectionListModel connectionListModel; -static QString str_error(const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - const QString str = QString::vasprintf(fmt, args); - va_end(args); - - return str; -} - static void updateRememberedDCs() { QString current = qPrefDiveComputer::vendor() + " - " + qPrefDiveComputer::product() + " - " + qPrefDiveComputer::device(); @@ -89,7 +81,7 @@ void DownloadThread::run() auto internalData = m_data->internalData(); internalData->descriptor = descriptorLookup[m_data->vendor().toLower() + m_data->product().toLower()]; internalData->log = &log; - internalData->btname = strdup(m_data->devBluetoothName().toUtf8()); + internalData->btname = m_data->devBluetoothName().toStdString(); if (!internalData->descriptor) { report_info("No download possible when DC type is unknown"); return; @@ -105,27 +97,28 @@ void DownloadThread::run() report_info("Starting download from %s", qPrintable(getTransportString(transports))); report_info("downloading %s dives", internalData->force_download ? "all" : "only new"); - clear_divelog(&log); + log.clear(); Q_ASSERT(internalData->log != nullptr); - const char *errorText; + std::string errorText; import_thread_cancelled = false; error.clear(); - if (!strcmp(internalData->vendor, "Uemis")) + if (internalData->vendor == "Uemis") errorText = do_uemis_import(internalData); else errorText = do_libdivecomputer_import(internalData); - if (errorText) { - error = str_error(errorText, internalData->devname, internalData->vendor, internalData->product); - report_info("Finishing download thread: %s", qPrintable(error)); + if (!errorText.empty()) { + error = format_string_std(errorText.c_str(), internalData->devname.c_str(), + internalData->vendor.c_str(), internalData->product.c_str()); + report_info("Finishing download thread: %s", error.c_str()); } else { - if (!log.dives->nr) - error = tr("No new dives downloaded from dive computer"); - report_info("Finishing download thread: %d dives downloaded", log.dives->nr); + if (log.dives.empty()) + error = tr("No new dives downloaded from dive computer").toStdString(); + report_info("Finishing download thread: %d dives downloaded", static_cast(log.dives.size())); } - qPrefDiveComputer::set_vendor(internalData->vendor); - qPrefDiveComputer::set_product(internalData->product); - qPrefDiveComputer::set_device(internalData->devname); + qPrefDiveComputer::set_vendor(internalData->vendor.c_str()); + qPrefDiveComputer::set_product(internalData->product.c_str()); + qPrefDiveComputer::set_device(internalData->devname.c_str()); qPrefDiveComputer::set_device_name(m_data->devBluetoothName()); updateRememberedDCs(); @@ -209,7 +202,6 @@ void show_computer_list() DCDeviceData::DCDeviceData() { - memset(&data, 0, sizeof(data)); data.log = nullptr; data.diveid = 0; #if defined(BT_SUPPORT) @@ -241,9 +233,8 @@ QStringList DCDeviceData::getProductListFromVendor(const QString &vendor) return productList[vendor]; } -int DCDeviceData::getMatchingAddress(const QString &vendor, const QString &product) +int DCDeviceData::getMatchingAddress(const QString &, const QString &product) { - Q_UNUSED(vendor) return connectionListModel.indexOf(product); } @@ -254,17 +245,17 @@ DCDeviceData *DownloadThread::data() QString DCDeviceData::vendor() const { - return data.vendor; + return QString::fromStdString(data.vendor); } QString DCDeviceData::product() const { - return data.product; + return QString::fromStdString(data.product); } QString DCDeviceData::devName() const { - return data.devname; + return QString::fromStdString(data.devname); } QString DCDeviceData::devBluetoothName() const @@ -299,12 +290,12 @@ bool DCDeviceData::syncTime() const void DCDeviceData::setVendor(const QString &vendor) { - data.vendor = copy_qstring(vendor); + data.vendor = vendor.toStdString(); } void DCDeviceData::setProduct(const QString &product) { - data.product = copy_qstring(product); + data.product = product.toStdString(); } void DCDeviceData::setDevName(const QString &devName) @@ -321,11 +312,11 @@ void DCDeviceData::setDevName(const QString &devName) QString back = devName.mid(idx1 + 1, idx2 - idx1 - 1); QString newDevName = back.indexOf(':') >= 0 ? back : front; qWarning() << "Found invalid bluetooth device" << devName << "corrected to" << newDevName << "."; - data.devname = copy_qstring(newDevName); + data.devname = newDevName.toStdString(); return; } } - data.devname = copy_qstring(devName); + data.devname = devName.toStdString(); } #if defined(Q_OS_ANDROID) diff --git a/core/downloadfromdcthread.h b/core/downloadfromdcthread.h index 8ff3450d6..1c6a5b2b5 100644 --- a/core/downloadfromdcthread.h +++ b/core/downloadfromdcthread.h @@ -74,7 +74,7 @@ public: void run() override; DCDeviceData *data(); - QString error; + std::string error; struct divelog log; private: diff --git a/core/equipment.c b/core/equipment.c deleted file mode 100644 index 6f1904adf..000000000 --- a/core/equipment.c +++ /dev/null @@ -1,591 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifdef __clang__ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#endif - -/* equipment.c */ -#include -#include -#include -#include -#include -#include -#include "equipment.h" -#include "gettext.h" -#include "dive.h" -#include "divelist.h" -#include "divelog.h" -#include "errorhelper.h" -#include "pref.h" -#include "subsurface-string.h" -#include "table.h" - -/* Warning: this has strange semantics for C-code! Not the weightsystem object - * is freed, but the data it references. The object itself is passed in by value. - * This is due to the fact how the table macros work. - */ -void free_weightsystem(weightsystem_t ws) -{ - free((void *)ws.description); - ws.description = NULL; -} - -void free_cylinder(cylinder_t c) -{ - free((void *)c.type.description); - c.type.description = NULL; -} - -void copy_weights(const struct weightsystem_table *s, struct weightsystem_table *d) -{ - clear_weightsystem_table(d); - for (int i = 0; i < s->nr; i++) - add_cloned_weightsystem(d, s->weightsystems[i]); -} - -void copy_cylinders(const struct cylinder_table *s, struct cylinder_table *d) -{ - int i; - clear_cylinder_table(d); - for (i = 0; i < s->nr; i++) - add_cloned_cylinder(d, s->cylinders[i]); -} - -static void free_tank_info(struct tank_info info) -{ - free((void *)info.name); -} - -/* weightsystem table functions */ -//static MAKE_GET_IDX(weightsystem_table, weightsystem_t, weightsystems) -static MAKE_GROW_TABLE(weightsystem_table, weightsystem_t, weightsystems) -//static MAKE_GET_INSERTION_INDEX(weightsystem_table, weightsystem_t, weightsystems, weightsystem_less_than) -MAKE_ADD_TO(weightsystem_table, weightsystem_t, weightsystems) -static MAKE_REMOVE_FROM(weightsystem_table, weightsystems) -//MAKE_SORT(weightsystem_table, weightsystem_t, weightsystems, comp_weightsystems) -//MAKE_REMOVE(weightsystem_table, weightsystem_t, weightsystem) -MAKE_CLEAR_TABLE(weightsystem_table, weightsystems, weightsystem) - -/* cylinder table functions */ -//static MAKE_GET_IDX(cylinder_table, cylinder_t, cylinders) -static MAKE_GROW_TABLE(cylinder_table, cylinder_t, cylinders) -//static MAKE_GET_INSERTION_INDEX(cylinder_table, cylinder_t, cylinders, cylinder_less_than) -static MAKE_ADD_TO(cylinder_table, cylinder_t, cylinders) -static MAKE_REMOVE_FROM(cylinder_table, cylinders) -//MAKE_SORT(cylinder_table, cylinder_t, cylinders, comp_cylinders) -//MAKE_REMOVE(cylinder_table, cylinder_t, cylinder) -MAKE_CLEAR_TABLE(cylinder_table, cylinders, cylinder) - -/* tank_info table functions */ -static MAKE_GROW_TABLE(tank_info_table, tank_info_t, infos) -static MAKE_ADD_TO(tank_info_table, tank_info_t, infos) -//static MAKE_REMOVE_FROM(tank_info_table, infos) -MAKE_CLEAR_TABLE(tank_info_table, infos, tank_info) - -const char *cylinderuse_text[NUM_GAS_USE] = { - QT_TRANSLATE_NOOP("gettextFromC", "OC-gas"), QT_TRANSLATE_NOOP("gettextFromC", "diluent"), QT_TRANSLATE_NOOP("gettextFromC", "oxygen"), QT_TRANSLATE_NOOP("gettextFromC", "not used") -}; - -enum cylinderuse cylinderuse_from_text(const char *text) -{ - for (enum cylinderuse i = 0; i < NUM_GAS_USE; i++) { - if (same_string(text, cylinderuse_text[i]) || same_string(text, translate("gettextFromC", cylinderuse_text[i]))) - return i; - } - return (enum cylinderuse)-1; -} - -/* Add a metric or an imperial tank info structure. Copies the passed-in string. */ -void add_tank_info_metric(struct tank_info_table *table, const char *name, int ml, int bar) -{ - struct tank_info info = { strdup(name), .ml = ml, .bar = bar }; - add_to_tank_info_table(table, table->nr, info); -} - -void add_tank_info_imperial(struct tank_info_table *table, const char *name, int cuft, int psi) -{ - struct tank_info info = { strdup(name), .cuft = cuft, .psi = psi }; - add_to_tank_info_table(table, table->nr, info); -} - -static struct tank_info *get_tank_info(struct tank_info_table *table, const char *name) -{ - for (int i = 0; i < table->nr; ++i) { - if (same_string(table->infos[i].name, name)) - return &table->infos[i]; - } - return NULL; -} - -extern void set_tank_info_data(struct tank_info_table *table, const char *name, volume_t size, pressure_t working_pressure) -{ - struct tank_info *info = get_tank_info(table, name); - if (info) { - if (info->ml != 0 || info->bar != 0) { - info->bar = working_pressure.mbar / 1000; - info->ml = size.mliter; - } else { - info->psi = lrint(to_PSI(working_pressure)); - info->cuft = lrint(ml_to_cuft(size.mliter) * mbar_to_atm(working_pressure.mbar)); - } - } else { - // Metric is a better choice as the volume is independent of the working pressure - add_tank_info_metric(table, name, size.mliter, working_pressure.mbar / 1000); - } -} - -extern void extract_tank_info(const struct tank_info *info, volume_t *size, pressure_t *working_pressure) -{ - working_pressure->mbar = info->bar != 0 ? info->bar * 1000 : psi_to_mbar(info->psi); - if (info->ml != 0) - size->mliter = info->ml; - else if (working_pressure->mbar != 0) - size->mliter = lrint(cuft_to_l(info->cuft) * 1000 / mbar_to_atm(working_pressure->mbar)); -} - -extern bool get_tank_info_data(struct tank_info_table *table, const char *name, volume_t *size, pressure_t *working_pressure) -{ - struct tank_info *info = get_tank_info(table, name); - if (info) { - extract_tank_info(info, size, working_pressure); - - return true; - } - return false; -} - -/* placeholders for a few functions that we need to redesign for the Qt UI */ -void add_cylinder_description(const cylinder_type_t *type) -{ - const char *desc = type->description; - if (empty_string(desc)) - return; - for (int i = 0; i < tank_info_table.nr; i++) { - if (strcmp(tank_info_table.infos[i].name, desc) == 0) - return; - } - add_tank_info_metric(&tank_info_table, desc, type->size.mliter, - type->workingpressure.mbar / 1000); -} - -void add_weightsystem_description(const weightsystem_t *weightsystem) -{ - const char *desc; - int i; - - desc = weightsystem->description; - if (!desc) - return; - for (i = 0; i < MAX_WS_INFO && ws_info[i].name != NULL; i++) { - if (same_string(ws_info[i].name, desc)) { - ws_info[i].grams = weightsystem->weight.grams; - return; - } - } - if (i < MAX_WS_INFO) { - // FIXME: leaked on exit - ws_info[i].name = strdup(desc); - ws_info[i].grams = weightsystem->weight.grams; - } -} - -struct ws_info_t *get_weightsystem_description(const char *name) -{ - for (int i = 0; i < MAX_WS_INFO && ws_info[i].name != NULL; i++) { - // Also finds translated names (TODO: should only consider non-user items). - if (same_string(ws_info[i].name, name) || - same_string(translate("gettextFromC", ws_info[i].name), name)) - return &ws_info[i]; - } - return NULL; -} - -weightsystem_t clone_weightsystem(weightsystem_t ws) -{ - weightsystem_t res = { ws.weight, copy_string(ws.description), ws.auto_filled }; - return res; -} - -/* Add a clone of a weightsystem to the end of a weightsystem table. - * Cloned means that the description-string is copied. */ -void add_cloned_weightsystem(struct weightsystem_table *t, weightsystem_t ws) -{ - add_to_weightsystem_table(t, t->nr, clone_weightsystem(ws)); -} - -cylinder_t clone_cylinder(cylinder_t cyl) -{ - cylinder_t res = cyl; - res.type.description = copy_string(res.type.description); - return res; -} - -void add_cylinder(struct cylinder_table *t, int idx, cylinder_t cyl) -{ - add_to_cylinder_table(t, idx, cyl); - /* FIXME: This is a horrible hack: we make sure that at the end of - * every single cylinder table there is an empty cylinder that can - * be used by the planner as "surface air" cylinder. Fix this. - */ - add_to_cylinder_table(t, t->nr, empty_cylinder); - t->nr--; - t->cylinders[t->nr].cylinder_use = NOT_USED; -} - -/* Add a clone of a cylinder to the end of a cylinder table. - * Cloned means that the description-string is copied. */ -void add_cloned_cylinder(struct cylinder_table *t, cylinder_t cyl) -{ - add_cylinder(t, t->nr, clone_cylinder(cyl)); -} - -bool same_weightsystem(weightsystem_t w1, weightsystem_t w2) -{ - return w1.weight.grams == w2.weight.grams && - same_string(w1.description, w2.description); -} - -void get_gas_string(struct gasmix gasmix, char *text, int len) -{ - if (gasmix_is_air(gasmix)) - snprintf(text, len, "%s", translate("gettextFromC", "air")); - else if (get_he(gasmix) == 0 && get_o2(gasmix) < 1000) - snprintf(text, len, translate("gettextFromC", "EAN%d"), (get_o2(gasmix) + 5) / 10); - else if (get_he(gasmix) == 0 && get_o2(gasmix) == 1000) - snprintf(text, len, "%s", translate("gettextFromC", "oxygen")); - else - snprintf(text, len, "(%d/%d)", (get_o2(gasmix) + 5) / 10, (get_he(gasmix) + 5) / 10); -} - -/* Returns a static char buffer - only good for immediate use by printf etc */ -const char *gasname(struct gasmix gasmix) -{ - static char gas[64]; - get_gas_string(gasmix, gas, sizeof(gas)); - return gas; -} - -int gas_volume(const cylinder_t *cyl, pressure_t p) -{ - double bar = p.mbar / 1000.0; - double z_factor = gas_compressibility_factor(cyl->gasmix, bar); - return lrint(cyl->type.size.mliter * bar_to_atm(bar) / z_factor); -} - -int find_best_gasmix_match(struct gasmix mix, const struct cylinder_table *cylinders) -{ - int i; - int best = -1, score = INT_MAX; - - for (i = 0; i < cylinders->nr; i++) { - const cylinder_t *match; - int distance; - - match = cylinders->cylinders + i; - distance = gasmix_distance(mix, match->gasmix); - if (distance >= score) - continue; - best = i; - score = distance; - } - return best; -} - -/* - * We hardcode the most common standard cylinders, - * we should pick up any other names from the dive - * logs directly. - */ -static void add_default_tank_infos(struct tank_info_table *table) -{ - /* Size-only metric cylinders */ - add_tank_info_metric(table, "10.0ℓ", 10000, 0); - add_tank_info_metric(table, "11.1ℓ", 11100, 0); - - /* Most common AL cylinders */ - add_tank_info_imperial(table, "AL40", 40, 3000); - add_tank_info_imperial(table, "AL50", 50, 3000); - add_tank_info_imperial(table, "AL63", 63, 3000); - add_tank_info_imperial(table, "AL72", 72, 3000); - add_tank_info_imperial(table, "AL80", 80, 3000); - add_tank_info_imperial(table, "AL100", 100, 3300); - - /* Metric AL cylinders */ - add_tank_info_metric(table, "ALU7", 7000, 200); - - /* Somewhat common LP steel cylinders */ - add_tank_info_imperial(table, "LP85", 85, 2640); - add_tank_info_imperial(table, "LP95", 95, 2640); - add_tank_info_imperial(table, "LP108", 108, 2640); - add_tank_info_imperial(table, "LP121", 121, 2640); - - /* Somewhat common HP steel cylinders */ - add_tank_info_imperial(table, "HP65", 65, 3442); - add_tank_info_imperial(table, "HP80", 80, 3442); - add_tank_info_imperial(table, "HP100", 100, 3442); - add_tank_info_imperial(table, "HP117", 117, 3442); - add_tank_info_imperial(table, "HP119", 119, 3442); - add_tank_info_imperial(table, "HP130", 130, 3442); - - /* Common European steel cylinders */ - add_tank_info_metric(table, "3ℓ 232 bar", 3000, 232); - add_tank_info_metric(table, "3ℓ 300 bar", 3000, 300); - add_tank_info_metric(table, "10ℓ 200 bar", 10000, 200); - add_tank_info_metric(table, "10ℓ 232 bar", 10000, 232); - add_tank_info_metric(table, "10ℓ 300 bar", 10000, 300); - add_tank_info_metric(table, "12ℓ 200 bar", 12000, 200); - add_tank_info_metric(table, "12ℓ 232 bar", 12000, 232); - add_tank_info_metric(table, "12ℓ 300 bar", 12000, 300); - add_tank_info_metric(table, "15ℓ 200 bar", 15000, 200); - add_tank_info_metric(table, "15ℓ 232 bar", 15000, 232); - add_tank_info_metric(table, "D7 300 bar", 14000, 300); - add_tank_info_metric(table, "D8.5 232 bar", 17000, 232); - add_tank_info_metric(table, "D12 232 bar", 24000, 232); - add_tank_info_metric(table, "D13 232 bar", 26000, 232); - add_tank_info_metric(table, "D15 232 bar", 30000, 232); - add_tank_info_metric(table, "D16 232 bar", 32000, 232); - add_tank_info_metric(table, "D18 232 bar", 36000, 232); - add_tank_info_metric(table, "D20 232 bar", 40000, 232); -} - -struct tank_info_table tank_info_table; -void reset_tank_info_table(struct tank_info_table *table) -{ - clear_tank_info_table(table); - if (prefs.display_default_tank_infos) - add_default_tank_infos(table); - - /* Add cylinders from dive list */ - for (int i = 0; i < divelog.dives->nr; ++i) { - const struct dive *dive = divelog.dives->dives[i]; - for (int j = 0; j < dive->cylinders.nr; j++) { - const cylinder_t *cyl = get_cylinder(dive, j); - add_cylinder_description(&cyl->type); - } - } -} - -/* - * We hardcode the most common weight system types - * This is a bit odd as the weight system types don't usually encode weight - */ -struct ws_info_t ws_info[MAX_WS_INFO] = { - { QT_TRANSLATE_NOOP("gettextFromC", "integrated"), 0 }, - { QT_TRANSLATE_NOOP("gettextFromC", "belt"), 0 }, - { QT_TRANSLATE_NOOP("gettextFromC", "ankle"), 0 }, - { QT_TRANSLATE_NOOP("gettextFromC", "backplate"), 0 }, - { QT_TRANSLATE_NOOP("gettextFromC", "clip-on"), 0 }, -}; - -void remove_cylinder(struct dive *dive, int idx) -{ - remove_from_cylinder_table(&dive->cylinders, idx); -} - -void remove_weightsystem(struct dive *dive, int idx) -{ - remove_from_weightsystem_table(&dive->weightsystems, idx); -} - -// ws is cloned. -void set_weightsystem(struct dive *dive, int idx, weightsystem_t ws) -{ - if (idx < 0 || idx >= dive->weightsystems.nr) - return; - free_weightsystem(dive->weightsystems.weightsystems[idx]); - dive->weightsystems.weightsystems[idx] = clone_weightsystem(ws); -} - -/* when planning a dive we need to make sure that all cylinders have a sane depth assigned - * and if we are tracking gas consumption the pressures need to be reset to start = end = workingpressure */ -void reset_cylinders(struct dive *dive, bool track_gas) -{ - pressure_t decopo2 = {.mbar = prefs.decopo2}; - - for (int i = 0; i < dive->cylinders.nr; i++) { - cylinder_t *cyl = get_cylinder(dive, i); - if (cyl->depth.mm == 0) /* if the gas doesn't give a mod, calculate based on prefs */ - cyl->depth = gas_mod(cyl->gasmix, decopo2, dive, M_OR_FT(3,10)); - if (track_gas) - cyl->start.mbar = cyl->end.mbar = cyl->type.workingpressure.mbar; - cyl->gas_used.mliter = 0; - cyl->deco_gas_used.mliter = 0; - } -} - -static void copy_cylinder_type(const cylinder_t *s, cylinder_t *d) -{ - free_cylinder(*d); - d->type = s->type; - d->type.description = s->type.description ? strdup(s->type.description) : NULL; - d->gasmix = s->gasmix; - d->depth = s->depth; - d->cylinder_use = s->cylinder_use; - d->manually_added = true; -} - -/* copy the equipment data part of the cylinders but keep pressures */ -void copy_cylinder_types(const struct dive *s, struct dive *d) -{ - int i; - if (!s || !d) - return; - - for (i = 0; i < s->cylinders.nr && i < d->cylinders.nr; i++) - copy_cylinder_type(get_cylinder(s, i), get_cylinder(d, i)); - - for ( ; i < s->cylinders.nr; i++) - add_cloned_cylinder(&d->cylinders, *get_cylinder(s, i)); -} - -cylinder_t *add_empty_cylinder(struct cylinder_table *t) -{ - cylinder_t cyl = empty_cylinder; - cyl.type.description = strdup(""); - add_cylinder(t, t->nr, cyl); - return &t->cylinders[t->nr - 1]; -} - -/* access to cylinders is controlled by two functions: - * - get_cylinder() returns the cylinder of a dive and supposes that - * the cylinder with the given index exists. If it doesn't, an error - * message is printed and NULL returned. - * - get_or_create_cylinder() creates an empty cylinder if it doesn't exist. - * Multiple cylinders might be created if the index is bigger than the - * number of existing cylinders - */ -cylinder_t *get_cylinder(const struct dive *d, int idx) -{ - /* FIXME: The planner uses a dummy cylinder one past the official number of cylinders - * in the table to mark no-cylinder surface interavals. This is horrendous. Fix ASAP. */ - // if (idx < 0 || idx >= d->cylinders.nr) { - if (idx < 0 || idx >= d->cylinders.nr + 1 || idx >= d->cylinders.allocated) { - report_info("Warning: accessing invalid cylinder %d (%d existing)", idx, d->cylinders.nr); - return NULL; - } - return &d->cylinders.cylinders[idx]; -} - -cylinder_t *get_or_create_cylinder(struct dive *d, int idx) -{ - if (idx < 0) { - report_info("Warning: accessing invalid cylinder %d", idx); - return NULL; - } - while (idx >= d->cylinders.nr) - add_empty_cylinder(&d->cylinders); - return &d->cylinders.cylinders[idx]; -} - -/* if a default cylinder is set, use that */ -void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl) -{ - const char *cyl_name = prefs.default_cylinder; - pressure_t pO2 = {.mbar = lrint(prefs.modpO2 * 1000.0)}; - - if (!cyl_name) - return; - for (int i = 0; i < tank_info_table.nr; ++i) { - struct tank_info *ti = &tank_info_table.infos[i]; - if (strcmp(ti->name, cyl_name) == 0) { - cyl->type.description = strdup(ti->name); - if (ti->ml) { - cyl->type.size.mliter = ti->ml; - cyl->type.workingpressure.mbar = ti->bar * 1000; - } else { - cyl->type.workingpressure.mbar = psi_to_mbar(ti->psi); - if (ti->psi) - cyl->type.size.mliter = lrint(cuft_to_l(ti->cuft) * 1000 / bar_to_atm(psi_to_bar(ti->psi))); - } - // MOD of air - cyl->depth = gas_mod(cyl->gasmix, pO2, dive, 1); - return; - } - } -} - -cylinder_t create_new_cylinder(const struct dive *d) -{ - cylinder_t cyl = empty_cylinder; - fill_default_cylinder(d, &cyl); - cyl.start = cyl.type.workingpressure; - cyl.cylinder_use = OC_GAS; - return cyl; -} - -cylinder_t create_new_manual_cylinder(const struct dive *d) -{ - cylinder_t cyl = create_new_cylinder(d); - cyl.manually_added = true; - return cyl; -} - -void add_default_cylinder(struct dive *d) -{ - // Only add if there are no cylinders yet - if (d->cylinders.nr > 0) - return; - - cylinder_t cyl; - if (!empty_string(prefs.default_cylinder)) { - cyl = create_new_cylinder(d); - } else { - cyl = empty_cylinder; - // roughly an AL80 - cyl.type.description = strdup(translate("gettextFromC", "unknown")); - cyl.type.size.mliter = 11100; - cyl.type.workingpressure.mbar = 207000; - } - add_cylinder(&d->cylinders, 0, cyl); - reset_cylinders(d, false); -} - -static bool show_cylinder(const struct dive *d, int i) -{ - if (is_cylinder_used(d, i)) - return true; - - const cylinder_t *cyl = &d->cylinders.cylinders[i]; - if (cyl->start.mbar || cyl->sample_start.mbar || - cyl->end.mbar || cyl->sample_end.mbar) - return true; - if (cyl->manually_added) - return true; - - /* - * The cylinder has some data, but none of it is very interesting, - * it has no pressures and no gas switches. Do we want to show it? - */ - return false; -} - -/* The unused cylinders at the end of the cylinder list are hidden. */ -int first_hidden_cylinder(const struct dive *d) -{ - int res = d->cylinders.nr; - while (res > 0 && !show_cylinder(d, res - 1)) - --res; - return res; -} - -#ifdef DEBUG_CYL -void dump_cylinders(struct dive *dive, bool verbose) -{ - printf("Cylinder list:\n"); - for (int i = 0; i < dive->cylinders; i++) { - cylinder_t *cyl = get_cylinder(dive, i); - - printf("%02d: Type %s, %3.1fl, %3.0fbar\n", i, cyl->type.description, cyl->type.size.mliter / 1000.0, cyl->type.workingpressure.mbar / 1000.0); - printf(" Gasmix O2 %2.0f%% He %2.0f%%\n", cyl->gasmix.o2.permille / 10.0, cyl->gasmix.he.permille / 10.0); - printf(" Pressure Start %3.0fbar End %3.0fbar Sample start %3.0fbar Sample end %3.0fbar\n", cyl->start.mbar / 1000.0, cyl->end.mbar / 1000.0, cyl->sample_start.mbar / 1000.0, cyl->sample_end.mbar / 1000.0); - if (verbose) { - printf(" Depth %3.0fm\n", cyl->depth.mm / 1000.0); - printf(" Added %s\n", (cyl->manually_added ? "manually" : "")); - printf(" Gas used Bottom %5.0fl Deco %5.0fl\n", cyl->gas_used.mliter / 1000.0, cyl->deco_gas_used.mliter / 1000.0); - printf(" Use %d\n", cyl->cylinder_use); - printf(" Bestmix %s %s\n", (cyl->bestmix_o2 ? "O2" : " "), (cyl->bestmix_he ? "He" : " ")); - } - } -} -#endif diff --git a/core/equipment.cpp b/core/equipment.cpp new file mode 100644 index 000000000..fff8c1fa2 --- /dev/null +++ b/core/equipment.cpp @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifdef __clang__ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif + +/* equipment.cpp */ +#include +#include +#include +#include +#include +#include +#include "equipment.h" +#include "gettext.h" +#include "dive.h" +#include "divelist.h" +#include "divelog.h" +#include "errorhelper.h" +#include "pref.h" +#include "range.h" +#include "subsurface-string.h" + +cylinder_t::cylinder_t() = default; +cylinder_t::~cylinder_t() = default; + +static cylinder_t make_surface_air_cylinder() +{ + cylinder_t res; + res.cylinder_use = NOT_USED; + return res; +} +static const cylinder_t surface_air_cylinder = make_surface_air_cylinder(); + +static void warn_index(size_t i, size_t max) +{ + if (i >= max + 1) { + report_info("Warning: accessing invalid cylinder %lu (%lu existing)", + static_cast(i), static_cast(max)); + } +} + +cylinder_t &cylinder_table::operator[](size_t i) +{ + warn_index(i, size()); + return i < size() ? std::vector::operator[](i) + : const_cast(surface_air_cylinder); +} + +const cylinder_t &cylinder_table::operator[](size_t i) const +{ + warn_index(i, size()); + return i < size() ? std::vector::operator[](i) + : surface_air_cylinder; +} + +weightsystem_t::weightsystem_t() = default; +weightsystem_t::~weightsystem_t() = default; +weightsystem_t::weightsystem_t(weight_t w, std::string desc, bool auto_filled) + : weight(w), description(std::move(desc)), auto_filled(auto_filled) +{ +} + +const char *cylinderuse_text[NUM_GAS_USE] = { + QT_TRANSLATE_NOOP("gettextFromC", "OC-gas"), QT_TRANSLATE_NOOP("gettextFromC", "diluent"), QT_TRANSLATE_NOOP("gettextFromC", "oxygen"), QT_TRANSLATE_NOOP("gettextFromC", "not used") +}; + +enum cylinderuse cylinderuse_from_text(const char *text) +{ + for (int i = 0; i < static_cast(NUM_GAS_USE); i++) { + if (same_string(text, cylinderuse_text[i]) || same_string(text, translate("gettextFromC", cylinderuse_text[i]))) + return static_cast(i); + } + return static_cast(-1); +} + +/* Add a metric or an imperial tank info structure. Copies the passed-in string. */ +static void add_tank_info_metric(std::vector &table, const std::string &name, int ml, int bar) +{ + table.push_back(tank_info{ name, .ml = ml, .bar = bar }); +} + +static void add_tank_info_imperial(std::vector &table, const std::string &name, int cuft, int psi) +{ + table.push_back(tank_info{ name, .cuft = cuft, .psi = psi }); +} + +struct tank_info *get_tank_info(std::vector &table, const std::string &name) +{ + auto it = std::find_if(table.begin(), table.end(), [&name](const tank_info &info) + { return info.name == name; }); + return it != table.end() ? &*it : nullptr; +} + +void set_tank_info_data(std::vector &table, const std::string &name, volume_t size, pressure_t working_pressure) +{ + struct tank_info *info = get_tank_info(table, name); + if (info) { + if (info->ml != 0 || info->bar != 0) { + info->bar = working_pressure.mbar / 1000; + info->ml = size.mliter; + } else { + info->psi = lrint(to_PSI(working_pressure)); + info->cuft = lrint(ml_to_cuft(size.mliter) * mbar_to_atm(working_pressure.mbar)); + } + } else { + // Metric is a better choice as the volume is independent of the working pressure + add_tank_info_metric(table, name, size.mliter, working_pressure.mbar / 1000); + } +} + +std::pair extract_tank_info(const struct tank_info &info) +{ + pressure_t working_pressure { + .mbar = static_cast(info.bar != 0 ? info.bar * 1000 : psi_to_mbar(info.psi)) + }; + volume_t size; + if (info.ml != 0) + size.mliter = info.ml; + else if (working_pressure.mbar != 0) + size.mliter = lrint(cuft_to_l(info.cuft) * 1000 / mbar_to_atm(working_pressure.mbar)); + return std::make_pair(size, working_pressure); +} + +std::pair get_tank_info_data(const std::vector &table, const std::string &name) +{ + // Here, we would need a const version of get_tank_info(). + auto it = std::find_if(table.begin(), table.end(), [&name](const tank_info &info) + { return info.name == name; }); + return it != table.end() ? extract_tank_info(*it) + : std::make_pair(volume_t(), pressure_t()); +} + +void add_cylinder_description(const cylinder_type_t &type) +{ + const std::string &desc = type.description; + if (desc.empty()) + return; + if (std::any_of(tank_info_table.begin(), tank_info_table.end(), + [&desc](const tank_info &info) { return info.name == desc; })) + return; + add_tank_info_metric(tank_info_table, desc, type.size.mliter, + type.workingpressure.mbar / 1000); +} + +void add_weightsystem_description(const weightsystem_t &weightsystem) +{ + if (weightsystem.description.empty()) + return; + + auto it = std::find_if(ws_info_table.begin(), ws_info_table.end(), + [&weightsystem](const ws_info &info) + { return info.name == weightsystem.description; }); + if (it != ws_info_table.end()) { + it->weight = weightsystem.weight; + return; + } + ws_info_table.push_back(ws_info { std::string(weightsystem.description), weightsystem.weight }); +} + +weight_t get_weightsystem_weight(const std::string &name) +{ + // Also finds translated names (TODO: should only consider non-user items). + auto it = std::find_if(ws_info_table.begin(), ws_info_table.end(), + [&name](const ws_info &info) + { return info.name == name || translate("gettextFromC", info.name.c_str()) == name; }); + return it != ws_info_table.end() ? it->weight : weight_t(); +} + +void cylinder_table::add(int idx, cylinder_t cyl) +{ + insert(begin() + idx, std::move(cyl)); +} + +bool weightsystem_t::operator==(const weightsystem_t &w2) const +{ + return std::tie(weight.grams, description) == + std::tie(w2.weight.grams, w2.description); +} + +volume_t cylinder_t::gas_volume(pressure_t p) const +{ + double bar = p.mbar / 1000.0; + double z_factor = gas_compressibility_factor(gasmix, bar); + return volume_t { .mliter = int_cast(type.size.mliter * bar_to_atm(bar) / z_factor) }; +} + +int find_best_gasmix_match(struct gasmix mix, const struct cylinder_table &cylinders) +{ + int best = -1, score = INT_MAX; + + for (auto [i, match]: enumerated_range(cylinders)) { + int distance = gasmix_distance(mix, match.gasmix); + if (distance >= score) + continue; + best = i; + score = distance; + } + return best; +} + +/* + * We hardcode the most common standard cylinders, + * we should pick up any other names from the dive + * logs directly. + */ +static void add_default_tank_infos(std::vector &table) +{ + /* Size-only metric cylinders */ + add_tank_info_metric(table, "10.0ℓ", 10000, 0); + add_tank_info_metric(table, "11.1ℓ", 11100, 0); + + /* Most common AL cylinders */ + add_tank_info_imperial(table, "AL40", 40, 3000); + add_tank_info_imperial(table, "AL50", 50, 3000); + add_tank_info_imperial(table, "AL63", 63, 3000); + add_tank_info_imperial(table, "AL72", 72, 3000); + add_tank_info_imperial(table, "AL80", 80, 3000); + add_tank_info_imperial(table, "AL100", 100, 3300); + + /* Metric AL cylinders */ + add_tank_info_metric(table, "ALU7", 7000, 200); + + /* Somewhat common LP steel cylinders */ + add_tank_info_imperial(table, "LP85", 85, 2640); + add_tank_info_imperial(table, "LP95", 95, 2640); + add_tank_info_imperial(table, "LP108", 108, 2640); + add_tank_info_imperial(table, "LP121", 121, 2640); + + /* Somewhat common HP steel cylinders */ + add_tank_info_imperial(table, "HP65", 65, 3442); + add_tank_info_imperial(table, "HP80", 80, 3442); + add_tank_info_imperial(table, "HP100", 100, 3442); + add_tank_info_imperial(table, "HP117", 117, 3442); + add_tank_info_imperial(table, "HP119", 119, 3442); + add_tank_info_imperial(table, "HP130", 130, 3442); + + /* Common European steel cylinders */ + add_tank_info_metric(table, "3ℓ 232 bar", 3000, 232); + add_tank_info_metric(table, "3ℓ 300 bar", 3000, 300); + add_tank_info_metric(table, "10ℓ 200 bar", 10000, 200); + add_tank_info_metric(table, "10ℓ 232 bar", 10000, 232); + add_tank_info_metric(table, "10ℓ 300 bar", 10000, 300); + add_tank_info_metric(table, "12ℓ 200 bar", 12000, 200); + add_tank_info_metric(table, "12ℓ 232 bar", 12000, 232); + add_tank_info_metric(table, "12ℓ 300 bar", 12000, 300); + add_tank_info_metric(table, "15ℓ 200 bar", 15000, 200); + add_tank_info_metric(table, "15ℓ 232 bar", 15000, 232); + add_tank_info_metric(table, "D7 232 bar", 14000, 232); + add_tank_info_metric(table, "D7 300 bar", 14000, 300); + add_tank_info_metric(table, "D8.5 232 bar", 17000, 232); + add_tank_info_metric(table, "D12 232 bar", 24000, 232); + add_tank_info_metric(table, "D13 232 bar", 26000, 232); + add_tank_info_metric(table, "D15 232 bar", 30000, 232); + add_tank_info_metric(table, "D16 232 bar", 32000, 232); + add_tank_info_metric(table, "D18 232 bar", 36000, 232); + add_tank_info_metric(table, "D20 232 bar", 40000, 232); +} + +std::vector tank_info_table; +void reset_tank_info_table(std::vector &table) +{ + table.clear(); + if (prefs.display_default_tank_infos) + add_default_tank_infos(table); + + /* Add cylinders from dive list */ + for (auto &dive: divelog.dives) { + for (auto &cyl: dive->cylinders) + add_cylinder_description(cyl.type); + } +} + +/* + * We hardcode the most common weight system types + * This is a bit odd as the weight system types don't usually encode weight + */ +struct std::vector ws_info_table = { + { QT_TRANSLATE_NOOP("gettextFromC", "integrated"), weight_t() }, + { QT_TRANSLATE_NOOP("gettextFromC", "belt"), weight_t() }, + { QT_TRANSLATE_NOOP("gettextFromC", "ankle"), weight_t() }, + { QT_TRANSLATE_NOOP("gettextFromC", "backplate"), weight_t() }, + { QT_TRANSLATE_NOOP("gettextFromC", "clip-on"), weight_t() }, +}; + +void remove_cylinder(struct dive *dive, int idx) +{ + dive->cylinders.erase(dive->cylinders.begin() + idx); +} + +void weightsystem_table::remove(int idx) +{ + erase(begin() + idx); +} + +void weightsystem_table::add(int idx, weightsystem_t ws) +{ + idx = std::clamp(idx, 0, static_cast(size())); + insert(begin() + idx, std::move(ws)); +} + +void weightsystem_table::set(int idx, weightsystem_t ws) +{ + if (idx < 0 || static_cast(idx) >= size()) + return; + (*this)[idx] = std::move(ws); +} + +/* when planning a dive we need to make sure that all cylinders have a sane depth assigned + * and if we are tracking gas consumption the pressures need to be reset to start = end = workingpressure */ +void reset_cylinders(struct dive *dive, bool track_gas) +{ + pressure_t decopo2 = {.mbar = prefs.decopo2}; + + for (cylinder_t &cyl: dive->cylinders) { + if (cyl.depth.mm == 0) /* if the gas doesn't give a mod, calculate based on prefs */ + cyl.depth = dive->gas_mod(cyl.gasmix, decopo2, M_OR_FT(3,10)); + if (track_gas) + cyl.start.mbar = cyl.end.mbar = cyl.type.workingpressure.mbar; + cyl.gas_used = 0_l; + cyl.deco_gas_used = 0_l; + } +} + +static void copy_cylinder_type(const cylinder_t &s, cylinder_t &d) +{ + d.type = s.type; + d.gasmix = s.gasmix; + d.depth = s.depth; + d.cylinder_use = s.cylinder_use; + d.manually_added = true; +} + +/* copy the equipment data part of the cylinders but keep pressures */ +void copy_cylinder_types(const struct dive *s, struct dive *d) +{ + if (!s || !d) + return; + + for (size_t i = 0; i < s->cylinders.size() && i < d->cylinders.size(); i++) + copy_cylinder_type(s->cylinders[i], d->cylinders[i]); + + for (size_t i = d->cylinders.size(); i < s->cylinders.size(); i++) + d->cylinders.push_back(s->cylinders[i]); +} + +/* if a default cylinder is set, use that */ +void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl) +{ + const std::string &cyl_name = prefs.default_cylinder; + pressure_t pO2 = {.mbar = int_cast(prefs.modpO2 * 1000.0)}; + + if (cyl_name.empty()) + return; + for (auto &ti: tank_info_table) { + if (ti.name == cyl_name) { + cyl->type.description = ti.name; + if (ti.ml) { + cyl->type.size.mliter = ti.ml; + cyl->type.workingpressure.mbar = ti.bar * 1000; + } else { + cyl->type.workingpressure.mbar = psi_to_mbar(ti.psi); + if (ti.psi) + cyl->type.size.mliter = lrint(cuft_to_l(ti.cuft) * 1000 / bar_to_atm(psi_to_bar(ti.psi))); + } + // MOD of air + cyl->depth = dive->gas_mod(cyl->gasmix, pO2, 1); + return; + } + } +} + +cylinder_t default_cylinder(const struct dive *d) +{ + cylinder_t res; + fill_default_cylinder(d, &res); + return res; +} + +cylinder_t create_new_cylinder(const struct dive *d) +{ + cylinder_t cyl = default_cylinder(d); + cyl.start = cyl.type.workingpressure; + cyl.cylinder_use = OC_GAS; + return cyl; +} + +cylinder_t create_new_manual_cylinder(const struct dive *d) +{ + cylinder_t cyl = create_new_cylinder(d); + cyl.manually_added = true; + return cyl; +} + +void add_default_cylinder(struct dive *d) +{ + // Only add if there are no cylinders yet + if (!d->cylinders.empty()) + return; + + cylinder_t cyl; + if (!prefs.default_cylinder.empty()) { + cyl = create_new_cylinder(d); + } else { + // roughly an AL80 + cyl.type.description = translate("gettextFromC", "unknown"); + cyl.type.size = 11100_ml; + cyl.type.workingpressure = 207_bar; + } + d->cylinders.add(0, std::move(cyl)); + reset_cylinders(d, false); +} + +static bool show_cylinder(const struct dive *d, int i) +{ + if (d->is_cylinder_used(i)) + return true; + + const cylinder_t &cyl = d->cylinders[i]; + if (cyl.start.mbar || cyl.sample_start.mbar || + cyl.end.mbar || cyl.sample_end.mbar) + return true; + if (cyl.manually_added) + return true; + + /* + * The cylinder has some data, but none of it is very interesting, + * it has no pressures and no gas switches. Do we want to show it? + */ + return false; +} + +/* The unused cylinders at the end of the cylinder list are hidden. */ +int first_hidden_cylinder(const struct dive *d) +{ + size_t res = d->cylinders.size(); + while (res > 0 && !show_cylinder(d, res - 1)) + --res; + return static_cast(res); +} + +#ifdef DEBUG_CYL +void dump_cylinders(struct dive *dive, bool verbose) +{ + printf("Cylinder list:\n"); + for (int i = 0; i < dive->cylinders; i++) { + cylinder_t *cyl = dive->get_cylinder(i); + + printf("%02d: Type %s, %3.1fl, %3.0fbar\n", i, cyl->type.description.c_str(), cyl->type.size.mliter / 1000.0, cyl->type.workingpressure.mbar / 1000.0); + printf(" Gasmix O2 %2.0f%% He %2.0f%%\n", cyl->gasmix.o2.permille / 10.0, cyl->gasmix.he.permille / 10.0); + printf(" Pressure Start %3.0fbar End %3.0fbar Sample start %3.0fbar Sample end %3.0fbar\n", cyl->start.mbar / 1000.0, cyl->end.mbar / 1000.0, cyl->sample_start.mbar / 1000.0, cyl->sample_end.mbar / 1000.0); + if (verbose) { + printf(" Depth %3.0fm\n", cyl->depth.mm / 1000.0); + printf(" Added %s\n", (cyl->manually_added ? "manually" : "")); + printf(" Gas used Bottom %5.0fl Deco %5.0fl\n", cyl->gas_used.mliter / 1000.0, cyl->deco_gas_used.mliter / 1000.0); + printf(" Use %d\n", cyl->cylinder_use); + printf(" Bestmix %s %s\n", (cyl->bestmix_o2 ? "O2" : " "), (cyl->bestmix_he ? "He" : " ")); + } + } +} +#endif diff --git a/core/equipment.h b/core/equipment.h index 0d23b9341..716809b54 100644 --- a/core/equipment.h +++ b/core/equipment.h @@ -4,95 +4,95 @@ #include "gas.h" -#ifdef __cplusplus -extern "C" { -#endif +#include +#include +#include struct dive; enum cylinderuse {OC_GAS, DILUENT, OXYGEN, NOT_USED, NUM_GAS_USE}; // The different uses for cylinders extern const char *cylinderuse_text[NUM_GAS_USE]; -typedef struct +struct cylinder_type_t { volume_t size; pressure_t workingpressure; - const char *description; /* "LP85", "AL72", "AL80", "HP100+" or whatever */ -} cylinder_type_t; + std::string description; /* "LP85", "AL72", "AL80", "HP100+" or whatever */ +}; -typedef struct +struct cylinder_t { cylinder_type_t type; - struct gasmix gasmix; + struct gasmix gasmix = gasmix_air; pressure_t start, end, sample_start, sample_end; depth_t depth; - bool manually_added; + bool manually_added = false; volume_t gas_used; volume_t deco_gas_used; - enum cylinderuse cylinder_use; - bool bestmix_o2; - bool bestmix_he; -} cylinder_t; + enum cylinderuse cylinder_use = OC_GAS; + bool bestmix_o2 = false; + bool bestmix_he = false; -static const cylinder_t empty_cylinder = { { { 0 }, { 0 }, (const char *)0}, { { 0 }, { 0 } } , { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, false, { 0 }, { 0 }, OC_GAS, false, false }; + cylinder_t(); + ~cylinder_t(); + cylinder_t(const cylinder_t &) = default; + cylinder_t(cylinder_t &&) = default; + cylinder_t &operator=(const cylinder_t &) = default; + cylinder_t &operator=(cylinder_t &&) = default; -/* Table of cylinders. Attention: this stores cylinders, - * *not* pointers to cylinders. This has two crucial consequences: - * 1) Pointers to cylinders are not stable. They may be - * invalidated if the table is reallocated. - * 2) add_cylinder(), etc. take ownership of the - * cylinder. Notably of the description string. */ -struct cylinder_table { - int nr, allocated; - cylinder_t *cylinders; + volume_t gas_volume(pressure_t p) const; /* Volume of a cylinder at pressure 'p' */ }; -typedef struct +/* Table of cylinders. + * This is a crazy class: it is basically a std::vector<>, but overrides + * the [] accessor functions and allows out-of-bound accesses. + * This is used in the planner, which uses "max_index + 1" for the + * surface air cylinder. + * Note: an out-of-bound access returns a reference to an object with + * static linkage that MUST NOT be written into. + * Yes, this is all very mad, but it grew historically. + */ +struct cylinder_table : public std::vector { + cylinder_t &operator[](size_t i); + const cylinder_t &operator[](size_t i) const; + + void add(int idx, cylinder_t cyl); +}; + +struct weightsystem_t { weight_t weight; - const char *description; /* "integrated", "belt", "ankle" */ - bool auto_filled; /* weight was automatically derived from the type */ -} weightsystem_t; + std::string description; /* "integrated", "belt", "ankle" */ + bool auto_filled = false; /* weight was automatically derived from the type */ -static const weightsystem_t empty_weightsystem = { { 0 }, 0, false }; + weightsystem_t(); + weightsystem_t(weight_t w, std::string desc, bool auto_filled); + ~weightsystem_t(); + weightsystem_t(const weightsystem_t &) = default; + weightsystem_t(weightsystem_t &&) = default; + weightsystem_t &operator=(const weightsystem_t &) = default; + weightsystem_t &operator=(weightsystem_t &&) = default; -/* Table of weightsystems. Attention: this stores weightsystems, - * *not* pointers * to weightsystems. This has two crucial - * consequences: - * 1) Pointers to weightsystems are not stable. They may be - * invalidated if the table is reallocated. - * 2) add_to_weightsystem_table(), etc. takes ownership of the - * weightsystem. Notably of the description string */ -struct weightsystem_table { - int nr, allocated; - weightsystem_t *weightsystems; + bool operator==(const weightsystem_t &w2) const; }; -#define MAX_WS_INFO (100) +/* Table of weightsystems. Attention: this stores weightsystems, + * *not* pointers * to weightsystems. Therefore pointers to + * weightsystems are *not* stable. + */ +struct weightsystem_table : public std::vector { + void add(int idx, weightsystem_t ws); + void set(int idx, weightsystem_t ws); + void remove(int idx); +}; extern enum cylinderuse cylinderuse_from_text(const char *text); -extern void copy_weights(const struct weightsystem_table *s, struct weightsystem_table *d); -extern void copy_cylinders(const struct cylinder_table *s, struct cylinder_table *d); -extern weightsystem_t clone_weightsystem(weightsystem_t ws); -extern void free_weightsystem(weightsystem_t ws); extern void copy_cylinder_types(const struct dive *s, struct dive *d); -extern void add_cloned_weightsystem(struct weightsystem_table *t, weightsystem_t ws); -extern cylinder_t clone_cylinder(cylinder_t cyl); -extern void free_cylinder(cylinder_t cyl); -extern cylinder_t *add_empty_cylinder(struct cylinder_table *t); -extern void add_cloned_cylinder(struct cylinder_table *t, cylinder_t cyl); -extern cylinder_t *get_cylinder(const struct dive *d, int idx); -extern cylinder_t *get_or_create_cylinder(struct dive *d, int idx); -extern void add_cylinder_description(const cylinder_type_t *); -extern void add_weightsystem_description(const weightsystem_t *); -extern bool same_weightsystem(weightsystem_t w1, weightsystem_t w2); extern void remove_cylinder(struct dive *dive, int idx); -extern void remove_weightsystem(struct dive *dive, int idx); -extern void set_weightsystem(struct dive *dive, int idx, weightsystem_t ws); extern void reset_cylinders(struct dive *dive, bool track_gas); -extern int gas_volume(const cylinder_t *cyl, pressure_t p); /* Volume in mliter of a cylinder at pressure 'p' */ -extern int find_best_gasmix_match(struct gasmix mix, const struct cylinder_table *cylinders); +extern int find_best_gasmix_match(struct gasmix mix, const struct cylinder_table &cylinders); extern void fill_default_cylinder(const struct dive *dive, cylinder_t *cyl); /* dive is needed to fill out MOD, which depends on salinity. */ +extern cylinder_t default_cylinder(const struct dive *d); extern cylinder_t create_new_manual_cylinder(const struct dive *dive); /* dive is needed to fill out MOD, which depends on salinity. */ extern void add_default_cylinder(struct dive *dive); extern int first_hidden_cylinder(const struct dive *d); @@ -100,45 +100,25 @@ extern int first_hidden_cylinder(const struct dive *d); extern void dump_cylinders(struct dive *dive, bool verbose); #endif -/* Weightsystem table functions */ -extern void clear_weightsystem_table(struct weightsystem_table *); -extern void add_to_weightsystem_table(struct weightsystem_table *, int idx, weightsystem_t ws); +struct ws_info { + std::string name; + weight_t weight; +}; +extern std::vector ws_info_table; +extern weight_t get_weightsystem_weight(const std::string &name); // returns 0 if not found +extern void add_weightsystem_description(const weightsystem_t &); -/* Cylinder table functions */ -extern void clear_cylinder_table(struct cylinder_table *); -extern void add_cylinder(struct cylinder_table *, int idx, cylinder_t cyl); - -void get_gas_string(struct gasmix gasmix, char *text, int len); -const char *gasname(struct gasmix gasmix); - -typedef struct tank_info { - const char *name; +struct tank_info { + std::string name; int cuft, ml, psi, bar; -} tank_info_t; - -struct tank_info_table { - int nr, allocated; - struct tank_info *infos; }; -extern struct tank_info_table tank_info_table; -extern void reset_tank_info_table(struct tank_info_table *table); -extern void clear_tank_info_table(struct tank_info_table *table); -extern void add_tank_info_metric(struct tank_info_table *table, const char *name, int ml, int bar); -extern void add_tank_info_imperial(struct tank_info_table *table, const char *name, int cuft, int psi); -extern void extract_tank_info(const struct tank_info *info, volume_t *size, pressure_t *working_pressure); -extern bool get_tank_info_data(struct tank_info_table *table, const char *name, volume_t *size, pressure_t *pressure); -extern void set_tank_info_data(struct tank_info_table *table, const char *name, volume_t size, pressure_t working_pressure); - -struct ws_info_t { - const char *name; - int grams; -}; -extern struct ws_info_t ws_info[MAX_WS_INFO]; -extern struct ws_info_t *get_weightsystem_description(const char *name); - -#ifdef __cplusplus -} -#endif +extern std::vector tank_info_table; +extern struct tank_info *get_tank_info(std::vector &table, const std::string &name); +extern void set_tank_info_data(std::vector &table, const std::string &name, volume_t size, pressure_t working_pressure); +extern std::pair extract_tank_info(const struct tank_info &info); +extern std::pair get_tank_info_data(const std::vector &table, const std::string &name); +extern void add_cylinder_description(const cylinder_type_t &); +extern void reset_tank_info_table(std::vector &table); #endif // EQUIPMENT_H diff --git a/core/errorhelper.cpp b/core/errorhelper.cpp index 5d9518b62..89b435f85 100644 --- a/core/errorhelper.cpp +++ b/core/errorhelper.cpp @@ -1,50 +1,44 @@ // SPDX-License-Identifier: GPL-2.0 -#ifdef __clang__ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#endif -#include #include "errorhelper.h" -#include "membuffer.h" +#include "format.h" + +#include #if !defined(Q_OS_ANDROID) && !defined(__ANDROID__) -#define LOG_MSG(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) +#define LOG_MSG(fmt, s) fprintf(stderr, fmt, s) #else #include -#define LOG_MSG(fmt, ...) __android_log_print(ANDROID_LOG_INFO, "Subsurface", fmt, ##__VA_ARGS__); +#define LOG_MSG(fmt, s) __android_log_print(ANDROID_LOG_INFO, "Subsurface", fmt, s); #endif -#define VA_BUF(b, fmt) do { va_list args; va_start(args, fmt); put_vformat(b, fmt, args); va_end(args); } while (0) - int verbose; void report_info(const char *fmt, ...) { - struct membufferpp buf; - - VA_BUF(&buf, fmt); - strip_mb(&buf); - LOG_MSG("INFO: %s\n", mb_cstring(&buf)); + va_list args; + va_start(args, fmt); + std::string s = vformat_string_std(fmt, args); + va_end(args); + LOG_MSG("INFO: %s\n", s.c_str()); } -static void (*error_cb)(char *) = NULL; +static void (*error_cb)(std::string) = NULL; int report_error(const char *fmt, ...) { - struct membufferpp buf; - - VA_BUF(&buf, fmt); - strip_mb(&buf); - LOG_MSG("ERROR: %s\n", mb_cstring(&buf)); + va_list args; + va_start(args, fmt); + std::string s = vformat_string_std(fmt, args); + va_end(args); + LOG_MSG("ERROR: %s\n", s.c_str()); /* if there is no error callback registered, don't produce errors */ - if (!error_cb) - return -1; - error_cb(detach_cstring(&buf)); + if (error_cb) + error_cb(std::move(s)); return -1; } -void set_error_cb(void(*cb)(char *)) +void set_error_cb(void(*cb)(std::string)) { error_cb = cb; } diff --git a/core/errorhelper.h b/core/errorhelper.h index d81ea8a96..f6fbb9ff3 100644 --- a/core/errorhelper.h +++ b/core/errorhelper.h @@ -4,9 +4,7 @@ // error reporting functions -#ifdef __cplusplus -extern "C" { -#endif +#include #ifdef __GNUC__ #define __printf(x, y) __attribute__((__format__(__printf__, x, y))) @@ -17,11 +15,6 @@ extern "C" { extern int verbose; extern int __printf(1, 2) report_error(const char *fmt, ...); extern void __printf(1, 2) report_info(const char *fmt, ...); -extern void set_error_cb(void(*cb)(char *)); // Callback takes ownership of passed string - - -#ifdef __cplusplus -} -#endif +extern void set_error_cb(void(*cb)(std::string s)); // Callback takes ownership of passed string #endif diff --git a/core/event.c b/core/event.c deleted file mode 100644 index dd5551761..000000000 --- a/core/event.c +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "event.h" -#include "eventtype.h" -#include "subsurface-string.h" - -#include -#include - -int event_is_gaschange(const struct event *ev) -{ - return ev->type == SAMPLE_EVENT_GASCHANGE || - ev->type == SAMPLE_EVENT_GASCHANGE2; -} - -bool event_is_divemodechange(const struct event *ev) -{ - return same_string(ev->name, "modechange"); -} - -struct event *clone_event(const struct event *src_ev) -{ - struct event *ev; - if (!src_ev) - return NULL; - - size_t size = sizeof(*src_ev) + strlen(src_ev->name) + 1; - ev = (struct event*) malloc(size); - if (!ev) - exit(1); - memcpy(ev, src_ev, size); - ev->next = NULL; - - return ev; -} - -void free_events(struct event *ev) -{ - while (ev) { - struct event *next = ev->next; - free(ev); - ev = next; - } -} - -struct event *create_event(unsigned int time, int type, int flags, int value, const char *name) -{ - int gas_index = -1; - struct event *ev; - unsigned int size, len = strlen(name); - - size = sizeof(*ev) + len + 1; - ev = malloc(size); - if (!ev) - return NULL; - memset(ev, 0, size); - memcpy(ev->name, name, len); - ev->time.seconds = time; - ev->type = type; - ev->flags = flags; - ev->value = value; - - /* - * Expand the events into a sane format. Currently - * just gas switches - */ - switch (type) { - case SAMPLE_EVENT_GASCHANGE2: - /* High 16 bits are He percentage */ - ev->gas.mix.he.permille = (value >> 16) * 10; - - /* Extension to the GASCHANGE2 format: cylinder index in 'flags' */ - /* TODO: verify that gas_index < num_cylinders. */ - if (flags > 0) - gas_index = flags-1; - /* Fallthrough */ - case SAMPLE_EVENT_GASCHANGE: - /* Low 16 bits are O2 percentage */ - ev->gas.mix.o2.permille = (value & 0xffff) * 10; - ev->gas.index = gas_index; - break; - } - - remember_event_type(ev); - - return ev; -} - -struct event *clone_event_rename(const struct event *ev, const char *name) -{ - return create_event(ev->time.seconds, ev->type, ev->flags, ev->value, name); -} - -bool same_event(const struct event *a, const struct event *b) -{ - if (a->time.seconds != b->time.seconds) - return 0; - if (a->type != b->type) - return 0; - if (a->flags != b->flags) - return 0; - if (a->value != b->value) - return 0; - return !strcmp(a->name, b->name); -} - -extern enum event_severity get_event_severity(const struct event *ev) -{ - switch (ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) { - case SAMPLE_FLAGS_SEVERITY_INFO: - return EVENT_SEVERITY_INFO; - case SAMPLE_FLAGS_SEVERITY_WARN: - return EVENT_SEVERITY_WARN; - case SAMPLE_FLAGS_SEVERITY_ALARM: - return EVENT_SEVERITY_ALARM; - default: - return EVENT_SEVERITY_NONE; - } -} diff --git a/core/event.cpp b/core/event.cpp new file mode 100644 index 000000000..329daf009 --- /dev/null +++ b/core/event.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "event.h" +#include "divecomputer.h" +#include "eventtype.h" +#include "subsurface-string.h" + +#include + +event::event() : type(SAMPLE_EVENT_NONE), flags(0), value(0), + divemode(OC), hidden(false) +{ + /* That overwrites divemode. Is this a smart thing to do? */ + gas.index = -1; + gas.mix = gasmix_air; +} + +event::event(unsigned int time, int type, int flags, int value, const std::string &name) : + type(type), flags(flags), value(value), divemode(OC), + hidden(false), name(name) +{ + int gas_index = -1; + fraction_t he; + this->time.seconds = time; + + /* + * Expand the events into a sane format. Currently + * just gas switches + */ + switch (type) { + case SAMPLE_EVENT_GASCHANGE2: + /* High 16 bits are He percentage */ + he.permille = (value >> 16) * 10; + + /* Extension to the GASCHANGE2 format: cylinder index in 'flags' */ + /* TODO: verify that gas_index < num_cylinders. */ + if (flags > 0) + gas_index = flags-1; + /* Fallthrough */ + case SAMPLE_EVENT_GASCHANGE: + /* Low 16 bits are O2 percentage */ + gas.mix.he = he; + gas.mix.o2.permille = (value & 0xffff) * 10; + gas.index = gas_index; + break; + } + + remember_event_type(this); +} + +event::~event() +{ +} + +bool event::is_gaschange() const +{ + return type == SAMPLE_EVENT_GASCHANGE || type == SAMPLE_EVENT_GASCHANGE2; +} + +bool event::is_divemodechange() const +{ + return name == "modechange"; +} + +bool event::operator==(const event &b) const +{ + return std::tie(time.seconds, type, flags, value, name) == + std::tie(b.time.seconds, b.type, b.flags, b.value, b.name); +} + +enum event_severity event::get_severity() const +{ + switch (flags & SAMPLE_FLAGS_SEVERITY_MASK) { + case SAMPLE_FLAGS_SEVERITY_INFO: + return EVENT_SEVERITY_INFO; + case SAMPLE_FLAGS_SEVERITY_WARN: + return EVENT_SEVERITY_WARN; + case SAMPLE_FLAGS_SEVERITY_ALARM: + return EVENT_SEVERITY_ALARM; + default: + return EVENT_SEVERITY_NONE; + } +} + +event_loop::event_loop(const char *name, const struct divecomputer &dc) : name(name), idx(0), dc(dc) +{ +} + +const struct event *event_loop::next() +{ + if (name.empty()) + return nullptr; + while (idx < dc.events.size()) { + const struct event &ev = dc.events[idx++]; + if (ev.name == name) + return &ev; + } + return nullptr; +} + +struct event *get_first_event(struct divecomputer &dc, const std::string &name) +{ + auto it = std::find_if(dc.events.begin(), dc.events.end(), [name](auto &ev) { return ev.name == name; }); + return it != dc.events.end() ? &*it : nullptr; +} + +const struct event *get_first_event(const struct divecomputer &dc, const std::string &name) +{ + return get_first_event(const_cast(dc), name); +} diff --git a/core/event.h b/core/event.h index fc8a03d64..d96df5733 100644 --- a/core/event.h +++ b/core/event.h @@ -6,11 +6,10 @@ #include "gas.h" #include "units.h" +#include #include -#ifdef __cplusplus -extern "C" { -#endif +struct divecomputer; enum event_severity { EVENT_SEVERITY_NONE = 0, @@ -23,8 +22,8 @@ enum event_severity { * Events are currently based straight on what libdivecomputer gives us. * We need to wrap these into our own events at some point to remove some of the limitations. */ + struct event { - struct event *next; duration_t time; int type; /* This is the annoying libdivecomputer format. */ @@ -42,26 +41,67 @@ struct event { struct gasmix mix; } gas; }; - bool deleted; // used internally in the parser and in fixup_dive(). bool hidden; - char name[]; + std::string name; + event(); + event(unsigned int time, int type, int flags, int value, const std::string &name); + ~event(); + + bool operator==(const event &b2) const; + + bool is_gaschange() const; + bool is_divemodechange() const; + event_severity get_severity() const; }; -extern int event_is_gaschange(const struct event *ev); -extern bool event_is_divemodechange(const struct event *ev); -extern struct event *clone_event(const struct event *src_ev); -extern void free_events(struct event *ev); -extern struct event *create_event(unsigned int time, int type, int flags, int value, const char *name); -extern struct event *clone_event_rename(const struct event *ev, const char *name); -extern bool same_event(const struct event *a, const struct event *b); -extern enum event_severity get_event_severity(const struct event *ev); +class event_loop +{ + std::string name; + size_t idx; + const struct divecomputer &dc; +public: + event_loop(const char *name, const struct divecomputer &dc); + const struct event *next(); // nullptr -> end +}; -/* Since C doesn't have parameter-based overloading, two versions of get_next_event. */ -extern const struct event *get_next_event(const struct event *event, const char *name); -extern struct event *get_next_event_mutable(struct event *event, const char *name); +/* Get gasmixes at increasing timestamps. */ +class gasmix_loop { + const struct dive &dive; + const struct divecomputer &dc; + bool first_run; + event_loop loop; + const struct event *next_event; + int last_cylinder_index; + int last_time; +public: + gasmix_loop(const struct dive &dive, const struct divecomputer &dc); + // Return the next cylinder index / gasmix from the list of gas switches + // and the time in seconds when this gas switch happened + // (including the potentially imaginary first gas switch to cylinder 0 / air) + std::pair next_cylinder_index(); // -1 -> end + std::pair next(); // gasmix_invalid -> end -#ifdef __cplusplus -} -#endif + // Return the cylinder index / gasmix at a given time during the dive + // and the time in seconds when this switch to this gas happened + // (including the potentially imaginary first gas switch to cylinder 0 / air) + std::pair cylinder_index_at(int time); // -1 -> end + std::pair at(int time); // gasmix_invalid -> end + + bool has_next() const; +}; + +/* Get divemodes at increasing timestamps. */ +class divemode_loop { + divemode_t last; + event_loop loop; + const struct event *ev; +public: + divemode_loop(const struct divecomputer &dc); + // Return the divemode at a given time during the dive + divemode_t at(int time); +}; + +extern const struct event *get_first_event(const struct divecomputer &dc, const std::string &name); +extern struct event *get_first_event(struct divecomputer &dc, const std::string &name); #endif diff --git a/core/eventtype.cpp b/core/eventtype.cpp index 5e88480a1..4352707af 100644 --- a/core/eventtype.cpp +++ b/core/eventtype.cpp @@ -14,7 +14,7 @@ struct event_type { bool plot; event_type(const struct event *ev) : name(ev->name), - severity(get_event_severity(ev)), + severity(ev->get_severity()), plot(true) { } @@ -27,14 +27,14 @@ static bool operator==(const event_type &en1, const event_type &en2) return en1.name == en2.name && en1.severity == en2.severity; } -extern "C" void clear_event_types() +void clear_event_types() { event_types.clear(); } -extern "C" void remember_event_type(const struct event *ev) +void remember_event_type(const struct event *ev) { - if (empty_string(ev->name)) + if (ev->name.empty()) return; event_type type(ev); if (std::find(event_types.begin(), event_types.end(), type) != event_types.end()) @@ -42,33 +42,33 @@ extern "C" void remember_event_type(const struct event *ev) event_types.push_back(std::move(type)); } -extern "C" bool is_event_type_hidden(const struct event *ev) +bool is_event_type_hidden(const struct event *ev) { auto it = std::find(event_types.begin(), event_types.end(), ev); return it != event_types.end() && !it->plot; } -extern "C" void hide_event_type(const struct event *ev) +void hide_event_type(const struct event *ev) { auto it = std::find(event_types.begin(), event_types.end(), ev); if (it != event_types.end()) it->plot = false; } -extern "C" void show_all_event_types() +void show_all_event_types() { for (event_type &e: event_types) e.plot = true; } -extern "C" void show_event_type(int idx) +void show_event_type(int idx) { if (idx < 0 || idx >= (int)event_types.size()) return; event_types[idx].plot = true; } -extern "C" bool any_event_types_hidden() +bool any_event_types_hidden() { return std::any_of(event_types.begin(), event_types.end(), [] (const event_type &e) { return !e.plot; }); @@ -102,13 +102,13 @@ static QString event_type_name(QString name, event_severity severity) return QStringLiteral("%1 (%2)").arg(name, severity_name); } -QString event_type_name(const event *ev) +QString event_type_name(const event &ev) { - if (!ev || empty_string(ev->name)) + if (ev.name.empty()) return QString(); - QString name = QString::fromUtf8(ev->name); - return event_type_name(std::move(name), get_event_severity(ev)); + QString name = QString::fromStdString(ev.name); + return event_type_name(std::move(name), ev.get_severity()); } QString event_type_name(int idx) diff --git a/core/eventtype.h b/core/eventtype.h index 8f5a8f5ac..6b9f843d3 100644 --- a/core/eventtype.h +++ b/core/eventtype.h @@ -3,29 +3,18 @@ #ifndef EVENTNAME_H #define EVENTNAME_H -#ifdef __cplusplus -extern "C" { -#endif +#include +#include -extern void clear_event_types(void); +extern void clear_event_types(); extern void remember_event_type(const struct event *ev); extern bool is_event_type_hidden(const struct event *ev); extern void hide_event_type(const struct event *ev); extern void show_all_event_types(); extern void show_event_type(int idx); extern bool any_event_types_hidden(); - -#ifdef __cplusplus -} - -// C++-only functions - -#include -#include extern std::vector hidden_event_types(); -QString event_type_name(const event *ev); -QString event_type_name(int idx); - -#endif +extern QString event_type_name(const event &ev); +extern QString event_type_name(int idx); #endif diff --git a/core/extradata.h b/core/extradata.h index 4f9a72f20..d81ad17ad 100644 --- a/core/extradata.h +++ b/core/extradata.h @@ -2,10 +2,11 @@ #ifndef EXTRADATA_H #define EXTRADATA_H +#include + struct extra_data { - const char *key; - const char *value; - struct extra_data *next; + std::string key; + std::string value; }; #endif diff --git a/core/file.cpp b/core/file.cpp index dd9f15b8b..36d47b1c3 100644 --- a/core/file.cpp +++ b/core/file.cpp @@ -1,5 +1,4 @@ // SPDX-License-Identifier: GPL-2.0 -#include "ssrf.h" #include #include #include @@ -78,7 +77,7 @@ static void zip_read(struct zip_file *file, const char *filename, struct divelog (void) parse_xml_buffer(filename, mem.data(), read, log, NULL); } -extern "C" int try_to_open_zip(const char *filename, struct divelog *log) +int try_to_open_zip(const char *filename, struct divelog *log) { int success = 0; /* Grr. libzip needs to re-open the file, it can't take a buffer */ @@ -268,7 +267,7 @@ bool remote_repo_uptodate(const char *filename, struct git_info *info) return false; } -extern "C" int parse_file(const char *filename, struct divelog *log) +int parse_file(const char *filename, struct divelog *log) { struct git_info info; const char *fmt; @@ -290,7 +289,7 @@ extern "C" int parse_file(const char *filename, struct divelog *log) auto [mem, err] = readfile(filename); if (err < 0) { /* we don't want to display an error if this was the default file */ - if (same_string(filename, prefs.default_filename)) + if (filename == prefs.default_filename) return 0; return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); diff --git a/core/file.h b/core/file.h index 3303f224f..054fd384b 100644 --- a/core/file.h +++ b/core/file.h @@ -6,13 +6,12 @@ #include #include +#include +#include struct divelog; struct zip; -#ifdef __cplusplus -extern "C" { -#endif extern void ostctools_import(const char *file, struct divelog *log); extern int parse_file(const char *filename, struct divelog *log); @@ -28,22 +27,9 @@ extern int subsurface_access(const char *path, int mode); extern int subsurface_stat(const char *path, struct stat *buf); extern struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp); extern int subsurface_zip_close(struct zip *zip); - -#ifdef __cplusplus - -} - -// C++ only functions - -#include -#include - -// return data, errorcode pair. -extern std::pair readfile(const char *filename); +extern std::pair readfile(const char *filename); // return data, errorcode pair. extern int try_to_open_cochran(const char *filename, std::string &mem, struct divelog *log); extern int try_to_open_liquivision(const char *filename, std::string &mem, struct divelog *log); extern int datatrak_import(std::string &mem, std::string &wl_mem, struct divelog *log); -#endif - #endif // FILE_H diff --git a/core/filterconstraint.cpp b/core/filterconstraint.cpp index 654377c8a..e7f69b316 100644 --- a/core/filterconstraint.cpp +++ b/core/filterconstraint.cpp @@ -134,87 +134,87 @@ static const range_mode_description *get_range_mode_description(enum filter_cons return nullptr; } -static enum filter_constraint_type filter_constraint_type_from_string(const char *s) +static enum filter_constraint_type filter_constraint_type_from_string(const std::string &s) { for (const auto &desc: type_descriptions) { - if (same_string(desc.token, s)) + if (desc.token == s) return desc.type; } - report_error("unknown filter constraint type: %s", s); + report_error("unknown filter constraint type: %s", s.c_str()); return FILTER_CONSTRAINT_DATE; } -static enum filter_constraint_string_mode filter_constraint_string_mode_from_string(const char *s) +static enum filter_constraint_string_mode filter_constraint_string_mode_from_string(const std::string &s) { for (const auto &desc: string_mode_descriptions) { - if (same_string(desc.token, s)) + if (desc.token == s) return desc.mode; } - report_error("unknown filter constraint string mode: %s", s); + report_error("unknown filter constraint string mode: %s", s.c_str()); return FILTER_CONSTRAINT_EXACT; } -static enum filter_constraint_range_mode filter_constraint_range_mode_from_string(const char *s) +static enum filter_constraint_range_mode filter_constraint_range_mode_from_string(const std::string &s) { for (const auto &desc: range_mode_descriptions) { - if (same_string(desc.token, s)) + if (desc.token == s) return desc.mode; } - report_error("unknown filter constraint range mode: %s", s); + report_error("unknown filter constraint range mode: %s", s.c_str()); return FILTER_CONSTRAINT_EQUAL; } -extern "C" const char *filter_constraint_type_to_string(enum filter_constraint_type type) +const char *filter_constraint_type_to_string(enum filter_constraint_type type) { const type_description *desc = get_type_description(type); return desc ? desc->token : "unknown"; } -extern "C" const char *filter_constraint_string_mode_to_string(enum filter_constraint_string_mode mode) +const char *filter_constraint_string_mode_to_string(enum filter_constraint_string_mode mode) { const string_mode_description *desc = get_string_mode_description(mode); return desc ? desc->token : "unknown"; } -extern "C" const char *filter_constraint_range_mode_to_string(enum filter_constraint_range_mode mode) +const char *filter_constraint_range_mode_to_string(enum filter_constraint_range_mode mode) { const range_mode_description *desc = get_range_mode_description(mode); return desc ? desc->token : "unknown"; } -extern "C" int filter_constraint_type_to_index(enum filter_constraint_type type) +int filter_constraint_type_to_index(enum filter_constraint_type type) { const type_description *desc = get_type_description(type); return desc ? desc - type_descriptions : -1; } -extern "C" int filter_constraint_string_mode_to_index(enum filter_constraint_string_mode mode) +int filter_constraint_string_mode_to_index(enum filter_constraint_string_mode mode) { const string_mode_description *desc = get_string_mode_description(mode); return desc ? desc - string_mode_descriptions : -1; } -extern "C" int filter_constraint_range_mode_to_index(enum filter_constraint_range_mode mode) +int filter_constraint_range_mode_to_index(enum filter_constraint_range_mode mode) { const range_mode_description *desc = get_range_mode_description(mode); return desc ? desc - range_mode_descriptions : -1; } -extern "C" enum filter_constraint_type filter_constraint_type_from_index(int index) +enum filter_constraint_type filter_constraint_type_from_index(int index) { if (index >= 0 && index < (int)std::size(type_descriptions)) return type_descriptions[index].type; return (enum filter_constraint_type)-1; } -extern "C" enum filter_constraint_string_mode filter_constraint_string_mode_from_index(int index) +enum filter_constraint_string_mode filter_constraint_string_mode_from_index(int index) { if (index >= 0 && index < (int)std::size(string_mode_descriptions)) return string_mode_descriptions[index].mode; return (enum filter_constraint_string_mode)-1; } -extern "C" enum filter_constraint_range_mode filter_constraint_range_mode_from_index(int index) +enum filter_constraint_range_mode filter_constraint_range_mode_from_index(int index) { if (index >= 0 && index < (int)std::size(range_mode_descriptions)) return range_mode_descriptions[index].mode; @@ -283,7 +283,7 @@ static int display_to_base_unit(double f, enum filter_constraint_type type) switch (desc->units) { case FILTER_CONSTRAINT_NO_UNIT: default: - return (int)lrint(f); + return int_cast(f); case FILTER_CONSTRAINT_LENGTH_UNIT: return prefs.units.length == units::METERS ? lrint(f * 1000.0) : feet_to_mm(f); case FILTER_CONSTRAINT_DURATION_UNIT: @@ -390,7 +390,7 @@ QStringList filter_contraint_multiple_choice_translated(enum filter_constraint_t return QStringList(); } -extern "C" bool filter_constraint_is_string(filter_constraint_type type) +bool filter_constraint_is_string(filter_constraint_type type) { // Currently a constraint is filter based if and only if it has a string // mode (i.e. starts with, substring, exact). In the future we might also @@ -398,7 +398,7 @@ extern "C" bool filter_constraint_is_string(filter_constraint_type type) return filter_constraint_has_string_mode(type); } -extern "C" bool filter_constraint_is_timestamp(filter_constraint_type type) +bool filter_constraint_is_timestamp(filter_constraint_type type) { return type == FILTER_CONSTRAINT_DATE || type == FILTER_CONSTRAINT_DATE_TIME; } @@ -408,37 +408,37 @@ static bool is_numerical_constraint(filter_constraint_type type) return !filter_constraint_is_string(type) && !filter_constraint_is_timestamp(type); } -extern "C" bool filter_constraint_has_string_mode(enum filter_constraint_type type) +bool filter_constraint_has_string_mode(enum filter_constraint_type type) { const type_description *desc = get_type_description(type); return desc && desc->has_string_mode; } -extern "C" bool filter_constraint_has_range_mode(enum filter_constraint_type type) +bool filter_constraint_has_range_mode(enum filter_constraint_type type) { const type_description *desc = get_type_description(type); return desc && desc->has_range_mode; } -extern "C" bool filter_constraint_is_star(filter_constraint_type type) +bool filter_constraint_is_star(filter_constraint_type type) { const type_description *desc = get_type_description(type); return desc && desc->is_star_widget; } -extern "C" bool filter_constraint_has_date_widget(filter_constraint_type type) +bool filter_constraint_has_date_widget(filter_constraint_type type) { const type_description *desc = get_type_description(type); return desc && desc->has_date; } -extern "C" bool filter_constraint_has_time_widget(filter_constraint_type type) +bool filter_constraint_has_time_widget(filter_constraint_type type) { const type_description *desc = get_type_description(type); return desc && desc->has_time; } -extern "C" int filter_constraint_num_decimals(enum filter_constraint_type type) +int filter_constraint_num_decimals(enum filter_constraint_type type) { const type_description *desc = get_type_description(type); return desc ? desc->decimal_places : 1; @@ -446,7 +446,7 @@ extern "C" int filter_constraint_num_decimals(enum filter_constraint_type type) // String constraints are valid if there is at least one term. // Other constraints are always valid. -extern "C" bool filter_constraint_is_valid(const struct filter_constraint *constraint) +bool filter_constraint_is_valid(const struct filter_constraint *constraint) { if (!filter_constraint_is_string(constraint->type)) return true; @@ -551,14 +551,14 @@ filter_constraint::filter_constraint(const filter_constraint &c) : data.numerical_range = c.data.numerical_range; } -filter_constraint::filter_constraint(const char *type_in, const char *string_mode_in, - const char *range_mode_in, bool negate_in, const char *s_in) : +filter_constraint::filter_constraint(const std::string &type_in, const std::string &string_mode_in, + const std::string &range_mode_in, bool negate_in, const std::string &s_in) : type(filter_constraint_type_from_string(type_in)), string_mode(FILTER_CONSTRAINT_STARTS_WITH), range_mode(FILTER_CONSTRAINT_GREATER), negate(negate_in) { - QString s(s_in); + QString s = QString::fromStdString(s_in); if (filter_constraint_has_string_mode(type)) string_mode = filter_constraint_string_mode_from_string(string_mode_in); if (filter_constraint_has_range_mode(type)) @@ -622,22 +622,22 @@ filter_constraint::~filter_constraint() delete data.string_list; } -std::string filter_constraint_data_to_string(const filter_constraint *c) +std::string filter_constraint_data_to_string(const filter_constraint &c) { - if (filter_constraint_is_timestamp(c->type)) { - std::string from_s = format_datetime(c->data.timestamp_range.from); - std::string to_s = format_datetime(c->data.timestamp_range.to); + if (filter_constraint_is_timestamp(c.type)) { + std::string from_s = format_datetime(c.data.timestamp_range.from); + std::string to_s = format_datetime(c.data.timestamp_range.to); return from_s + ',' + to_s; - } else if (filter_constraint_is_string(c->type)) { + } else if (filter_constraint_is_string(c.type)) { // TODO: this obviously breaks if the strings contain ",". // That is currently not supported by the UI, but one day we might // have to escape the strings. - return c->data.string_list->join(",").toStdString(); - } else if (filter_constraint_is_multiple_choice(c->type)) { - return std::to_string(c->data.multiple_choice); + return c.data.string_list->join(",").toStdString(); + } else if (filter_constraint_is_multiple_choice(c.type)) { + return std::to_string(c.data.multiple_choice); } else { - return std::to_string(c->data.numerical_range.from) + ',' + - std::to_string(c->data.numerical_range.to); + return std::to_string(c.data.numerical_range.from) + ',' + + std::to_string(c.data.numerical_range.to); } } @@ -818,17 +818,17 @@ static bool check(const filter_constraint &c, const QStringList &list) static bool has_tags(const filter_constraint &c, const struct dive *d) { QStringList dive_tags; - for (const tag_entry *tag = d->tag_list; tag; tag = tag->next) - dive_tags.push_back(QString::fromStdString(tag->tag->name).trimmed()); + for (const divetag *tag: d->tags) + dive_tags.push_back(QString::fromStdString(tag->name).trimmed()); return check(c, dive_tags); } static bool has_people(const filter_constraint &c, const struct dive *d) { QStringList dive_people; - for (const QString &s: QString(d->buddy).split(",", SKIP_EMPTY)) + for (const QString &s: QString::fromStdString(d->buddy).split(",", SKIP_EMPTY)) dive_people.push_back(s.trimmed()); - for (const QString &s: QString(d->diveguide).split(",", SKIP_EMPTY)) + for (const QString &s: QString::fromStdString(d->diveguide).split(",", SKIP_EMPTY)) dive_people.push_back(s.trimmed()); return check(c, dive_people); } @@ -837,10 +837,10 @@ static bool has_locations(const filter_constraint &c, const struct dive *d) { QStringList diveLocations; if (d->divetrip) - diveLocations.push_back(QString(d->divetrip->location).trimmed()); + diveLocations.push_back(QString::fromStdString(d->divetrip->location).trimmed()); if (d->dive_site) - diveLocations.push_back(QString(d->dive_site->name).trimmed()); + diveLocations.push_back(QString::fromStdString(d->dive_site->name).trimmed()); return check(c, diveLocations); } @@ -848,8 +848,8 @@ static bool has_locations(const filter_constraint &c, const struct dive *d) static bool has_weight_type(const filter_constraint &c, const struct dive *d) { QStringList weightsystemTypes; - for (int i = 0; i < d->weightsystems.nr; ++i) - weightsystemTypes.push_back(d->weightsystems.weightsystems[i].description); + for (auto &ws: d->weightsystems) + weightsystemTypes.push_back(QString::fromStdString(ws.description)); return check(c, weightsystemTypes); } @@ -857,8 +857,8 @@ static bool has_weight_type(const filter_constraint &c, const struct dive *d) static bool has_cylinder_type(const filter_constraint &c, const struct dive *d) { QStringList cylinderTypes; - for (int i = 0; i < d->cylinders.nr; ++i) - cylinderTypes.push_back(d->cylinders.cylinders[i].type.description); + for (const cylinder_t &cyl: d->cylinders) + cylinderTypes.push_back(QString::fromStdString(cyl.type.description)); return check(c, cylinderTypes); } @@ -866,16 +866,16 @@ static bool has_cylinder_type(const filter_constraint &c, const struct dive *d) static bool has_suits(const filter_constraint &c, const struct dive *d) { QStringList diveSuits; - if (d->suit) - diveSuits.push_back(QString(d->suit)); + if (!d->suit.empty()) + diveSuits.push_back(QString::fromStdString(d->suit)); return check(c, diveSuits); } static bool has_notes(const filter_constraint &c, const struct dive *d) { QStringList diveNotes; - if (d->notes) - diveNotes.push_back(QString(d->notes)); + if (!d->notes.empty()) + diveNotes.push_back(QString::fromStdString(d->notes)); return check(c, diveNotes); } @@ -904,22 +904,15 @@ static bool check_numerical_range_non_zero(const filter_constraint &c, int v) static bool check_cylinder_size(const filter_constraint &c, const struct dive *d) { - for (int i = 0; i < d->cylinders.nr; ++i) { - const cylinder_t &cyl = d->cylinders.cylinders[i]; - if (cyl.type.size.mliter && check_numerical_range(c, cyl.type.size.mliter)) - return true; - } - return false; + return std::any_of(d->cylinders.begin(), d->cylinders.end(), [&c](auto &cyl) + { return cyl.type.size.mliter && + check_numerical_range(c, cyl.type.size.mliter); }); } static bool check_gas_range(const filter_constraint &c, const struct dive *d, gas_component component) { - for (int i = 0; i < d->cylinders.nr; ++i) { - const cylinder_t &cyl = d->cylinders.cylinders[i]; - if (check_numerical_range(c, get_gas_component_fraction(cyl.gasmix, component).permille)) - return true; - } - return false; + return std::any_of(d->cylinders.begin(), d->cylinders.end(), [&c, &component](auto &cyl) + { return check_numerical_range(c, get_gas_component_fraction(cyl.gasmix, component).permille); }); } static long days_since_epoch(timestamp_t timestamp) @@ -956,14 +949,14 @@ static bool check_datetime_range(const filter_constraint &c, const struct dive * case FILTER_CONSTRAINT_EQUAL: // Exact mode is a bit strange for timestamps. Therefore we return any dive // where the given timestamp is during that dive. - return time_during_dive_with_offset(d, c.data.timestamp_range.from, 0) != c.negate; + return d->time_during_dive_with_offset(c.data.timestamp_range.from, 0) != c.negate; case FILTER_CONSTRAINT_LESS: - return (dive_endtime(d) <= c.data.timestamp_range.to) != c.negate; + return (d->endtime() <= c.data.timestamp_range.to) != c.negate; case FILTER_CONSTRAINT_GREATER: return (d->when >= c.data.timestamp_range.from) != c.negate; case FILTER_CONSTRAINT_RANGE: return (d->when >= c.data.timestamp_range.from && - dive_endtime(d) <= c.data.timestamp_range.to) != c.negate; + d->endtime() <= c.data.timestamp_range.to) != c.negate; } return false; } @@ -978,14 +971,14 @@ static bool check_time_of_day_internal(const dive *d, enum filter_constraint_ran // where the given timestamp is during that dive. Note: this will fail for dives // that run past midnight. We might want to special case that. return (seconds_since_midnight(d->when) <= from && - seconds_since_midnight(dive_endtime(d)) >= from) != negate; + seconds_since_midnight(d->endtime()) >= from) != negate; case FILTER_CONSTRAINT_LESS: - return (seconds_since_midnight(dive_endtime(d)) <= to) != negate; + return (seconds_since_midnight(d->endtime()) <= to) != negate; case FILTER_CONSTRAINT_GREATER: return (seconds_since_midnight(d->when) >= from) != negate; case FILTER_CONSTRAINT_RANGE: return (seconds_since_midnight(d->when) >= from && - seconds_since_midnight(dive_endtime(d)) <= to) != negate; + seconds_since_midnight(d->endtime()) <= to) != negate; } return false; } @@ -1063,7 +1056,7 @@ bool filter_constraint_match_dive(const filter_constraint &c, const struct dive case FILTER_CONSTRAINT_DURATION: return check_numerical_range(c, d->duration.seconds); case FILTER_CONSTRAINT_WEIGHT: - return check_numerical_range(c, total_weight(d)); + return check_numerical_range(c, d->total_weight().grams); case FILTER_CONSTRAINT_WATER_TEMP: return check_numerical_range(c, d->watertemp.mkelvin); case FILTER_CONSTRAINT_AIR_TEMP: @@ -1073,11 +1066,11 @@ bool filter_constraint_match_dive(const filter_constraint &c, const struct dive case FILTER_CONSTRAINT_SAC: return check_numerical_range_non_zero(c, d->sac); case FILTER_CONSTRAINT_LOGGED: - return is_logged(d) != c.negate; + return d->is_logged() != c.negate; case FILTER_CONSTRAINT_PLANNED: - return is_planned(d) != c.negate; + return d->is_planned() != c.negate; case FILTER_CONSTRAINT_DIVE_MODE: - return check_multiple_choice(c, (int)d->dc.divemode); // should we be smarter and check all DCs? + return check_multiple_choice(c, (int)d->dcs[0].divemode); // should we be smarter and check all DCs? case FILTER_CONSTRAINT_TAGS: return has_tags(c, d); case FILTER_CONSTRAINT_PEOPLE: diff --git a/core/filterconstraint.h b/core/filterconstraint.h index b906a8e03..53c7f6287 100644 --- a/core/filterconstraint.h +++ b/core/filterconstraint.h @@ -5,16 +5,10 @@ #define FILTER_CONSTRAINT_H #include "units.h" +#include struct dive; -#ifdef __cplusplus -#include -extern "C" { -#else -typedef void QStringList; -#endif - enum filter_constraint_type { FILTER_CONSTRAINT_DATE, FILTER_CONSTRAINT_DATE_TIME, @@ -82,16 +76,14 @@ struct filter_constraint { QStringList *string_list; uint64_t multiple_choice; // bit-field for multiple choice lists. currently, we support 64 items, extend if needed. } data; -#ifdef __cplusplus // For C++, define constructors, assignment operators and destructor to make our lives easier. filter_constraint(filter_constraint_type type); - filter_constraint(const char *type, const char *string_mode, - const char *range_mode, bool negate, const char *data); // from parser data + filter_constraint(const std::string &type, const std::string &string_mode, + const std::string &range_mode, bool negate, const std::string &data); // from parser data filter_constraint(const filter_constraint &); filter_constraint &operator=(const filter_constraint &); ~filter_constraint(); bool operator==(const filter_constraint &f2) const; -#endif }; extern const char *filter_constraint_type_to_string(enum filter_constraint_type); @@ -117,12 +109,6 @@ extern bool filter_constraint_has_time_widget(enum filter_constraint_type); extern int filter_constraint_num_decimals(enum filter_constraint_type); extern bool filter_constraint_is_valid(const struct filter_constraint *constraint); -#ifdef __cplusplus -} -#endif - -// C++ only functions -#ifdef __cplusplus QString filter_constraint_type_to_string_translated(enum filter_constraint_type); QString filter_constraint_negate_to_string_translated(bool negate); QString filter_constraint_string_mode_to_string_translated(enum filter_constraint_string_mode); @@ -151,8 +137,6 @@ void filter_constraint_set_timestamp_from(filter_constraint &c, timestamp_t from void filter_constraint_set_timestamp_to(filter_constraint &c, timestamp_t to); // convert according to current units (metric or imperial) void filter_constraint_set_multiple_choice(filter_constraint &c, uint64_t); bool filter_constraint_match_dive(const filter_constraint &c, const struct dive *d); -std::string filter_constraint_data_to_string(const struct filter_constraint *constraint); // caller takes ownership of returned string - -#endif +std::string filter_constraint_data_to_string(const struct filter_constraint &constraint); // caller takes ownership of returned string #endif diff --git a/core/filterpreset.cpp b/core/filterpreset.cpp index fa07f5879..60f9ca8a7 100644 --- a/core/filterpreset.cpp +++ b/core/filterpreset.cpp @@ -4,24 +4,14 @@ #include "qthelper.h" #include "subsurface-string.h" -static filter_preset_table &global_table() +std::string filter_preset::fulltext_query() const { - return *divelog.filter_presets; + return data.fullText.originalQuery.toStdString(); } -extern "C" int filter_presets_count(void) +const char *filter_preset::fulltext_mode() const { - return (int)global_table().size(); -} - -extern std::string filter_preset_fulltext_query(int preset) -{ - return global_table()[preset].data.fullText.originalQuery.toStdString(); -} - -extern "C" const char *filter_preset_fulltext_mode(int preset) -{ - switch (global_table()[preset].data.fulltextStringMode) { + switch (data.fulltextStringMode) { default: case StringFilterMode::SUBSTRING: return "substring"; @@ -32,98 +22,19 @@ extern "C" const char *filter_preset_fulltext_mode(int preset) } } -extern "C" void filter_preset_set_fulltext(struct filter_preset *preset, const char *fulltext, const char *fulltext_string_mode) +void filter_preset::set_fulltext(const std::string fulltext, const std::string &fulltext_string_mode) { - if (same_string(fulltext_string_mode, "substring")) - preset->data.fulltextStringMode = StringFilterMode::SUBSTRING; - else if (same_string(fulltext_string_mode, "startswith")) - preset->data.fulltextStringMode = StringFilterMode::STARTSWITH; - else // if (same_string(fulltext_string_mode, "exact")) - preset->data.fulltextStringMode = StringFilterMode::EXACT; - preset->data.fullText = fulltext; + if (fulltext_string_mode == "substring") + data.fulltextStringMode = StringFilterMode::SUBSTRING; + else if (fulltext_string_mode == "startswith") + data.fulltextStringMode = StringFilterMode::STARTSWITH; + else // if (fulltext_string_mode == "exact")) + data.fulltextStringMode = StringFilterMode::EXACT; + data.fullText = QString::fromStdString(std::move(fulltext)); } -extern "C" int filter_preset_constraint_count(int preset) +void filter_preset::add_constraint(const std::string &type, const std::string &string_mode, + const std::string &range_mode, bool negate, const std::string &constraint_data) { - return (int)global_table()[preset].data.constraints.size(); -} - -extern "C" const filter_constraint *filter_preset_constraint(int preset, int constraint) -{ - return &global_table()[preset].data.constraints[constraint]; -} - -extern "C" void filter_preset_set_name(struct filter_preset *preset, const char *name) -{ - preset->name = name; -} - -static int filter_preset_add_to_table(const std::string name, const FilterData &d, struct filter_preset_table &table) -{ - // std::lower_bound does a binary search - the vector must be sorted. - filter_preset newEntry { name, d }; - auto it = std::lower_bound(table.begin(), table.end(), newEntry, - [](const filter_preset &p1, const filter_preset &p2) - { return p1.name < p2.name; }); - it = table.insert(it, newEntry); - return it - table.begin(); -} - -// Take care that the name doesn't already exist by adding numbers -static std::string get_unique_preset_name(const std::string &orig, const struct filter_preset_table &table) -{ - std::string res = orig; - int count = 2; - while (std::find_if(table.begin(), table.end(), - [&res](const filter_preset &preset) - { return preset.name == res; }) != table.end()) { - res = orig + "#" + std::to_string(count); - ++count; - } - return res; -} - -extern "C" void add_filter_preset_to_table(const struct filter_preset *preset, struct filter_preset_table *table) -{ - std::string name = get_unique_preset_name(preset->name, *table); - filter_preset_add_to_table(name, preset->data, *table); -} - -extern "C" void filter_preset_add_constraint(struct filter_preset *preset, const char *type, const char *string_mode, - const char *range_mode, bool negate, const char *data) -{ - preset->data.constraints.emplace_back(type, string_mode, range_mode, negate, data); -} - -int filter_preset_id(const std::string &name) -{ - auto it = std::find_if(global_table().begin(), global_table().end(), - [&name] (filter_preset &p) { return p.name == name; }); - return it != global_table().end() ? it - global_table().begin() : -1; -} - -std::string filter_preset_name(int preset) -{ - return global_table()[preset].name; -} - -void filter_preset_set(int preset, const FilterData &data) -{ - global_table()[preset].data = data; -} - -FilterData filter_preset_get(int preset) -{ - return global_table()[preset].data; -} - -int filter_preset_add(const std::string &nameIn, const FilterData &d) -{ - std::string name = get_unique_preset_name(nameIn, global_table()); - return filter_preset_add_to_table(name, d, global_table()); -} - -void filter_preset_delete(int preset) -{ - global_table().erase(global_table().begin() + preset); + data.constraints.emplace_back(type, string_mode, range_mode, negate, constraint_data); } diff --git a/core/filterpreset.h b/core/filterpreset.h index c15a88fdf..14e22ebf3 100644 --- a/core/filterpreset.h +++ b/core/filterpreset.h @@ -1,75 +1,23 @@ // SPDX-License-Identifier: GPL-2.0 -// A list of filter settings with names. Every name is unique, which -// means that saving to an old name will overwrite the old preset. -// -// Even though the filter data itself is a C++/Qt class to simplify -// string manipulation and memory management, the data is accessible -// via pure C functions so that it can be written to the log (git or xml). #ifndef FILTER_PRESETS_H #define FILTER_PRESETS_H +#include "divefilter.h" +#include + struct dive; struct filter_constraint; +struct FilterData; -// So that we can pass filter preset table between C and C++ we define -// it as an opaque type in C. Thus we can easily create the table in C++ -// without having to do our own memory management and pass pointers to -// void through C. -#ifdef __cplusplus -#include "divefilter.h" -#include -#include struct filter_preset { std::string name; FilterData data; + + std::string fulltext_query() const; // Fulltext query of filter preset. + const char *fulltext_mode() const; // String mode of fulltext query. Ownership is *not* passed to caller. + void set_fulltext(const std::string fulltext, const std::string &fulltext_string_mode); // First argument is consumed. + void add_constraint(const std::string &type, const std::string &string_mode, + const std::string &range_mode, bool negate, const std::string &data); // called by the parser, therefore data passed as strings. }; -// Subclassing standard library containers is generally -// not recommended. However, this makes interaction with -// C-code easier and since we don't add any member functions, -// this is not a problem. -struct filter_preset_table : public std::vector -{ -}; - -#else -struct filter_preset; -struct filter_preset_table; -#endif - - -#ifdef __cplusplus -extern "C" { -#endif - -// The C IO code accesses the filter presets via integer indices. -extern int filter_presets_count(void); -extern const char *filter_preset_fulltext_mode(int preset); // string mode of fulltext query. ownership is *not* passed to caller. -extern int filter_preset_constraint_count(int preset); // number of constraints in the filter preset. -extern const struct filter_constraint *filter_preset_constraint(int preset, int constraint); // get constraint. ownership is *not* passed to caller. -extern void filter_preset_set_name(struct filter_preset *preset, const char *name); -extern void filter_preset_set_fulltext(struct filter_preset *preset, const char *fulltext, const char *fulltext_string_mode); -extern void add_filter_preset_to_table(const struct filter_preset *preset, struct filter_preset_table *table); -extern void filter_preset_add_constraint(struct filter_preset *preset, const char *type, const char *string_mode, - const char *range_mode, bool negate, const char *data); // called by the parser, therefore data passed as strings. - -#ifdef __cplusplus -} -#endif - -// C++ only functions -#ifdef __cplusplus - -struct FilterData; - -int filter_preset_id(const std::string &s); // for now, we assume that names are unique. returns -1 if no preset with that name. -void filter_preset_set(int preset, const FilterData &d); // this will override a preset if the name already exists. -FilterData filter_preset_get(int preset); -int filter_preset_add(const std::string &name, const FilterData &d); // returns point of insertion -void filter_preset_delete(int preset); -std::string filter_preset_name(int preset); // name of filter preset - caller must free the result. -std::string filter_preset_fulltext_query(int preset); // fulltext query of filter preset - caller must free the result. - -#endif - #endif diff --git a/core/filterpresettable.cpp b/core/filterpresettable.cpp new file mode 100644 index 000000000..6a545316c --- /dev/null +++ b/core/filterpresettable.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "filterpresettable.h" +#include "filterpreset.h" + +#include + +int filter_preset_table::preset_id(const std::string &name) const +{ + auto it = std::find_if(begin(), end(), [&name] (const filter_preset &p) { return p.name == name; }); + return it != end() ? it - begin() : -1; +} + +// Take care that the name doesn't already exist by adding numbers +static std::string get_unique_preset_name(const std::string &orig, const struct filter_preset_table &table) +{ + std::string res = orig; + int count = 2; + while (std::find_if(table.begin(), table.end(), + [&res](const filter_preset &preset) + { return preset.name == res; }) != table.end()) { + res = orig + "#" + std::to_string(count); + ++count; + } + return res; +} + +static int filter_preset_add_to_table(const std::string name, const FilterData &d, struct filter_preset_table &table) +{ + // std::lower_bound does a binary search - the vector must be sorted. + filter_preset newEntry { name, d }; + auto it = std::lower_bound(table.begin(), table.end(), newEntry, + [](const filter_preset &p1, const filter_preset &p2) + { return p1.name < p2.name; }); + it = table.insert(it, newEntry); + return it - table.begin(); +} + +void filter_preset_table::add(const filter_preset &preset) +{ + std::string name = get_unique_preset_name(preset.name, *this); + filter_preset_add_to_table(std::move(name), preset.data, *this); +} + +int filter_preset_table::add(const std::string &nameIn, const FilterData &d) +{ + std::string name = get_unique_preset_name(nameIn, *this); + return filter_preset_add_to_table(std::move(name), d, *this); +} + +void filter_preset_table::remove(int preset) +{ + erase(begin() + preset); +} diff --git a/core/filterpresettable.h b/core/filterpresettable.h new file mode 100644 index 000000000..849c5a48a --- /dev/null +++ b/core/filterpresettable.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +// A list of filter settings with names. Every name is unique, which +// means that saving to an old name will overwrite the old preset. +#ifndef FILTER_PRESETSTABLE_H +#define FILTER_PRESETSTABLE_H + +#include +#include + +struct filter_preset; +struct FilterData; + +// Subclassing standard library containers is generally +// not recommended. However, this makes interaction with +// C-code easier and since we don't add any member functions, +// this is not a problem. +struct filter_preset_table : public std::vector +{ + void add(const filter_preset &preset); + int add(const std::string &name, const FilterData &d); // returns point of insertion + void remove(int iox); + int preset_id(const std::string &s) const; // for now, we assume that names are unique. returns -1 if no preset with that name +}; + +#endif diff --git a/core/format.cpp b/core/format.cpp index 693a8f7f2..dc9d3db0f 100644 --- a/core/format.cpp +++ b/core/format.cpp @@ -353,20 +353,27 @@ std::string casprintf_loc(const char *cformat, ...) return std::string(utf8.constData(), utf8.size()); } -std::string __printf(1, 2) format_string_std(const char *fmt, ...) +std::string format_string_std(const char *fmt, ...) { va_list ap; va_start(ap, fmt); - size_t stringsize = vsnprintf(NULL, 0, fmt, ap); + std::string res = vformat_string_std(fmt, ap); va_end(ap); + return res; +} + +std::string vformat_string_std(const char *fmt, va_list ap) +{ + va_list ap2; + va_copy(ap2, ap); + size_t stringsize = vsnprintf(NULL, 0, fmt, ap2); + va_end(ap2); if (stringsize == 0) return std::string(); std::string res; res.resize(stringsize); // Pointless clearing, oh my. // This overwrites the terminal null-byte of std::string. // That's probably "undefined behavior". Oh my. - va_start(ap, fmt); vsnprintf(res.data(), stringsize + 1, fmt, ap); - va_end(ap); return res; } diff --git a/core/format.h b/core/format.h index 599911082..9b9567f5d 100644 --- a/core/format.h +++ b/core/format.h @@ -7,12 +7,11 @@ #define __printf(x, y) #endif -#ifdef __cplusplus #include __printf(1, 2) QString qasprintf_loc(const char *cformat, ...); __printf(1, 0) QString vqasprintf_loc(const char *cformat, va_list ap); __printf(1, 2) std::string casprintf_loc(const char *cformat, ...); +__printf(1, 0) std::string vformat_string_std(const char *fmt, va_list ap); __printf(1, 2) std::string format_string_std(const char *fmt, ...); -#endif #endif diff --git a/core/fulltext.cpp b/core/fulltext.cpp index 8a9c956d7..1437d3eae 100644 --- a/core/fulltext.cpp +++ b/core/fulltext.cpp @@ -10,11 +10,6 @@ #include #include -// This class caches each dives words, so that we can unregister a dive from the full text search -struct full_text_cache { - std::vector words; -}; - // The FullText-search class class FullText { std::map> words; // Dives that belong to each word @@ -35,8 +30,6 @@ static FullText self; // C-interface functions -extern "C" { - void fulltext_register(struct dive *d) { self.registerDive(d); @@ -57,8 +50,6 @@ void fulltext_populate() self.populate(); } -} // extern "C" - // C++-only interface functions FullTextResult fulltext_find_dives(const FullTextQuery &q, StringFilterMode mode) { @@ -123,31 +114,27 @@ static void tokenize(QString s, std::vector &res) static std::vector getWords(const dive *d) { std::vector res; - tokenize(QString(d->notes), res); - tokenize(QString(d->diveguide), res); - tokenize(QString(d->buddy), res); - tokenize(QString(d->suit), res); - for (const tag_entry *tag = d->tag_list; tag; tag = tag->next) - tokenize(QString::fromStdString(tag->tag->name), res); - for (int i = 0; i < d->cylinders.nr; ++i) { - const cylinder_t &cyl = *get_cylinder(d, i); - tokenize(QString(cyl.type.description), res); - } - for (int i = 0; i < d->weightsystems.nr; ++i) { - const weightsystem_t &ws = d->weightsystems.weightsystems[i]; - tokenize(QString(ws.description), res); - } + tokenize(QString::fromStdString(d->notes), res); + tokenize(QString::fromStdString(d->diveguide), res); + tokenize(QString::fromStdString(d->buddy), res); + tokenize(QString::fromStdString(d->suit), res); + for (const divetag *tag: d->tags) + tokenize(QString::fromStdString(tag->name), res); + for (auto &cyl: d->cylinders) + tokenize(QString::fromStdString(cyl.type.description), res); + for (auto &ws: d->weightsystems) + tokenize(QString::fromStdString(ws.description), res); // TODO: We should tokenize all dive-sites and trips first and then // take the tokens from a cache. if (d->dive_site) { - tokenize(d->dive_site->name, res); - const char *country = taxonomy_get_country(&d->dive_site->taxonomy); - if (country) - tokenize(country, res); + tokenize(QString::fromStdString(d->dive_site->name), res); + std::string country = taxonomy_get_country(d->dive_site->taxonomy); + if (!country.empty()) + tokenize(country.c_str(), res); } // TODO: We should index trips separately! if (d->divetrip) - tokenize(d->divetrip->location, res); + tokenize(QString::fromStdString(d->divetrip->location), res); return res; } @@ -156,11 +143,9 @@ void FullText::populate() // we want this to be two calls as the second text is overwritten below by the lines starting with "\r" uiNotification(QObject::tr("Create full text index")); uiNotification(QObject::tr("start processing")); - int i; - dive *d; - for_each_dive(i, d) - registerDive(d); - uiNotification(QObject::tr("%1 dives processed").arg(divelog.dives->nr)); + for (auto &d: divelog.dives) + registerDive(d.get()); + uiNotification(QObject::tr("%1 dives processed").arg(divelog.dives.size())); } void FullText::registerDive(struct dive *d) @@ -168,7 +153,7 @@ void FullText::registerDive(struct dive *d) if (d->full_text) unregisterWords(d, d->full_text->words); else - d->full_text = new full_text_cache; + d->full_text = std::make_unique(); d->full_text->words = getWords(d); registerWords(d, d->full_text->words); } @@ -178,18 +163,13 @@ void FullText::unregisterDive(struct dive *d) if (!d->full_text) return; unregisterWords(d, d->full_text->words); - delete d->full_text; - d->full_text = nullptr; + d->full_text.reset(); } void FullText::unregisterAll() { - int i; - dive *d; - for_each_dive(i, d) { - delete d->full_text; - d->full_text = nullptr; - } + for (auto &d: divelog.dives) + d->full_text.reset(); words.clear(); } diff --git a/core/fulltext.h b/core/fulltext.h index 2f1c63db1..0775e5e8a 100644 --- a/core/fulltext.h +++ b/core/fulltext.h @@ -5,43 +5,30 @@ // issues such as COW semantics and UTF-16 encoding, it provides // platform independence and reasonable performance. Therefore, // this is based in QString instead of std::string. -// -// To make this accessible from C, this does manual memory management: -// Every dive is associated with a cache of words. Thus, when deleting -// a dive, a function freeing that data has to be called. #ifndef FULLTEXT_H #define FULLTEXT_H -// 1) The C-accessible interface +#include +#include -#ifdef __cplusplus -extern "C" { -#endif - -struct full_text_cache; struct dive; void fulltext_register(struct dive *d); // Note: can be called repeatedly void fulltext_unregister(struct dive *d); // Note: can be called repeatedly void fulltext_unregister_all(); // Unregisters all dives in the dive table void fulltext_populate(); // Registers all dives in the dive table -#ifdef __cplusplus -} -#endif - -// 2) The C++-only interface -#ifdef __cplusplus - -#include -#include - enum class StringFilterMode { SUBSTRING = 0, STARTSWITH = 1, EXACT = 2 }; +// This class caches each dives words, so that we can unregister a dive from the full text search +struct full_text_cache { + std::vector words; +}; + // A fulltext query. Basically a list of normalized words we search for struct FullTextQuery { std::vector words; @@ -63,4 +50,3 @@ FullTextResult fulltext_find_dives(const FullTextQuery &q, StringFilterMode); bool fulltext_dive_matches(const struct dive *d, const FullTextQuery &q, StringFilterMode); #endif -#endif diff --git a/core/gas-model.c b/core/gas-model.cpp similarity index 87% rename from core/gas-model.c rename to core/gas-model.cpp index 194c8a030..6c8bee1a6 100644 --- a/core/gas-model.c +++ b/core/gas-model.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 -/* gas-model.c */ +/* gas-model.cpp */ /* gas compressibility model */ +#include // std::clamp #include #include #include "dive.h" @@ -46,23 +47,20 @@ double gas_compressibility_factor(struct gasmix gas, double bar) -8.83632921053e-08, +5.33304543646e-11 }; - int o2, he; - double Z; /* * The curve fitting range is only [0,500] bar. * Anything else is way out of range for cylinder * pressures. */ - if (bar < 0) bar = 0; - if (bar > 500) bar = 500; + bar = std::clamp(bar, 0.0, 500.0); - o2 = get_o2(gas); - he = get_he(gas); + int o2 = get_o2(gas); + int he = get_he(gas); - Z = virial_m1(o2_coefficients, bar) * o2 + - virial_m1(he_coefficients, bar) * he + - virial_m1(n2_coefficients, bar) * (1000 - o2 - he); + double Z = virial_m1(o2_coefficients, bar) * o2 + + virial_m1(he_coefficients, bar) * he + + virial_m1(n2_coefficients, bar) * (1000 - o2 - he); /* * We add the 1.0 at the very end - the linear mixing of the diff --git a/core/gas.c b/core/gas.cpp similarity index 73% rename from core/gas.c rename to core/gas.cpp index d43781e8a..0ef4033ba 100644 --- a/core/gas.c +++ b/core/gas.cpp @@ -2,9 +2,11 @@ #include "gas.h" #include "pref.h" #include "errorhelper.h" +#include "format.h" #include "gettext.h" #include #include +#include // for QT_TRANSLATE_NOOP /* Perform isobaric counterdiffusion calculations for gas changes in trimix dives. * Here we use the rule-of-fifths where, during a change involving trimix gas, the increase in nitrogen @@ -39,20 +41,20 @@ int same_gasmix(struct gasmix a, struct gasmix b) return get_o2(a) == get_o2(b) && get_he(a) == get_he(b); } -void sanitize_gasmix(struct gasmix *mix) +void sanitize_gasmix(struct gasmix &mix) { unsigned int o2, he; - o2 = get_o2(*mix); - he = get_he(*mix); + o2 = get_o2(mix); + he = get_he(mix); /* Regular air: leave empty */ if (!he) { if (!o2) return; /* 20.8% to 21% O2 is just air */ - if (gasmix_is_air(*mix)) { - mix->o2.permille = 0; + if (gasmix_is_air(mix)) { + mix.o2 = 0_percent; return; } } @@ -61,7 +63,7 @@ void sanitize_gasmix(struct gasmix *mix) if (o2 <= 1000 && he <= 1000 && o2 + he <= 1000) return; report_info("Odd gasmix: %u O2 %u He", o2, he); - memset(mix, 0, sizeof(*mix)); + mix = gasmix_air; } int gasmix_distance(struct gasmix a, struct gasmix b) @@ -115,42 +117,43 @@ int pscr_o2(const double amb_pressure, struct gasmix mix) * The structure "pressures" is used to return calculated gas pressures to the calling software. * Call parameters: po2 = po2 value applicable to the record in calling function * amb_pressure = ambient pressure applicable to the record in calling function - * *pressures = structure for communicating o2 sensor values from and gas pressures to the calling function. * *mix = structure containing cylinder gas mixture information. * divemode = the dive mode pertaining to this point in the dive profile. * This function called by: calculate_gas_information_new() in profile.cpp; add_segment() in deco.cpp. */ -void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, struct gasmix mix, double po2, enum divemode_t divemode) +gas_pressures fill_pressures(const double amb_pressure, struct gasmix mix, double po2, enum divemode_t divemode) { - if ((divemode != OC) && po2) { // This is a rebreather dive where pressures->o2 is defined + struct gas_pressures pressures; + if ((divemode != OC) && po2) { // This is a rebreather dive where pressures.o2 is defined if (po2 >= amb_pressure) { - pressures->o2 = amb_pressure; - pressures->n2 = pressures->he = 0.0; + pressures.o2 = amb_pressure; + pressures.n2 = pressures.he = 0.0; } else { - pressures->o2 = po2; + pressures.o2 = po2; if (get_o2(mix) == 1000) { - pressures->he = pressures->n2 = 0; + pressures.he = pressures.n2 = 0; } else { - pressures->he = (amb_pressure - pressures->o2) * (double)get_he(mix) / (1000 - get_o2(mix)); - pressures->n2 = amb_pressure - pressures->o2 - pressures->he; + pressures.he = (amb_pressure - pressures.o2) * (double)get_he(mix) / (1000 - get_o2(mix)); + pressures.n2 = amb_pressure - pressures.o2 - pressures.he; } } } else { if (divemode == PSCR) { /* The steady state approximation should be good enough */ - pressures->o2 = pscr_o2(amb_pressure, mix) / 1000.0; + pressures.o2 = pscr_o2(amb_pressure, mix) / 1000.0; if (get_o2(mix) != 1000) { - pressures->he = (amb_pressure - pressures->o2) * get_he(mix) / (1000.0 - get_o2(mix)); - pressures->n2 = (amb_pressure - pressures->o2) * get_n2(mix) / (1000.0 - get_o2(mix)); + pressures.he = (amb_pressure - pressures.o2) * get_he(mix) / (1000.0 - get_o2(mix)); + pressures.n2 = (amb_pressure - pressures.o2) * get_n2(mix) / (1000.0 - get_o2(mix)); } else { - pressures->he = pressures->n2 = 0; + pressures.he = pressures.n2 = 0; } } else { // Open circuit dives: no gas pressure values available, they need to be calculated - pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure; // These calculations are also used if the CCR calculation above.. - pressures->he = get_he(mix) / 1000.0 * amb_pressure; // ..returned a po2 of zero (i.e. o2 sensor data not resolvable) - pressures->n2 = get_n2(mix) / 1000.0 * amb_pressure; + pressures.o2 = get_o2(mix) / 1000.0 * amb_pressure; // These calculations are also used if the CCR calculation above.. + pressures.he = get_he(mix) / 1000.0 * amb_pressure; // ..returned a po2 of zero (i.e. o2 sensor data not resolvable) + pressures.n2 = get_n2(mix) / 1000.0 * amb_pressure; } } + return pressures; } enum gastype gasmix_to_type(struct gasmix mix) @@ -181,3 +184,17 @@ const char *gastype_name(enum gastype type) return ""; return translate("gettextFromC", gastype_names[type]); } + +std::string gasmix::name() const +{ + if (get_he(*this) < 0 || get_o2(*this) < 0) + return translate("gettextFromC", "invalid gas"); + else if (gasmix_is_air(*this)) + return translate("gettextFromC", "air"); + else if (get_he(*this) == 0 && get_o2(*this) < 1000) + return format_string_std(translate("gettextFromC", "EAN%d"), (get_o2(*this) + 5) / 10); + else if (get_he(*this) == 0 && get_o2(*this) == 1000) + return translate("gettextFromC", "oxygen"); + else + return format_string_std("(%d/%d)", (get_o2(*this) + 5) / 10, (get_he(*this) + 5) / 10); +} diff --git a/core/gas.h b/core/gas.h index d1b466eeb..ee583877b 100644 --- a/core/gas.h +++ b/core/gas.h @@ -5,11 +5,7 @@ #include "divemode.h" #include "units.h" -#ifdef __cplusplus -extern "C" { -#else -#include -#endif +#include enum gas_component { N2, HE, O2 }; @@ -18,9 +14,10 @@ enum gas_component { N2, HE, O2 }; struct gasmix { fraction_t o2; fraction_t he; + std::string name() const; }; -static const struct gasmix gasmix_invalid = { { -1 }, { -1 } }; -static const struct gasmix gasmix_air = { { 0 }, { 0 } }; +static const struct gasmix gasmix_invalid = { { .permille = -1 }, { .permille = -1 } }; +static const struct gasmix gasmix_air = { 0_percent, 0_percent }; enum gastype { GASTYPE_AIR, @@ -61,13 +58,13 @@ static inline int get_n2(struct gasmix mix) int pscr_o2(const double amb_pressure, struct gasmix mix); struct gas_pressures { - double o2, n2, he; + double o2 = 0.0, n2 = 0.0, he = 0.0; }; -extern void sanitize_gasmix(struct gasmix *mix); +extern void sanitize_gasmix(struct gasmix &mix); extern int gasmix_distance(struct gasmix a, struct gasmix b); extern fraction_t get_gas_component_fraction(struct gasmix mix, enum gas_component component); -extern void fill_pressures(struct gas_pressures *pressures, double amb_pressure, struct gasmix mix, double po2, enum divemode_t dctype); +extern gas_pressures fill_pressures(double amb_pressure, struct gasmix mix, double po2, enum divemode_t dctype); extern bool gasmix_is_air(struct gasmix gasmix); extern bool gasmix_is_invalid(struct gasmix mix); @@ -75,8 +72,4 @@ extern enum gastype gasmix_to_type(struct gasmix mix); extern const char *gastype_name(enum gastype type); extern fraction_t make_fraction(int f); -#ifdef __cplusplus -} -#endif - #endif diff --git a/core/gaspressures.c b/core/gaspressures.cpp similarity index 58% rename from core/gaspressures.c rename to core/gaspressures.cpp index 3e440c5df..9698e54c2 100644 --- a/core/gaspressures.c +++ b/core/gaspressures.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 -/* gaspressures.c - * --------------- +/* gaspressures.cpp + * ---------------- * This file contains the routines to calculate the gas pressures in the cylinders. * The functions below support the code in profile.cpp. * The high-level function is populate_pressure_information(), called by function @@ -10,15 +10,8 @@ * populate_pressure_information() -> calc_pressure_time() * -> fill_missing_tank_pressures() -> fill_missing_segment_pressures() * -> get_pr_interpolate_data() - * - * The pr_track_t related functions below implement a linked list that is used by - * the majority of the functions below. The linked list covers a part of the dive profile - * for which there are no cylinder pressure data. Each element in the linked list - * represents a segment between two consecutive points on the dive profile. - * pr_track_t is defined in gaspressures.h */ -#include "ssrf.h" #include "dive.h" #include "event.h" #include "profile.h" @@ -26,23 +19,29 @@ #include "pref.h" #include +#include /* * simple structure to track the beginning and end tank pressure as * well as the integral of depth over time spent while we have no * pressure reading from the tank */ -typedef struct pr_track_struct pr_track_t; -struct pr_track_struct { +struct pr_track_t { int start; int end; int t_start; int t_end; int pressure_time; - pr_track_t *next; + pr_track_t(int start, int t_start) : + start(start), + end(0), + t_start(t_start), + t_end(t_start), + pressure_time(0) + { + } }; -typedef struct pr_interpolate_struct pr_interpolate_t; -struct pr_interpolate_struct { +struct pr_interpolate_t { int start; int end; int pressure_time; @@ -51,61 +50,17 @@ struct pr_interpolate_struct { enum interpolation_strategy {SAC, TIME, CONSTANT}; -static pr_track_t *pr_track_alloc(int start, int t_start) -{ - pr_track_t *pt = malloc(sizeof(pr_track_t)); - pt->start = start; - pt->end = 0; - pt->t_start = pt->t_end = t_start; - pt->pressure_time = 0; - pt->next = NULL; - return pt; -} - -/* poor man's linked list */ -static pr_track_t *list_last(pr_track_t *list) -{ - pr_track_t *tail = list; - if (!tail) - return NULL; - while (tail->next) { - tail = tail->next; - } - return tail; -} - -static pr_track_t *list_add(pr_track_t *list, pr_track_t *element) -{ - pr_track_t *tail = list_last(list); - if (!tail) - return element; - tail->next = element; - return list; -} - -static void list_free(pr_track_t *list) -{ - if (!list) - return; - list_free(list->next); - free(list); -} - #ifdef DEBUG_PR_TRACK -static void dump_pr_track(int cyl, pr_track_t *track_pr) +static void dump_pr_track(int cyl, std::vector &track_pr) { - pr_track_t *list; - printf("cyl%d:\n", cyl); - list = track_pr; - while (list) { + for (const auto &item: track_pr) { printf(" start %f end %f t_start %d:%02d t_end %d:%02d pt %d\n", - mbar_to_PSI(list->start), - mbar_to_PSI(list->end), - FRACTION_TUPLE(list->t_start, 60), - FRACTION_TUPLE(list->t_end, 60), - list->pressure_time); - list = list->next; + mbar_to_PSI(item.start), + mbar_to_PSI(item.end), + FRACTION_TUPLE(item.t_start, 60), + FRACTION_TUPLE(item.t_end, 60), + item.pressure_time); } } #endif @@ -128,24 +83,22 @@ static void dump_pr_track(int cyl, pr_track_t *track_pr) * segments according to how big of a time_pressure area * they have. */ -static void fill_missing_segment_pressures(pr_track_t *list, enum interpolation_strategy strategy) +static void fill_missing_segment_pressures(std::vector &list, enum interpolation_strategy strategy) { - double magic; - - while (list) { - int start = list->start, end; - pr_track_t *tmp = list; + for (auto it = list.begin(); it != list.end(); ++it) { + int start = it->start, end; int pt_sum = 0, pt = 0; + auto tmp = it; for (;;) { pt_sum += tmp->pressure_time; end = tmp->end; if (end) break; end = start; - if (!tmp->next) + if (std::next(tmp) == list.end()) break; - tmp = tmp->next; + ++tmp; } if (!start) @@ -159,37 +112,34 @@ static void fill_missing_segment_pressures(pr_track_t *list, enum interpolation_ * * Now dole out the pressures relative to pressure-time. */ - list->start = start; + it->start = start; tmp->end = end; switch (strategy) { case SAC: for (;;) { int pressure; - pt += list->pressure_time; + pt += it->pressure_time; pressure = start; if (pt_sum) pressure -= lrint((start - end) * (double)pt / pt_sum); - list->end = pressure; - if (list == tmp) + it->end = pressure; + if (it == tmp) break; - list = list->next; - list->start = pressure; + ++it; + it->start = pressure; } break; case TIME: - if (list->t_end && (tmp->t_start - tmp->t_end)) { - magic = (list->t_start - tmp->t_end) / (tmp->t_start - tmp->t_end); - list->end = lrint(start - (start - end) * magic); + if (it->t_end && (tmp->t_start - tmp->t_end)) { + double magic = (it->t_start - tmp->t_end) / (tmp->t_start - tmp->t_end); + it->end = lrint(start - (start - end) * magic); } else { - list->end = start; + it->end = start; } break; case CONSTANT: - list->end = start; + it->end = start; } - - /* Ok, we've done that set of segments */ - list = list->next; } } @@ -202,50 +152,47 @@ void dump_pr_interpolate(int i, pr_interpolate_t interpolate_pr) #endif -static struct pr_interpolate_struct get_pr_interpolate_data(pr_track_t *segment, struct plot_info *pi, int cur) -{ // cur = index to pi->entry corresponding to t_end of segment; - struct pr_interpolate_struct interpolate; +static pr_interpolate_t get_pr_interpolate_data(const pr_track_t &segment, struct plot_info &pi, int cur) +{ // cur = index to pi.entry corresponding to t_end of segment; + pr_interpolate_t interpolate; int i; - struct plot_data *entry; - interpolate.start = segment->start; - interpolate.end = segment->end; + interpolate.start = segment.start; + interpolate.end = segment.end; interpolate.acc_pressure_time = 0; interpolate.pressure_time = 0; - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; + for (i = 0; i < pi.nr; i++) { + const plot_data &entry = pi.entry[i]; - if (entry->sec < segment->t_start) + if (entry.sec < segment.t_start) continue; - interpolate.pressure_time += entry->pressure_time; - if (entry->sec >= segment->t_end) + interpolate.pressure_time += entry.pressure_time; + if (entry.sec >= segment.t_end) break; if (i <= cur) - interpolate.acc_pressure_time += entry->pressure_time; + interpolate.acc_pressure_time += entry.pressure_time; } return interpolate; } -static void fill_missing_tank_pressures(const struct dive *dive, struct plot_info *pi, pr_track_t *track_pr, int cyl) +static void fill_missing_tank_pressures(const struct dive *dive, struct plot_info &pi, std::vector &track_pr, int cyl) { int i; - struct plot_data *entry; pr_interpolate_t interpolate = { 0, 0, 0, 0 }; - pr_track_t *last_segment = NULL; int cur_pr; enum interpolation_strategy strategy; /* no segment where this cylinder is used */ - if (!track_pr) + if (track_pr.empty()) return; - if (get_cylinder(dive, cyl)->cylinder_use == OC_GAS) + if (dive->get_cylinder(cyl)->cylinder_use == OC_GAS) strategy = SAC; else strategy = TIME; fill_missing_segment_pressures(track_pr, strategy); // Interpolate the missing tank pressure values .. - cur_pr = track_pr->start; // in the pr_track_t lists of structures + cur_pr = track_pr[0].start; // in the pr_track_t lists of structures // and keep the starting pressure for each cylinder. #ifdef DEBUG_PR_TRACK dump_pr_track(cyl, track_pr); @@ -261,68 +208,64 @@ static void fill_missing_tank_pressures(const struct dive *dive, struct plot_inf * * The first two pi structures are "fillers", but in case we don't have a sample * at time 0 we need to process the second of them here, therefore i=1 */ - for (i = 1; i < pi->nr; i++) { // For each point on the profile: - double magic; - pr_track_t *segment; - int pressure; + auto last_segment = track_pr.end(); + for (i = 1; i < pi.nr; i++) { // For each point on the profile: + const struct plot_data &entry = pi.entry[i]; - entry = pi->entry + i; + int pressure = get_plot_pressure(pi, i, cyl); - pressure = get_plot_pressure(pi, i, cyl); - - if (pressure) { // If there is a valid pressure value, - last_segment = NULL; // get rid of interpolation data, - cur_pr = pressure; // set current pressure - continue; // and skip to next point. + if (pressure) { // If there is a valid pressure value, + last_segment = track_pr.end(); // get rid of interpolation data, + cur_pr = pressure; // set current pressure + continue; // and skip to next point. } // If there is NO valid pressure value.. // Find the pressure segment corresponding to this entry.. - segment = track_pr; - while (segment && segment->t_end < entry->sec) // Find the track_pr with end time.. - segment = segment->next; // ..that matches the plot_info time (entry->sec) + auto it = track_pr.begin(); + while (it != track_pr.end() && it->t_end < entry.sec) // Find the track_pr with end time.. + ++it; // ..that matches the plot_info time (entry.sec) // After last segment? All done. - if (!segment) + if (it == track_pr.end()) break; // Before first segment, or between segments.. Go on, no interpolation. - if (segment->t_start > entry->sec) + if (it->t_start > entry.sec) continue; - if (!segment->pressure_time) { // Empty segment? + if (!it->pressure_time) { // Empty segment? set_plot_pressure_data(pi, i, SENSOR_PR, cyl, cur_pr); // Just use our current pressure continue; // and skip to next point. } // If there is a valid segment but no tank pressure .. - if (segment == last_segment) { - interpolate.acc_pressure_time += entry->pressure_time; + if (it == last_segment) { + interpolate.acc_pressure_time += entry.pressure_time; } else { // Set up an interpolation structure - interpolate = get_pr_interpolate_data(segment, pi, i); - last_segment = segment; + interpolate = get_pr_interpolate_data(*it, pi, i); + last_segment = it; } - if(get_cylinder(dive, cyl)->cylinder_use == OC_GAS) { + if(dive->get_cylinder(cyl)->cylinder_use == OC_GAS) { /* if this segment has pressure_time, then calculate a new interpolated pressure */ if (interpolate.pressure_time) { /* Overall pressure change over total pressure-time for this segment*/ - magic = (interpolate.end - interpolate.start) / (double)interpolate.pressure_time; + double magic = (interpolate.end - interpolate.start) / (double)interpolate.pressure_time; /* Use that overall pressure change to update the current pressure */ cur_pr = lrint(interpolate.start + magic * interpolate.acc_pressure_time); } } else { - magic = (interpolate.end - interpolate.start) / (segment->t_end - segment->t_start); - cur_pr = lrint(segment->start + magic * (entry->sec - segment->t_start)); + double magic = (interpolate.end - interpolate.start) / static_cast(it->t_end - it->t_start); + cur_pr = lrint(it->start + magic * (entry.sec - it->t_start)); } set_plot_pressure_data(pi, i, INTERPOLATED_PR, cyl, cur_pr); // and store the interpolated data in plot_info } } - /* * What's the pressure-time between two plot data entries? * We're calculating the integral of pressure over time by @@ -334,49 +277,46 @@ static void fill_missing_tank_pressures(const struct dive *dive, struct plot_inf * scale pressures, so it ends up being a unitless scaling * factor. */ -static inline int calc_pressure_time(const struct dive *dive, struct plot_data *a, struct plot_data *b) +static inline int calc_pressure_time(const struct dive *dive, const struct plot_data &a, const struct plot_data &b) { - int time = b->sec - a->sec; - int depth = (a->depth + b->depth) / 2; + int time = b.sec - a.sec; + int depth = (a.depth + b.depth) / 2; if (depth <= SURFACE_THRESHOLD) return 0; - return depth_to_mbar(depth, dive) * time; + return dive->depth_to_mbar(depth) * time; } #ifdef PRINT_PRESSURES_DEBUG // A CCR debugging tool that prints the gas pressures in cylinder 0 and in the diluent cylinder, used in populate_pressure_information(): -static void debug_print_pressures(struct plot_info *pi) +static void debug_print_pressures(struct plot_info &pi) { int i; - for (i = 0; i < pi->nr; i++) + for (i = 0; i < pi.nr; i++) printf("%5d |%9d | %9d |\n", i, get_plot_sensor_pressure(pi, i), get_plot_interpolated_pressure(pi, i)); } #endif /* This function goes through the list of tank pressures, of structure plot_info for the dive profile where each * item in the list corresponds to one point (node) of the profile. It finds values for which there are no tank - * pressures (pressure==0). For each missing item (node) of tank pressure it creates a pr_track_alloc structure + * pressures (pressure==0). For each missing item (node) of tank pressure it creates a pr_track_t structure * that represents a segment on the dive profile and that contains tank pressures. There is a linked list of - * pr_track_alloc structures for each cylinder. These pr_track_alloc structures ultimately allow for filling + * pr_track_t structures for each cylinder. These pr_track_t structures ultimately allow for filling * the missing tank pressure values on the dive profile using the depth_pressure of the dive. To do this, it - * calculates the summed pressure-time value for the duration of the dive and stores these * in the pr_track_alloc + * calculates the summed pressure-time value for the duration of the dive and stores these in the pr_track_t * structures. This function is called by create_plot_info_new() in profile.cpp */ -void populate_pressure_information(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi, int sensor) +void populate_pressure_information(const struct dive *dive, const struct divecomputer *dc, struct plot_info &pi, int sensor) { - UNUSED(dc); int first, last, cyl; - cylinder_t *cylinder = get_cylinder(dive, sensor); - pr_track_t *track = NULL; - pr_track_t *current = NULL; - const struct event *ev, *b_ev; + const cylinder_t *cylinder = dive->get_cylinder(sensor); + std::vector track; + size_t current = std::string::npos; int missing_pr = 0, dense = 1; - enum divemode_t dmode = dc->divemode; const double gasfactor[5] = {1.0, 0.0, prefs.pscr_ratio/1000.0, 1.0, 1.0 }; - if (sensor < 0 || sensor >= dive->cylinders.nr) + if (sensor < 0 || static_cast(sensor) >= dive->cylinders.size()) return; /* if we have no pressure data whatsoever, this is pointless, so let's just return */ @@ -386,7 +326,7 @@ void populate_pressure_information(const struct dive *dive, const struct divecom /* Get a rough range of where we have any pressures at all */ first = last = -1; - for (int i = 0; i < pi->nr; i++) { + for (int i = 0; i < pi.nr; i++) { int pressure = get_plot_sensor_pressure(pi, i, sensor); if (!pressure) @@ -409,34 +349,29 @@ void populate_pressure_information(const struct dive *dive, const struct divecom * itself has a gas change event. */ cyl = sensor; - ev = NULL; - if (has_gaschange_event(dive, dc, sensor)) - ev = get_next_event(dc->events, "gaschange"); - b_ev = get_next_event(dc->events, "modechange"); + bool has_gaschange = dive->has_gaschange_event(dc, sensor); + gasmix_loop loop_gas(*dive, *dc); + divemode_loop loop_mode(*dc); for (int i = first; i <= last; i++) { - struct plot_data *entry = pi->entry + i; + struct plot_data &entry = pi.entry[i]; int pressure = get_plot_sensor_pressure(pi, i, sensor); - int time = entry->sec; + int time = entry.sec; - while (ev && ev->time.seconds <= time) { // Find 1st gaschange event after - cyl = get_cylinder_index(dive, ev); // the current gas change. + if (has_gaschange) { + cyl = loop_gas.cylinder_index_at(time).first; if (cyl < 0) cyl = sensor; - ev = get_next_event(ev->next, "gaschange"); } - while (b_ev && b_ev->time.seconds <= time) { // Keep existing divemode, then - dmode = b_ev->value; // find 1st divemode change event after the current - b_ev = get_next_event(b_ev->next, "modechange"); // divemode change. - } + divemode_t dmode = loop_mode.at(time); - if (current) { // calculate pressure-time, taking into account the dive mode for this specific segment. - entry->pressure_time = (int)(calc_pressure_time(dive, entry - 1, entry) * gasfactor[dmode] + 0.5); - current->pressure_time += entry->pressure_time; - current->t_end = entry->sec; + if (current != std::string::npos) { // calculate pressure-time, taking into account the dive mode for this specific segment. + entry.pressure_time = (int)(calc_pressure_time(dive, pi.entry[i - 1], entry) * gasfactor[dmode] + 0.5); + track[current].pressure_time += entry.pressure_time; + track[current].t_end = entry.sec; if (pressure) - current->end = pressure; + track[current].end = pressure; } // We have a final pressure for 'current' @@ -444,7 +379,7 @@ void populate_pressure_information(const struct dive *dive, const struct divecom // current pressure track entry and continue // until we get back to this cylinder. if (cyl != sensor) { - current = NULL; + current = std::string::npos; set_plot_pressure_data(pi, i, SENSOR_PR, sensor, 0); continue; } @@ -453,7 +388,7 @@ void populate_pressure_information(const struct dive *dive, const struct divecom // continue with or without a tracking entry. Mark any // existing tracking entry as non-dense, and remember // to fill in interpolated data. - if (current && !pressure) { + if (current != std::string::npos && !pressure) { missing_pr = 1; dense = 0; continue; @@ -462,7 +397,7 @@ void populate_pressure_information(const struct dive *dive, const struct divecom // If we already have a pressure tracking entry, and // it has not had any missing samples, just continue // using it - there's nothing to interpolate yet. - if (current && dense) + if (current != std::string::npos && dense) continue; // We need to start a new tracking entry, either @@ -471,18 +406,15 @@ void populate_pressure_information(const struct dive *dive, const struct divecom // missing entries that need to be interpolated. // Or maybe we didn't have a previous one at all, // and this is the first pressure entry. - current = pr_track_alloc(pressure, entry->sec); - track = list_add(track, current); + track.emplace_back(pressure, entry.sec); + current = track.size() - 1; dense = 1; } - if (missing_pr) { + if (missing_pr) fill_missing_tank_pressures(dive, pi, track, sensor); - } #ifdef PRINT_PRESSURES_DEBUG debug_print_pressures(pi); #endif - - list_free(track); } diff --git a/core/gaspressures.h b/core/gaspressures.h index a2d764816..d6755c625 100644 --- a/core/gaspressures.h +++ b/core/gaspressures.h @@ -2,13 +2,6 @@ #ifndef GASPRESSURES_H #define GASPRESSURES_H -#ifdef __cplusplus -extern "C" { -#endif +void populate_pressure_information(const struct dive *, const struct divecomputer *, struct plot_info &, int); -void populate_pressure_information(const struct dive *, const struct divecomputer *, struct plot_info *, int); - -#ifdef __cplusplus -} -#endif #endif // GASPRESSURES_H diff --git a/core/gettext.h b/core/gettext.h index 806154007..b0a70c067 100644 --- a/core/gettext.h +++ b/core/gettext.h @@ -2,22 +2,10 @@ #ifndef MYGETTEXT_H #define MYGETTEXT_H -#ifdef __cplusplus - -extern "C" const char *trGettext(const char *); +const char *trGettext(const char *); static inline const char *translate(const char *, const char *arg) { return trGettext(arg); } -#else - -/* this is for the Qt based translations */ -extern const char *trGettext(const char *); -#define translate(_context, arg) trGettext(arg) -#define QT_TRANSLATE_NOOP(_context, arg) arg -#define QT_TRANSLATE_NOOP3(_context, arg, _comment) arg - -#endif - #endif // MYGETTEXT_H diff --git a/core/gettextfromc.cpp b/core/gettextfromc.cpp index c3a592f5b..0dab3db83 100644 --- a/core/gettextfromc.cpp +++ b/core/gettextfromc.cpp @@ -6,7 +6,7 @@ static QHash translationCache; static QMutex lock; -extern "C" const char *trGettext(const char *text) +const char *trGettext(const char *text) { QByteArray key(text); QMutexLocker l(&lock); diff --git a/core/gettextfromc.h b/core/gettextfromc.h index 647ea52a8..23b407f9a 100644 --- a/core/gettextfromc.h +++ b/core/gettextfromc.h @@ -4,7 +4,7 @@ #include -extern "C" const char *trGettext(const char *text); +const char *trGettext(const char *text); class gettextFromC { Q_DECLARE_TR_FUNCTIONS(gettextFromC) diff --git a/core/git-access.cpp b/core/git-access.cpp index 1c9678c98..37445747b 100644 --- a/core/git-access.cpp +++ b/core/git-access.cpp @@ -4,7 +4,6 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "ssrf.h" #include #include #include @@ -58,7 +57,7 @@ static bool includes_string_caseinsensitive(const char *haystack, const char *ne return 0; } -extern "C" void set_git_update_cb(int(*cb)(const char *)) +void set_git_update_cb(int(*cb)(const char *)) { update_progress_cb = cb; } @@ -69,7 +68,7 @@ extern "C" void set_git_update_cb(int(*cb)(const char *)) // proportional - some parts are based on compute performance, some on network speed) // they also provide information where in the process we are so we can analyze the log // to understand which parts of the process take how much time. -extern "C" int git_storage_update_progress(const char *text) +int git_storage_update_progress(const char *text) { int ret = 0; if (update_progress_cb) @@ -135,9 +134,6 @@ std::string normalize_cloud_name(const std::string &remote_in) std::string get_local_dir(const std::string &url, const std::string &branch) { - SHA_CTX ctx; - unsigned char hash[20]; - // this optimization could in theory lead to odd things happening if the // cloud backend servers ever get out of sync - but when a user switches // between those servers (either because one is down, or because the algorithm @@ -148,13 +144,13 @@ std::string get_local_dir(const std::string &url, const std::string &branch) // That zero-byte update is so that we don't get hash // collisions for "repo1 branch" vs "repo 1branch". - SHA1_Init(&ctx); - SHA1_Update(&ctx, remote.c_str(), remote.size()); - SHA1_Update(&ctx, "", 1); - SHA1_Update(&ctx, branch.c_str(), branch.size()); - SHA1_Final(hash, &ctx); + SHA1 sha; + sha.update(remote); + sha.update("", 1); + sha.update(branch); + auto hash = sha.hash(); return format_string_std("%s/cloudstorage/%02x%02x%02x%02x%02x%02x%02x%02x", - system_default_directory(), + system_default_directory().c_str(), hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7]); } @@ -244,39 +240,39 @@ static bool exceeded_auth_attempts() return false; } -extern "C" int credential_ssh_cb(git_cred **out, +int credential_ssh_cb(git_cred **out, const char *, const char *, unsigned int allowed_types, void *) { - const char *username = prefs.cloud_storage_email_encoded; - const char *passphrase = prefs.cloud_storage_password ? prefs.cloud_storage_password : ""; + const std::string &username = prefs.cloud_storage_email_encoded; + const std::string &passphrase = prefs.cloud_storage_password; // TODO: We need a way to differentiate between password and private key authentication if (allowed_types & GIT_CREDTYPE_SSH_KEY) { - std::string priv_key = std::string(system_default_directory()) + "/ssrf_remote.key"; + std::string priv_key = system_default_directory() + "/ssrf_remote.key"; if (!access(priv_key.c_str(), F_OK)) { if (exceeded_auth_attempts()) return GIT_EUSER; - return git_cred_ssh_key_new(out, username, NULL, priv_key.c_str(), passphrase); + return git_cred_ssh_key_new(out, username.c_str(), NULL, priv_key.c_str(), passphrase.c_str()); } } if (allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT) { if (exceeded_auth_attempts()) return GIT_EUSER; - return git_cred_userpass_plaintext_new(out, username, passphrase); + return git_cred_userpass_plaintext_new(out, username.c_str(), passphrase.c_str()); } if (allowed_types & GIT_CREDTYPE_USERNAME) - return git_cred_username_new(out, username); + return git_cred_username_new(out, username.c_str()); report_error("No supported ssh authentication."); return GIT_EUSER; } -extern "C" int credential_https_cb(git_cred **out, +int credential_https_cb(git_cred **out, const char *, const char *, unsigned int, @@ -285,13 +281,13 @@ extern "C" int credential_https_cb(git_cred **out, if (exceeded_auth_attempts()) return GIT_EUSER; - const char *username = prefs.cloud_storage_email_encoded; - const char *password = prefs.cloud_storage_password ? prefs.cloud_storage_password : ""; + const std::string &username = prefs.cloud_storage_email_encoded; + const std::string &password = prefs.cloud_storage_password; - return git_cred_userpass_plaintext_new(out, username, password); + return git_cred_userpass_plaintext_new(out, username.c_str(), password.c_str()); } -extern "C" int certificate_check_cb(git_cert *cert, int valid, const char *host, void *) +int certificate_check_cb(git_cert *cert, int valid, const char *host, void *) { if (verbose) report_info("git storage: certificate callback for host %s with validity %d\n", host, valid); @@ -342,7 +338,7 @@ static int update_remote(struct git_info *info, git_remote *origin, git_referenc return 0; } -extern "C" int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree); +int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree); static int try_to_git_merge(struct git_info *info, git_reference **local_p, git_reference *, git_oid *base, const git_oid *local_id, const git_oid *remote_id) { @@ -350,7 +346,7 @@ static int try_to_git_merge(struct git_info *info, git_reference **local_p, git_ git_commit *local_commit, *remote_commit, *base_commit; git_index *merged_index; git_merge_options merge_options; - struct membufferpp msg; + membuffer msg; if (verbose) { char outlocal[41], outremote[41]; @@ -613,10 +609,10 @@ static std::string getProxyString() { if (prefs.proxy_type == QNetworkProxy::HttpProxy) { if (prefs.proxy_auth) - return format_string_std("http://%s:%s@%s:%d", prefs.proxy_user, prefs.proxy_pass, - prefs.proxy_host, prefs.proxy_port); + return format_string_std("http://%s:%s@%s:%d", prefs.proxy_user.c_str(), prefs.proxy_pass.c_str(), + prefs.proxy_host.c_str(), prefs.proxy_port); else - return format_string_std("http://%s:%d", prefs.proxy_host, prefs.proxy_port); + return format_string_std("http://%s:%d", prefs.proxy_host.c_str(), prefs.proxy_port); } return std::string(); } @@ -994,7 +990,7 @@ std::string extract_username(struct git_info *info, const std::string &url) * Ugly, ugly. Parsing the remote repo user name also sets * it in the preferences. We should do this somewhere else! */ - prefs.cloud_storage_email_encoded = strdup(info->username.c_str()); + prefs.cloud_storage_email_encoded = info->username; return url.substr(at + 1 - url.c_str()); } @@ -1111,7 +1107,7 @@ bool is_git_repository(const char *filename, struct git_info *info) * * This is used to create more user friendly error message and warnings. */ - info->is_subsurface_cloud = (strstr(info->url.c_str(), prefs.cloud_base_url) != NULL); + info->is_subsurface_cloud = contains(info->url, prefs.cloud_base_url); return true; } diff --git a/core/git-access.h b/core/git-access.h index 8dedebed5..e596e8b82 100644 --- a/core/git-access.h +++ b/core/git-access.h @@ -4,14 +4,12 @@ #include "git2.h" #include "filterpreset.h" +#include struct dive_log; - -#ifdef __cplusplus -extern "C" { -#else -#include -#endif +struct git_oid; +struct git_repository; +struct divelog; #define CLOUD_HOST_US "ssrf-cloud-us.subsurface-divelog.org" // preferred (faster/bigger) server in the US #define CLOUD_HOST_U2 "ssrf-cloud-u2.subsurface-divelog.org" // secondary (older) server in the US @@ -24,21 +22,12 @@ enum remote_transport { RT_LOCAL, RT_HTTPS, RT_SSH, RT_OTHER }; extern bool git_local_only; extern bool git_remote_sync_successful; -extern void clear_git_id(void); +extern void clear_git_id(); extern void set_git_id(const struct git_oid *); void set_git_update_cb(int(*)(const char *)); int git_storage_update_progress(const char *text); int get_authorship(git_repository *repo, git_signature **authorp); -#ifdef __cplusplus -} - -#include - -struct git_oid; -struct git_repository; -struct divelog; - struct git_info { std::string url; std::string branch; @@ -63,6 +52,4 @@ extern int git_load_dives(struct git_info *, struct divelog *log); extern int do_git_save(struct git_info *, bool select_only, bool create_empty); extern int git_create_local_repo(const std::string &filename); -#endif #endif // GITACCESS_H - diff --git a/core/imagedownloader.cpp b/core/imagedownloader.cpp index 54beff644..9b5d3e558 100644 --- a/core/imagedownloader.cpp +++ b/core/imagedownloader.cpp @@ -14,6 +14,9 @@ #include #include #include +#ifdef LIBRAW_SUPPORT +#include +#endif #include @@ -79,12 +82,40 @@ static bool hasVideoFileExtension(const QString &filename) return false; } -// Fetch a picture from the given filename and determine its type (picture of video). +#ifdef LIBRAW_SUPPORT +QImage fetchRawThumbnail(const QString &filename) +{ + LibRaw raw; // Might think about reusing that, one instance per thread + + // TODO: Convert filename to UTF-16 for windows + if (raw.open_file(qPrintable(filename)) != LIBRAW_SUCCESS || + raw.unpack_thumb() != LIBRAW_SUCCESS) { + return QImage(); + } + + switch (raw.imgdata.thumbnail.tformat) { + case LIBRAW_THUMBNAIL_JPEG: { + QImage res; + res.loadFromData(reinterpret_cast(raw.imgdata.thumbnail.thumb), + raw.imgdata.thumbnail.tlength); + return res; + } + case LIBRAW_THUMBNAIL_BITMAP: + return QImage(reinterpret_cast(raw.imgdata.thumbnail.thumb), + raw.imgdata.thumbnail.twidth, raw.imgdata.thumbnail.theight, + QImage::Format_RGB888); + default: // Unsupported + return QImage(); + } +} + +#endif + +// Fetch a picture from the given filename and determine its type (picture or video). // If this is a non-remote file, fetch it from disk. Remote files are fetched from the // net in a background thread. In such a case, the output-type is set to MEDIATYPE_STILL_LOADING. // If the input-flag "tryDownload" is set to false, no download attempt is made. This is to // prevent infinite loops, where failed image downloads would be repeated ad infinitum. -// Returns: fetched image, type Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &urlfilename, const QString &originalFilename, bool tryDownload) { QUrl url = QUrl::fromUserInput(urlfilename); @@ -96,12 +127,18 @@ Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &urlfilename, const // For io error or video, return early with the appropriate dummy-icon. if (type == MEDIATYPE_IO_ERROR) - return { failImage, MEDIATYPE_IO_ERROR, zero_duration }; + return { failImage, MEDIATYPE_IO_ERROR, duration_t() }; else if (type == MEDIATYPE_VIDEO) return fetchVideoThumbnail(filename, originalFilename, md.duration); // Try if Qt can parse this image. If it does, use this as a thumbnail. QImage thumb(filename); + +#ifdef LIBRAW_SUPPORT + // If note, perhaps a raw image? + if (thumb.isNull()) + thumb = fetchRawThumbnail(filename); +#endif if (!thumb.isNull()) { int size = maxThumbnailSize(); thumb = thumb.scaled(size, size, Qt::KeepAspectRatio); @@ -112,7 +149,7 @@ Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &urlfilename, const // Try to check for a video-file extension. Since we couldn't parse the video file, // we pass 0 as the duration. if (hasVideoFileExtension(filename)) - return fetchVideoThumbnail(filename, originalFilename, zero_duration); + return fetchVideoThumbnail(filename, originalFilename, duration_t()); // Give up: we simply couldn't determine what this thing is. // But since we managed to read this file, mark this file in the cache as unknown. @@ -122,9 +159,9 @@ Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &urlfilename, const // to treat requests from other threads. invokeMethod() is Qt's way of calling a // function in a different thread, namely the thread the called object is associated to. QMetaObject::invokeMethod(ImageDownloader::instance(), "load", Qt::AutoConnection, Q_ARG(QUrl, url), Q_ARG(QString, originalFilename)); - return { QImage(), MEDIATYPE_STILL_LOADING, zero_duration }; + return { QImage(), MEDIATYPE_STILL_LOADING, duration_t() }; } - return { QImage(), MEDIATYPE_IO_ERROR, zero_duration }; + return { QImage(), MEDIATYPE_IO_ERROR, duration_t() }; } // Fetch a picture based on its original filename. If there is a translated filename (obtained either @@ -140,7 +177,7 @@ Thumbnailer::Thumbnail Thumbnailer::getHashedImage(const QString &filename, bool // If there is a translated filename, try that first. // Note that we set the default type to io-error, so that if we didn't try // the local filename first, we will load the file from the canonical filename. - Thumbnail thumbnail { QImage(), MEDIATYPE_IO_ERROR, zero_duration }; + Thumbnail thumbnail { QImage(), MEDIATYPE_IO_ERROR, duration_t() }; if (localFilename != filename) thumbnail = fetchImage(localFilename, filename, tryDownload); @@ -187,7 +224,7 @@ Thumbnailer::Thumbnail Thumbnailer::getPictureThumbnailFromStream(QDataStream &s { QImage res; stream >> res; - return { std::move(res), MEDIATYPE_PICTURE, zero_duration }; + return { std::move(res), MEDIATYPE_PICTURE, duration_t() }; } void Thumbnailer::markVideoThumbnail(QImage &img) @@ -210,14 +247,14 @@ Thumbnailer::Thumbnail Thumbnailer::getVideoThumbnailFromStream(QDataStream &str // Likewise test the duration and number of pictures for sanity (no videos longer than 10 h, // no more than 10000 pictures). if (stream.status() != QDataStream::Ok || duration > 36000 || numPics > 10000) - return { QImage(), MEDIATYPE_VIDEO, zero_duration }; + return { QImage(), MEDIATYPE_VIDEO, duration_t() }; // If the file didn't contain an image, but user turned on thumbnail extraction, schedule thumbnail // for extraction. TODO: save failure to extract thumbnails to disk so that thumbnailing // is not repeated ad-nauseum for broken images. if (numPics == 0 && prefs.extract_video_thumbnails) { QMetaObject::invokeMethod(VideoFrameExtractor::instance(), "extract", Qt::AutoConnection, - Q_ARG(QString, filename), Q_ARG(QString, filename), Q_ARG(duration_t, duration_t{(int32_t)duration})); + Q_ARG(QString, filename), Q_ARG(QString, filename), Q_ARG(duration_t, duration_t{ .seconds = (int32_t)duration})); } // Currently, we support only one picture @@ -231,7 +268,7 @@ Thumbnailer::Thumbnail Thumbnailer::getVideoThumbnailFromStream(QDataStream &str res = videoImage; // No picture -> show dummy-icon else markVideoThumbnail(res); // We got an image -> place our video marker on top of it - return { res, MEDIATYPE_VIDEO, { (int32_t)duration } }; + return { res, MEDIATYPE_VIDEO, { .seconds = (int32_t)duration } }; } // Fetch a thumbnail from cache. @@ -240,7 +277,7 @@ Thumbnailer::Thumbnail Thumbnailer::getThumbnailFromCache(const QString &picture { QString filename = thumbnailFileName(picture_filename); if (filename.isEmpty()) - return { QImage(), MEDIATYPE_UNKNOWN, zero_duration }; + return { QImage(), MEDIATYPE_UNKNOWN, duration_t() }; QFile file(filename); if (prefs.auto_recalculate_thumbnails) { @@ -254,13 +291,13 @@ Thumbnailer::Thumbnail Thumbnailer::getThumbnailFromCache(const QString &picture if (pictureTime.isValid() && thumbnailTime.isValid() && thumbnailTime < pictureTime) { // Both files exist, have valid timestamps and thumbnail was calculated before picture. // Return an empty thumbnail to signal recalculation of the thumbnail - return { QImage(), MEDIATYPE_UNKNOWN, zero_duration }; + return { QImage(), MEDIATYPE_UNKNOWN, duration_t() }; } } } if (!file.open(QIODevice::ReadOnly)) - return { QImage(), MEDIATYPE_UNKNOWN, zero_duration }; + return { QImage(), MEDIATYPE_UNKNOWN, duration_t() }; QDataStream stream(&file); // Each thumbnail file is composed of a media-type and an image file. @@ -271,8 +308,8 @@ Thumbnailer::Thumbnail Thumbnailer::getThumbnailFromCache(const QString &picture switch (type) { case MEDIATYPE_PICTURE: return getPictureThumbnailFromStream(stream); case MEDIATYPE_VIDEO: return getVideoThumbnailFromStream(stream, picture_filename); - case MEDIATYPE_UNKNOWN: return { unknownImage, MEDIATYPE_UNKNOWN, zero_duration }; - default: return { QImage(), MEDIATYPE_UNKNOWN, zero_duration }; + case MEDIATYPE_UNKNOWN: return { unknownImage, MEDIATYPE_UNKNOWN, duration_t() }; + default: return { QImage(), MEDIATYPE_UNKNOWN, duration_t() }; } } @@ -319,7 +356,7 @@ Thumbnailer::Thumbnail Thumbnailer::fetchVideoThumbnail(const QString &filename, return { videoImage, MEDIATYPE_VIDEO, duration }; } else { // Video-thumbnailing is disabled. Write a thumbnail without picture. - return addVideoThumbnailToCache(originalFilename, duration, QImage(), zero_duration); + return addVideoThumbnailToCache(originalFilename, duration, QImage(), duration_t()); } } @@ -337,7 +374,7 @@ Thumbnailer::Thumbnail Thumbnailer::addPictureThumbnailToCache(const QString &pi stream << thumbnail; file.commit(); } - return { thumbnail, MEDIATYPE_PICTURE, zero_duration }; + return { thumbnail, MEDIATYPE_PICTURE, duration_t() }; } Thumbnailer::Thumbnail Thumbnailer::addUnknownThumbnailToCache(const QString &picture_filename) @@ -348,7 +385,7 @@ Thumbnailer::Thumbnail Thumbnailer::addUnknownThumbnailToCache(const QString &pi QDataStream stream(&file); stream << (quint32)MEDIATYPE_UNKNOWN; } - return { unknownImage, MEDIATYPE_UNKNOWN, zero_duration }; + return { unknownImage, MEDIATYPE_UNKNOWN, duration_t() }; } void Thumbnailer::frameExtracted(QString filename, QImage thumbnail, duration_t duration, duration_t offset) @@ -374,7 +411,7 @@ void Thumbnailer::frameExtractionFailed(QString filename, duration_t duration) { // Frame extraction failed, but this was due to ffmpeg not starting // add to the thumbnail cache as a video image with unknown thumbnail. - addVideoThumbnailToCache(filename, duration, QImage(), zero_duration); + addVideoThumbnailToCache(filename, duration, QImage(), duration_t()); QMutexLocker l(&lock); workingOn.remove(filename); } @@ -435,7 +472,7 @@ void Thumbnailer::imageDownloaded(QString filename) void Thumbnailer::imageDownloadFailed(QString filename) { - emit thumbnailChanged(filename, failImage, zero_duration); + emit thumbnailChanged(filename, failImage, duration_t()); QMutexLocker l(&lock); workingOn.remove(filename); } diff --git a/core/import-cobalt.cpp b/core/import-cobalt.cpp index d79a9a35e..de54a2d16 100644 --- a/core/import-cobalt.cpp +++ b/core/import-cobalt.cpp @@ -5,7 +5,6 @@ #endif #include -#include "ssrf.h" #include "dive.h" #include "divesite.h" #include "errorhelper.h" @@ -29,7 +28,7 @@ static int cobalt_profile_sample(void *param, int, char **data, char **) if (data[1]) state->cur_sample->depth.mm = atoi(data[1]); if (data[2]) - state->cur_sample->temperature.mkelvin = state->metric ? C_to_mkelvin(strtod_flags(data[2], NULL, 0)) : F_to_mkelvin(strtod_flags(data[2], NULL, 0)); + state->cur_sample->temperature.mkelvin = state->metric ? C_to_mkelvin(permissive_strtod(data[2], NULL)) : F_to_mkelvin(permissive_strtod(data[2], NULL)); sample_end(state); return 0; @@ -64,7 +63,7 @@ static int cobalt_buddies(void *param, int, char **data, char **) struct parser_state *state = (struct parser_state *)param; if (data[0]) - utf8_string(data[0], &state->cur_dive->buddy); + utf8_string_std(data[0], &state->cur_dive->buddy); return 0; } @@ -81,8 +80,8 @@ static int cobalt_visibility(void *, int, char **, char **) static int cobalt_location(void *param, int, char **data, char **) { - char **location = (char **)param; - *location = data[0] ? strdup(data[0]) : NULL; + std::string *location = (std::string *)param; + *location = data[0] ? data[0] : NULL; return 0; } @@ -92,7 +91,7 @@ static int cobalt_dive(void *param, int, char **data, char **) int retval = 0; struct parser_state *state = (struct parser_state *)param; sqlite3 *handle = state->sql_handle; - char *location, *location_site; + std::string location, location_site; char get_profile_template[] = "select runtime*60,(DepthPressure*10000/SurfacePressure)-10000,p.Temperature from Dive AS d JOIN TrackPoints AS p ON d.Id=p.DiveId where d.Id=%d"; char get_cylinder_template[] = "select FO2,FHe,StartingPressure,EndingPressure,TankSize,TankPressure,TotalConsumption from GasMixes where DiveID=%d and StartingPressure>0 and EndingPressure > 0 group by FO2,FHe"; char get_buddy_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=4"; @@ -107,7 +106,7 @@ static int cobalt_dive(void *param, int, char **data, char **) state->cur_dive->when = (time_t)(atol(data[1])); if (data[4]) - utf8_string(data[4], &state->cur_dive->notes); + utf8_string_std(data[4], &state->cur_dive->notes); /* data[5] should have information on Units used, but I cannot * parse it at all based on the sample log I have received. The @@ -119,13 +118,13 @@ static int cobalt_dive(void *param, int, char **data, char **) /* Cobalt stores the pressures, not the depth */ if (data[6]) - state->cur_dive->dc.maxdepth.mm = atoi(data[6]); + state->cur_dive->dcs[0].maxdepth.mm = atoi(data[6]); if (data[7]) - state->cur_dive->dc.duration.seconds = atoi(data[7]); + state->cur_dive->dcs[0].duration.seconds = atoi(data[7]); if (data[8]) - state->cur_dive->dc.surface_pressure.mbar = atoi(data[8]); + state->cur_dive->dcs[0].surface_pressure.mbar = atoi(data[8]); /* * TODO: the deviceid hash should be calculated here. */ @@ -134,15 +133,15 @@ static int cobalt_dive(void *param, int, char **data, char **) if (data[9]) { utf8_string_std(data[9], &state->cur_settings.dc.serial_nr); state->cur_settings.dc.deviceid = atoi(data[9]); - state->cur_settings.dc.model = strdup("Cobalt import"); + state->cur_settings.dc.model = "Cobalt import"; } dc_settings_end(state); settings_end(state); if (data[9]) { - state->cur_dive->dc.deviceid = atoi(data[9]); - state->cur_dive->dc.model = strdup("Cobalt import"); + state->cur_dive->dcs[0].deviceid = atoi(data[9]); + state->cur_dive->dcs[0].model = "Cobalt import"; } snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, state->cur_dive->number); @@ -180,19 +179,10 @@ static int cobalt_dive(void *param, int, char **data, char **) return 1; } - if (location && location_site) { - char *tmp = (char *)malloc(strlen(location) + strlen(location_site) + 4); - if (!tmp) { - free(location); - free(location_site); - return 1; - } - sprintf(tmp, "%s / %s", location, location_site); - add_dive_to_dive_site(state->cur_dive, find_or_create_dive_site_with_name(tmp, state->log->sites)); - free(tmp); + if (!location.empty() && !location_site.empty()) { + std::string tmp = location + " / " + location_site; + state->log->sites.find_or_create(tmp)->add_dive(state->cur_dive.get()); } - free(location); - free(location_site); snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, state->cur_dive->number); retval = sqlite3_exec(handle, get_buffer, &cobalt_profile_sample, state, NULL); @@ -206,8 +196,7 @@ static int cobalt_dive(void *param, int, char **data, char **) return SQLITE_OK; } - -extern "C" int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) +int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) { int retval; struct parser_state state; diff --git a/core/import-csv.cpp b/core/import-csv.cpp index 777db16a9..281653d6d 100644 --- a/core/import-csv.cpp +++ b/core/import-csv.cpp @@ -5,7 +5,6 @@ #include "dive.h" #include "errorhelper.h" -#include "ssrf.h" #include "subsurface-string.h" #include "divelist.h" #include "divelog.h" @@ -270,7 +269,7 @@ static int parse_dan_format(const char *filename, struct xml_params *params, str return ret; } -extern "C" int parse_csv_file(const char *filename, struct xml_params *params, const char *csvtemplate, struct divelog *log) +int parse_csv_file(const char *filename, struct xml_params *params, const char *csvtemplate, struct divelog *log) { int ret; std::string mem; @@ -402,7 +401,6 @@ int try_to_open_csv(std::string &mem, enum csv_format type, struct divelog *log) char *header[8]; int i, time; timestamp_t date; - struct dive *dive; struct divecomputer *dc; for (i = 0; i < 8; i++) { @@ -417,10 +415,10 @@ int try_to_open_csv(std::string &mem, enum csv_format type, struct divelog *log) if (!date) return 0; - dive = alloc_dive(); + auto dive = std::make_unique(); dive->when = date; dive->number = atoi(header[1]); - dc = &dive->dc; + dc = &dive->dcs[0]; time = 0; for (;;) { @@ -438,7 +436,6 @@ int try_to_open_csv(std::string &mem, enum csv_format type, struct divelog *log) sample = prepare_sample(dc); sample->time.seconds = time; add_sample_data(sample, type, val); - finish_sample(dc); time++; dc->duration.seconds = time; @@ -446,7 +443,7 @@ int try_to_open_csv(std::string &mem, enum csv_format type, struct divelog *log) break; p = end + 1; } - record_dive_to_table(dive, log->dives); + log->dives.record_dive(std::move(dive)); return 1; } @@ -497,9 +494,7 @@ int parse_txt_file(const char *filename, const char *csv, struct divelog *log) bool has_depth = false, has_setpoint = false, has_ndl = false; char *lineptr; int prev_time = 0; - cylinder_t cyl = empty_cylinder; - struct dive *dive; struct divecomputer *dc; struct tm cur_tm; @@ -513,34 +508,40 @@ int parse_txt_file(const char *filename, const char *csv, struct divelog *log) cur_tm.tm_min = mm; cur_tm.tm_sec = ss; - dive = alloc_dive(); + auto dive = std::make_unique(); dive->when = utc_mktime(&cur_tm);; - dive->dc.model = strdup("Poseidon MkVI Discovery"); + dive->dcs[0].model = "Poseidon MkVI Discovery"; value = parse_mkvi_value(memtxt.data(), "Rig Serial number"); - dive->dc.deviceid = atoi(value.c_str()); - dive->dc.divemode = CCR; - dive->dc.no_o2sensors = 2; + dive->dcs[0].deviceid = atoi(value.c_str()); + dive->dcs[0].divemode = CCR; + dive->dcs[0].no_o2sensors = 2; - cyl.cylinder_use = OXYGEN; - cyl.type.size.mliter = 3000; - cyl.type.workingpressure.mbar = 200000; - cyl.type.description = "3l Mk6"; - cyl.gasmix.o2.permille = 1000; - cyl.manually_added = true; - cyl.bestmix_o2 = 0; - cyl.bestmix_he = 0; - add_cloned_cylinder(&dive->cylinders, cyl); + { + cylinder_t cyl; + cyl.cylinder_use = OXYGEN; + cyl.type.size = 3_l; + cyl.type.workingpressure = 200_bar; + cyl.type.description = "3l Mk6"; + cyl.gasmix.o2 = 100_percent; + cyl.manually_added = true; + cyl.bestmix_o2 = 0; + cyl.bestmix_he = 0; + dive->cylinders.push_back(std::move(cyl)); + } - cyl.cylinder_use = DILUENT; - cyl.type.size.mliter = 3000; - cyl.type.workingpressure.mbar = 200000; - cyl.type.description = "3l Mk6"; - value = parse_mkvi_value(memtxt.data(), "Helium percentage"); - he = atoi(value.c_str()); - value = parse_mkvi_value(memtxt.data(), "Nitrogen percentage"); - cyl.gasmix.o2.permille = (100 - atoi(value.c_str()) - he) * 10; - cyl.gasmix.he.permille = he * 10; - add_cloned_cylinder(&dive->cylinders, cyl); + { + cylinder_t cyl; + cyl.cylinder_use = DILUENT; + cyl.type.size = 3_l; + cyl.type.workingpressure = 200_bar; + cyl.type.description = "3l Mk6"; + value = parse_mkvi_value(memtxt.data(), "Helium percentage"); + he = atoi(value.c_str()); + value = parse_mkvi_value(memtxt.data(), "Nitrogen percentage"); + cyl.gasmix.o2.permille = (100 - atoi(value.c_str()) - he) * 10; + cyl.gasmix.he.permille = he * 10; + dive->cylinders.push_back(std::move(cyl)); + } lineptr = strstr(memtxt.data(), "Dive started at"); while (!empty_string(lineptr) && (lineptr = strchr(lineptr, '\n'))) { @@ -551,9 +552,9 @@ int parse_txt_file(const char *filename, const char *csv, struct divelog *log) std::string value = parse_mkvi_value(lineptr, key.c_str()); if (value.empty()) break; - add_extra_data(&dive->dc, key.c_str(), value.c_str()); + add_extra_data(&dive->dcs[0], key, value); } - dc = &dive->dc; + dc = &dive->dcs[0]; /* * Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has @@ -573,10 +574,8 @@ int parse_txt_file(const char *filename, const char *csv, struct divelog *log) */ auto [memcsv, err] = readfile(csv); - if (err < 0) { - free_dive(dive); + if (err < 0) return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv); - } lineptr = memcsv.data(); for (;;) { struct sample *sample; @@ -746,12 +745,11 @@ int parse_txt_file(const char *filename, const char *csv, struct divelog *log) add_sample_data(sample, POSEIDON_SETPOINT, prev_setpoint); if (!has_ndl && prev_ndl >= 0) add_sample_data(sample, POSEIDON_NDL, prev_ndl); - finish_sample(dc); if (!lineptr || !*lineptr) break; } - record_dive_to_table(dive, log->dives); + log->dives.record_dive(std::move(dive)); return 1; } else { return 0; diff --git a/core/import-csv.h b/core/import-csv.h index b71ad2308..7247a516a 100644 --- a/core/import-csv.h +++ b/core/import-csv.h @@ -21,10 +21,6 @@ enum csv_format { #define MAXCOLDIGITS 10 -#ifdef __cplusplus -extern "C" { -#endif - int parse_csv_file(const char *filename, struct xml_params *params, const char *csvtemplate, struct divelog *log); int try_to_open_csv(std::string &mem, enum csv_format type, struct divelog *log); int parse_txt_file(const char *filename, const char *csv, struct divelog *log); @@ -32,8 +28,4 @@ int parse_txt_file(const char *filename, const char *csv, struct divelog *log); int parse_seabear_log(const char *filename, struct divelog *log); int parse_manual_file(const char *filename, struct xml_params *params, struct divelog *log); -#ifdef __cplusplus -} -#endif - #endif // IMPORTCSV_H diff --git a/core/import-divinglog.cpp b/core/import-divinglog.cpp index 8b4230721..5519411a3 100644 --- a/core/import-divinglog.cpp +++ b/core/import-divinglog.cpp @@ -4,7 +4,6 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "ssrf.h" #include "dive.h" #include "divesite.h" #include "sample.h" @@ -17,6 +16,15 @@ #include "membuffer.h" #include "gettext.h" +#include + +static int atoi_n(const char *ptr, size_t len) +{ + int res = 0; + std::from_chars(ptr, ptr + len, res); + return res; +} + static int divinglog_cylinder(void *param, int, char **data, char **) { struct parser_state *state = (struct parser_state *)param; @@ -56,6 +64,8 @@ static int divinglog_cylinder(void *param, int, char **data, char **) static int divinglog_profile(void *param, int, char **data, char **) { + using namespace std::string_literals; + struct parser_state *state = (struct parser_state *)param; int sinterval = 0; @@ -129,14 +139,14 @@ static int divinglog_profile(void *param, int, char **data, char **) state->cur_sample->temperature.mkelvin = C_to_mkelvin(temp / 10.0f); state->cur_sample->pressure[0].mbar = pressure * 100; state->cur_sample->rbt.seconds = rbt; - if (oldcyl != tank && tank >= 0 && tank < state->cur_dive->cylinders.nr) { - struct gasmix mix = get_cylinder(state->cur_dive, tank)->gasmix; + if (oldcyl != tank && tank >= 0 && static_cast(tank) < state->cur_dive->cylinders.size()) { + struct gasmix mix = state->cur_dive.get()->get_cylinder(tank)->gasmix; int o2 = get_o2(mix); int he = get_he(mix); event_start(state); state->cur_event.time.seconds = time; - strcpy(state->cur_event.name, "gaschange"); + state->cur_event.name = "gaschange"s; o2 = (o2 + 5) / 10; he = (he + 5) / 10; @@ -161,7 +171,7 @@ static int divinglog_profile(void *param, int, char **data, char **) */ int val = atoi_n(ptr4, 3); if (state->cur_sample->in_deco) { - state->cur_sample->ndl.seconds = 0; + state->cur_sample->ndl = 0_sec; if (val) state->cur_sample->tts.seconds = val * 60; } else { @@ -211,8 +221,8 @@ static int divinglog_profile(void *param, int, char **data, char **) * Count the number of o2 sensors */ - if (!state->cur_dive->dc.no_o2sensors && (state->cur_sample->o2sensor[0].mbar || state->cur_sample->o2sensor[1].mbar || state->cur_sample->o2sensor[2].mbar)) { - state->cur_dive->dc.no_o2sensors = state->cur_sample->o2sensor[0].mbar ? 1 : 0 + + if (!state->cur_dive->dcs[0].no_o2sensors && (state->cur_sample->o2sensor[0].mbar || state->cur_sample->o2sensor[1].mbar || state->cur_sample->o2sensor[2].mbar)) { + state->cur_dive->dcs[0].no_o2sensors = state->cur_sample->o2sensor[0].mbar ? 1 : 0 + state->cur_sample->o2sensor[1].mbar ? 1 : 0 + state->cur_sample->o2sensor[2].mbar ? 1 : 0; } @@ -223,7 +233,7 @@ static int divinglog_profile(void *param, int, char **data, char **) if (ptr1[6] - '0') { event_start(state); state->cur_event.time.seconds = time; - strcpy(state->cur_event.name, "rbt"); + state->cur_event.name = "rbt"s; event_end(state); } @@ -231,7 +241,7 @@ static int divinglog_profile(void *param, int, char **data, char **) if (ptr1[7] - '0') { event_start(state); state->cur_event.time.seconds = time; - strcpy(state->cur_event.name, "ascent"); + state->cur_event.name = "ascent"s; event_end(state); } @@ -239,7 +249,7 @@ static int divinglog_profile(void *param, int, char **data, char **) if (ptr1[8] - '0') { event_start(state); state->cur_event.time.seconds = time; - strcpy(state->cur_event.name, "violation"); + state->cur_event.name = "violation"s; event_end(state); } @@ -247,7 +257,7 @@ static int divinglog_profile(void *param, int, char **data, char **) if (ptr1[9] - '0') { event_start(state); state->cur_event.time.seconds = time; - strcpy(state->cur_event.name, "workload"); + state->cur_event.name = "workload"s; event_end(state); } @@ -275,22 +285,22 @@ static int divinglog_dive(void *param, int, char **data, char **) state->cur_dive->when = (time_t)(atol(data[1])); if (data[2]) - add_dive_to_dive_site(state->cur_dive, find_or_create_dive_site_with_name(data[2], state->log->sites)); + state->log->sites.find_or_create(std::string(data[2]))->add_dive(state->cur_dive.get()); if (data[3]) - utf8_string(data[3], &state->cur_dive->buddy); + utf8_string_std(data[3], &state->cur_dive->buddy); if (data[4]) - utf8_string(data[4], &state->cur_dive->notes); + utf8_string_std(data[4], &state->cur_dive->notes); if (data[5]) - state->cur_dive->dc.maxdepth.mm = lrint(strtod_flags(data[5], NULL, 0) * 1000); + state->cur_dive->dcs[0].maxdepth.mm = lrint(permissive_strtod(data[5], NULL) * 1000); if (data[6]) - state->cur_dive->dc.duration.seconds = atoi(data[6]) * 60; + state->cur_dive->dcs[0].duration.seconds = atoi(data[6]) * 60; if (data[7]) - utf8_string(data[7], &state->cur_dive->diveguide); + utf8_string_std(data[7], &state->cur_dive->diveguide); if (data[8]) state->cur_dive->airtemp.mkelvin = C_to_mkelvin(atol(data[8])); @@ -299,12 +309,12 @@ static int divinglog_dive(void *param, int, char **data, char **) state->cur_dive->watertemp.mkelvin = C_to_mkelvin(atol(data[9])); if (data[10]) { - weightsystem_t ws = { { atoi(data[10]) * 1000 }, translate("gettextFromC", "unknown"), false }; - add_cloned_weightsystem(&state->cur_dive->weightsystems, ws); + weightsystem_t ws = { { .grams = atoi(data[10]) * 1000 }, translate("gettextFromC", "unknown"), false }; + state->cur_dive->weightsystems.push_back(std::move(ws)); } if (data[11]) - state->cur_dive->suit = strdup(data[11]); + state->cur_dive->suit = data[11]; /* Divinglog has following visibility options: good, medium, bad */ if (data[14]) { @@ -329,9 +339,9 @@ static int divinglog_dive(void *param, int, char **data, char **) dc_settings_start(state); if (data[12]) { - state->cur_dive->dc.model = strdup(data[12]); + state->cur_dive->dcs[0].model = data[12]; } else { - state->cur_settings.dc.model = strdup("Divinglog import"); + state->cur_settings.dc.model = "Divinglog import"; } snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder0_template, diveid); @@ -354,10 +364,10 @@ static int divinglog_dive(void *param, int, char **data, char **) case '0': break; case '1': - state->cur_dive->dc.divemode = PSCR; + state->cur_dive->dcs[0].divemode = PSCR; break; case '2': - state->cur_dive->dc.divemode = CCR; + state->cur_dive->dcs[0].divemode = CCR; break; } } @@ -366,9 +376,9 @@ static int divinglog_dive(void *param, int, char **data, char **) settings_end(state); if (data[12]) { - state->cur_dive->dc.model = strdup(data[12]); + state->cur_dive->dcs[0].model = data[12]; } else { - state->cur_dive->dc.model = strdup("Divinglog import"); + state->cur_dive->dcs[0].model = "Divinglog import"; } snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, diveid); @@ -384,7 +394,7 @@ static int divinglog_dive(void *param, int, char **data, char **) } -extern "C" int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) +int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) { int retval; struct parser_state state; diff --git a/core/import-seac.cpp b/core/import-seac.cpp index 07416e47e..eca2bdea2 100644 --- a/core/import-seac.cpp +++ b/core/import-seac.cpp @@ -6,7 +6,6 @@ #include #include "qthelper.h" -#include "ssrf.h" #include "dive.h" #include "sample.h" #include "subsurface-string.h" @@ -18,6 +17,7 @@ #include "gettext.h" #include "tag.h" #include "errorhelper.h" +#include "format.h" #include #include "divecomputer.h" @@ -27,11 +27,12 @@ */ static int seac_gaschange(void *param, sqlite3_stmt *sqlstmt) { + using namespace std::string_literals; struct parser_state *state = (struct parser_state *)param; event_start(state); state->cur_event.time.seconds = sqlite3_column_int(sqlstmt, 1); - strcpy(state->cur_event.name, "gaschange"); + state->cur_event.name = "gaschange"s; state->cur_event.gas.mix.o2.permille = 10 * sqlite3_column_int(sqlstmt, 4); event_end(state); @@ -45,7 +46,6 @@ static int seac_dive(void *param, int, char **data, char **) { int retval = 0, cylnum = 0; int year, month, day, hour, min, sec, tz; - char isodatetime[30]; time_t divetime; struct gasmix lastgas, curgas; struct parser_state *state = (struct parser_state *)param; @@ -70,7 +70,7 @@ static int seac_dive(void *param, int, char **data, char **) state->cur_dive->number = atoi(data[0]); // Create first cylinder - cylinder_t *curcyl = get_or_create_cylinder(state->cur_dive, 0); + cylinder_t *curcyl = state->cur_dive->get_or_create_cylinder(0); // Get time and date sscanf(data[2], "%d/%d/%2d", &day, &month, &year); @@ -123,8 +123,8 @@ static int seac_dive(void *param, int, char **data, char **) "-13:45", // 40 "-14:00"}; // 41 - sprintf(isodatetime, "%4i-%02i-%02iT%02i:%02i:%02i%6s", year, month, day, hour, min, sec, timezoneoffset[tz]); - divetime = get_dive_datetime_from_isostring(isodatetime); + std::string isodatetime = format_string_std("%4i-%02i-%02iT%02i:%02i:%02i%6s", year, month, day, hour, min, sec, timezoneoffset[tz]); + divetime = get_dive_datetime_from_isostring(isodatetime.c_str()); state->cur_dive->when = divetime; // 6 = dive_type @@ -132,14 +132,14 @@ static int seac_dive(void *param, int, char **data, char **) if (data[6]) { switch (atoi(data[6])) { case 1: - state->cur_dive->dc.divemode = OC; + state->cur_dive->dcs[0].divemode = OC; break; // Gauge Mode case 2: - state->cur_dive->dc.divemode = UNDEF_COMP_TYPE; + state->cur_dive->dcs[0].divemode = UNDEF_COMP_TYPE; break; case 3: - state->cur_dive->dc.divemode = FREEDIVE; + state->cur_dive->dcs[0].divemode = FREEDIVE; break; default: if (verbose) { @@ -150,12 +150,12 @@ static int seac_dive(void *param, int, char **data, char **) // 9 = comments from seac app if (data[9]) { - utf8_string(data[9], &state->cur_dive->notes); + utf8_string_std(data[9], &state->cur_dive->notes); } // 10 = dive duration if (data[10]) { - state->cur_dive->dc.duration.seconds = atoi(data[10]); + state->cur_dive->dcs[0].duration.seconds = atoi(data[10]); } // 8 = water_type @@ -181,7 +181,7 @@ static int seac_dive(void *param, int, char **data, char **) if (data[11]) { - state->cur_dive->dc.maxdepth.mm = 10 * atoi(data[11]); + state->cur_dive->dcs[0].maxdepth.mm = 10 * atoi(data[11]); } // Create sql_stmt type to query DB @@ -205,27 +205,24 @@ static int seac_dive(void *param, int, char **data, char **) settings_start(state); dc_settings_start(state); - // These dc values are const char *, therefore we have to cast. - // Will be fixed by converting to std::string - utf8_string(data[1], (char **)&state->cur_dive->dc.serial); - utf8_string(data[12], (char **)&state->cur_dive->dc.fw_version); - state->cur_dive->dc.model = strdup("Seac Action"); + utf8_string_std(data[1], &state->cur_dive->dcs[0].serial); + utf8_string_std(data[12],&state->cur_dive->dcs[0].fw_version); + state->cur_dive->dcs[0].model = "Seac Action"; - state->cur_dive->dc.deviceid = calculate_string_hash(data[1]); + state->cur_dive->dcs[0].deviceid = calculate_string_hash(data[1]); - add_extra_data(&state->cur_dive->dc, "GF-Lo", (const char*)sqlite3_column_text(sqlstmt, 9)); - add_extra_data(&state->cur_dive->dc, "GF-Hi", (const char*)sqlite3_column_text(sqlstmt, 10)); + add_extra_data(&state->cur_dive->dcs[0], "GF-Lo", (const char*)sqlite3_column_text(sqlstmt, 9)); + add_extra_data(&state->cur_dive->dcs[0], "GF-Hi", (const char*)sqlite3_column_text(sqlstmt, 10)); dc_settings_end(state); settings_end(state); if (data[11]) { - state->cur_dive->dc.maxdepth.mm = 10 * atoi(data[11]); + state->cur_dive->dcs[0].maxdepth.mm = 10 * atoi(data[11]); } curcyl->gasmix.o2.permille = 10 * sqlite3_column_int(sqlstmt, 4); - // Track gasses to tell when switch occurs lastgas = curcyl->gasmix; curgas = curcyl->gasmix; @@ -241,7 +238,7 @@ static int seac_dive(void *param, int, char **data, char **) seac_gaschange(state, sqlstmt); lastgas = curgas; cylnum ^= 1; // Only need to toggle between two cylinders - curcyl = get_or_create_cylinder(state->cur_dive, cylnum); + curcyl = state->cur_dive->get_or_create_cylinder(cylnum); curcyl->gasmix.o2.permille = 10 * sqlite3_column_int(sqlstmt, 4); } state->cur_sample->stopdepth.mm = 10 * sqlite3_column_int(sqlstmt, 5); @@ -264,7 +261,7 @@ static int seac_dive(void *param, int, char **data, char **) * The callback function performs another SQL query on the other * table, to read in the sample values. */ -extern "C" int parse_seac_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) +int parse_seac_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) { int retval; char *err = NULL; @@ -293,7 +290,8 @@ extern "C" int parse_seac_buffer(sqlite3 *handle, const char *url, const char *, retval = sqlite3_exec(handle, get_dives, &seac_dive, &state, &err); if (retval != SQLITE_OK) { - report_info("Database query failed '%s'.", url); + report_info("Database query failed '%s': %s.", url, err); + sqlite3_free(err); return 1; } diff --git a/core/import-shearwater.cpp b/core/import-shearwater.cpp index 07c3a6a8f..e977ee6de 100644 --- a/core/import-shearwater.cpp +++ b/core/import-shearwater.cpp @@ -4,7 +4,6 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "ssrf.h" #include "dive.h" #include "sample.h" #include "subsurface-string.h" @@ -23,8 +22,8 @@ static int shearwater_cylinders(void *param, int, char **data, char **) struct parser_state *state = (struct parser_state *)param; cylinder_t *cyl; - int o2 = lrint(strtod_flags(data[0], NULL, 0) * 1000); - int he = lrint(strtod_flags(data[1], NULL, 0) * 1000); + int o2 = lrint(permissive_strtod(data[0], NULL) * 1000); + int he = lrint(permissive_strtod(data[1], NULL) * 1000); /* Shearwater allows entering only 99%, not 100% * so assume 99% to be pure oxygen */ @@ -50,8 +49,8 @@ static int shearwater_changes(void *param, int columns, char **data, char **) if (!data[0] || !data[1] || !data[2]) { return 2; } - int o2 = lrint(strtod_flags(data[1], NULL, 0) * 1000); - int he = lrint(strtod_flags(data[2], NULL, 0) * 1000); + int o2 = lrint(permissive_strtod(data[1], NULL) * 1000); + int he = lrint(permissive_strtod(data[2], NULL) * 1000); /* Shearwater allows entering only 99%, not 100% * so assume 99% to be pure oxygen */ @@ -59,24 +58,20 @@ static int shearwater_changes(void *param, int columns, char **data, char **) o2 = 1000; // Find the cylinder index - int index; - bool found = false; - for (index = 0; index < state->cur_dive->cylinders.nr; ++index) { - const cylinder_t *cyl = get_cylinder(state->cur_dive, index); - if (cyl->gasmix.o2.permille == o2 && cyl->gasmix.he.permille == he) { - found = true; - break; - } - } - if (!found) { + auto it = std::find_if(state->cur_dive->cylinders.begin(), state->cur_dive->cylinders.end(), + [o2, he](auto &cyl) + { return cyl.gasmix.o2.permille == o2 && cyl.gasmix.he.permille == he; }); + if (it == state->cur_dive->cylinders.end()) { // Cylinder not found, creating a new one cyl = cylinder_start(state); cyl->gasmix.o2.permille = o2; cyl->gasmix.he.permille = he; cylinder_end(state); + it = std::prev(state->cur_dive->cylinders.end()); } - add_gas_switch_event(state->cur_dive, get_dc(state), state->sample_rate ? atoi(data[0]) / state->sample_rate * 10 : atoi(data[0]), index); + add_gas_switch_event(state->cur_dive.get(), get_dc(state), state->sample_rate ? atoi(data[0]) / state->sample_rate * 10 : atoi(data[0]), + it - state->cur_dive->cylinders.begin()); return 0; } @@ -101,12 +96,11 @@ static int shearwater_profile_sample(void *param, int, char **data, char **) if (data[1]) - state->cur_sample->depth.mm = state->metric ? lrint(strtod_flags(data[1], NULL, 0) * 1000) : feet_to_mm(strtod_flags(data[1], NULL, 0)); + state->cur_sample->depth.mm = state->metric ? lrint(permissive_strtod(data[1], NULL) * 1000) : feet_to_mm(permissive_strtod(data[1], NULL)); if (data[2]) - state->cur_sample->temperature.mkelvin = state->metric ? C_to_mkelvin(strtod_flags(data[2], NULL, 0)) : F_to_mkelvin(strtod_flags(data[2], NULL, 0)); - if (data[3]) { - state->cur_sample->setpoint.mbar = lrint(strtod_flags(data[3], NULL, 0) * 1000); - } + state->cur_sample->temperature.mkelvin = state->metric ? C_to_mkelvin(permissive_strtod(data[2], NULL)) : F_to_mkelvin(permissive_strtod(data[2], NULL)); + if (data[3]) + state->cur_sample->setpoint.mbar = lrint(permissive_strtod(data[3], NULL) * 1000); if (data[4]) state->cur_sample->ndl.seconds = atoi(data[4]) * 60; if (data[5]) @@ -161,11 +155,11 @@ static int shearwater_ai_profile_sample(void *param, int, char **data, char **) state->cur_sample->time.seconds = atoi(data[0]); if (data[1]) - state->cur_sample->depth.mm = state->metric ? lrint(strtod_flags(data[1], NULL, 0) * 1000) : feet_to_mm(strtod_flags(data[1], NULL, 0)); + state->cur_sample->depth.mm = state->metric ? lrint(permissive_strtod(data[1], NULL) * 1000) : feet_to_mm(permissive_strtod(data[1], NULL)); if (data[2]) - state->cur_sample->temperature.mkelvin = state->metric ? C_to_mkelvin(strtod_flags(data[2], NULL, 0)) : F_to_mkelvin(strtod_flags(data[2], NULL, 0)); + state->cur_sample->temperature.mkelvin = state->metric ? C_to_mkelvin(permissive_strtod(data[2], NULL)) : F_to_mkelvin(permissive_strtod(data[2], NULL)); if (data[3]) { - state->cur_sample->setpoint.mbar = lrint(strtod_flags(data[3], NULL, 0) * 1000); + state->cur_sample->setpoint.mbar = lrint(permissive_strtod(data[3], NULL) * 1000); } if (data[4]) state->cur_sample->ndl.seconds = atoi(data[4]) * 60; @@ -215,7 +209,7 @@ static int shearwater_mode(void *param, int, char **data, char **) struct parser_state *state = (struct parser_state *)param; if (data[0]) - state->cur_dive->dc.divemode = atoi(data[0]) == 0 ? CCR : OC; + state->cur_dive->dcs[0].divemode = atoi(data[0]) == 0 ? CCR : OC; return 0; } @@ -240,23 +234,23 @@ static int shearwater_dive(void *param, int, char **data, char **) long int dive_id = atol(data[11]); if (data[2]) - add_dive_site(data[2], state->cur_dive, state); + add_dive_site(data[2], state->cur_dive.get(), state); if (data[3]) - utf8_string(data[3], &state->cur_dive->buddy); + utf8_string_std(data[3], &state->cur_dive->buddy); if (data[4]) - utf8_string(data[4], &state->cur_dive->notes); + utf8_string_std(data[4], &state->cur_dive->notes); state->metric = atoi(data[5]) == 1 ? 0 : 1; /* TODO: verify that metric calculation is correct */ if (data[6]) - state->cur_dive->dc.maxdepth.mm = state->metric ? lrint(strtod_flags(data[6], NULL, 0) * 1000) : feet_to_mm(strtod_flags(data[6], NULL, 0)); + state->cur_dive->dcs[0].maxdepth.mm = state->metric ? lrint(permissive_strtod(data[6], NULL) * 1000) : feet_to_mm(permissive_strtod(data[6], NULL)); if (data[7]) - state->cur_dive->dc.duration.seconds = atoi(data[7]) * 60; + state->cur_dive->dcs[0].duration.seconds = atoi(data[7]) * 60; if (data[8]) - state->cur_dive->dc.surface_pressure.mbar = atoi(data[8]); + state->cur_dive->dcs[0].surface_pressure.mbar = atoi(data[8]); /* * TODO: the deviceid hash should be calculated here. */ @@ -267,13 +261,13 @@ static int shearwater_dive(void *param, int, char **data, char **) if (data[10]) { switch (atoi(data[10])) { case 2: - state->cur_settings.dc.model = strdup("Shearwater Petrel/Perdix"); + state->cur_settings.dc.model = "Shearwater Petrel/Perdix"; break; case 4: - state->cur_settings.dc.model = strdup("Shearwater Predator"); + state->cur_settings.dc.model = "Shearwater Predator"; break; default: - state->cur_settings.dc.model = strdup("Shearwater import"); + state->cur_settings.dc.model = "Shearwater import"; break; } } @@ -286,13 +280,13 @@ static int shearwater_dive(void *param, int, char **data, char **) if (data[10]) { switch (atoi(data[10])) { case 2: - state->cur_dive->dc.model = strdup("Shearwater Petrel/Perdix"); + state->cur_dive->dcs[0].model = "Shearwater Petrel/Perdix"; break; case 4: - state->cur_dive->dc.model = strdup("Shearwater Predator"); + state->cur_dive->dcs[0].model = "Shearwater Predator"; break; default: - state->cur_dive->dc.model = strdup("Shearwater import"); + state->cur_dive->dcs[0].model = "Shearwater import"; break; } } @@ -370,23 +364,23 @@ static int shearwater_cloud_dive(void *param, int, char **data, char **) state->sample_rate = 0; if (data[2]) - add_dive_site(data[2], state->cur_dive, state); + add_dive_site(data[2], state->cur_dive.get(), state); if (data[3]) - utf8_string(data[3], &state->cur_dive->buddy); + utf8_string_std(data[3], &state->cur_dive->buddy); if (data[4]) - utf8_string(data[4], &state->cur_dive->notes); + utf8_string_std(data[4], &state->cur_dive->notes); state->metric = atoi(data[5]) == 1 ? 0 : 1; /* TODO: verify that metric calculation is correct */ if (data[6]) - state->cur_dive->dc.maxdepth.mm = state->metric ? lrint(strtod_flags(data[6], NULL, 0) * 1000) : feet_to_mm(strtod_flags(data[6], NULL, 0)); + state->cur_dive->dcs[0].maxdepth.mm = state->metric ? lrint(permissive_strtod(data[6], NULL) * 1000) : feet_to_mm(permissive_strtod(data[6], NULL)); if (data[7]) - state->cur_dive->dc.duration.seconds = atoi(data[7]); + state->cur_dive->dcs[0].duration.seconds = atoi(data[7]); if (data[8]) - state->cur_dive->dc.surface_pressure.mbar = atoi(data[8]); + state->cur_dive->dcs[0].surface_pressure.mbar = atoi(data[8]); /* * TODO: the deviceid hash should be calculated here. */ @@ -397,13 +391,13 @@ static int shearwater_cloud_dive(void *param, int, char **data, char **) if (data[10]) { switch (atoi(data[10])) { case 2: - state->cur_settings.dc.model = strdup("Shearwater Petrel/Perdix"); + state->cur_settings.dc.model = "Shearwater Petrel/Perdix"; break; case 4: - state->cur_settings.dc.model = strdup("Shearwater Predator"); + state->cur_settings.dc.model = "Shearwater Predator"; break; default: - state->cur_settings.dc.model = strdup("Shearwater import"); + state->cur_settings.dc.model = "Shearwater import"; break; } } @@ -416,13 +410,13 @@ static int shearwater_cloud_dive(void *param, int, char **data, char **) if (data[10]) { switch (atoi(data[10])) { case 2: - state->cur_dive->dc.model = strdup("Shearwater Petrel/Perdix"); + state->cur_dive->dcs[0].model = "Shearwater Petrel/Perdix"; break; case 4: - state->cur_dive->dc.model = strdup("Shearwater Predator"); + state->cur_dive->dcs[0].model = "Shearwater Predator"; break; default: - state->cur_dive->dc.model = strdup("Shearwater import"); + state->cur_dive->dcs[0].model = "Shearwater import"; break; } } @@ -473,7 +467,7 @@ static int shearwater_cloud_dive(void *param, int, char **data, char **) return SQLITE_OK; } -extern "C" int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) +int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) { int retval; struct parser_state state; @@ -496,7 +490,7 @@ extern "C" int parse_shearwater_buffer(sqlite3 *handle, const char *url, const c return 0; } -extern "C" int parse_shearwater_cloud_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) +int parse_shearwater_cloud_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) { int retval; struct parser_state state; diff --git a/core/import-suunto.cpp b/core/import-suunto.cpp index 0ed7dc396..33133a6cd 100644 --- a/core/import-suunto.cpp +++ b/core/import-suunto.cpp @@ -4,7 +4,6 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "ssrf.h" #include "dive.h" #include "parse.h" #include "sample.h" @@ -21,6 +20,7 @@ static int dm4_events(void *param, int, char **data, char **) { + using namespace std::string_literals; struct parser_state *state = (struct parser_state *)param; event_start(state); @@ -31,108 +31,108 @@ static int dm4_events(void *param, int, char **data, char **) switch (atoi(data[2])) { case 1: /* 1 Mandatory Safety Stop */ - strcpy(state->cur_event.name, "safety stop (mandatory)"); + state->cur_event.name = "safety stop (mandatory)"s; break; case 3: /* 3 Deco */ /* What is Subsurface's term for going to * deco? */ - strcpy(state->cur_event.name, "deco"); + state->cur_event.name = "deco"s; break; case 4: /* 4 Ascent warning */ - strcpy(state->cur_event.name, "ascent"); + state->cur_event.name = "ascent"s; break; case 5: /* 5 Ceiling broken */ - strcpy(state->cur_event.name, "violation"); + state->cur_event.name = "violation"s; break; case 6: /* 6 Mandatory safety stop ceiling error */ - strcpy(state->cur_event.name, "violation"); + state->cur_event.name = "violation"s; break; case 7: /* 7 Below deco floor */ - strcpy(state->cur_event.name, "below floor"); + state->cur_event.name = "below floor"s; break; case 8: /* 8 Dive time alarm */ - strcpy(state->cur_event.name, "divetime"); + state->cur_event.name = "divetime"s; break; case 9: /* 9 Depth alarm */ - strcpy(state->cur_event.name, "maxdepth"); + state->cur_event.name = "maxdepth"s; break; case 10: /* 10 OLF 80% */ case 11: /* 11 OLF 100% */ - strcpy(state->cur_event.name, "OLF"); + state->cur_event.name = "OLF"s; break; case 12: /* 12 High pO₂ */ - strcpy(state->cur_event.name, "PO2"); + state->cur_event.name = "PO2"s; break; case 13: /* 13 Air time */ - strcpy(state->cur_event.name, "airtime"); + state->cur_event.name = "airtime"s; break; case 17: /* 17 Ascent warning */ - strcpy(state->cur_event.name, "ascent"); + state->cur_event.name = "ascent"s; break; case 18: /* 18 Ceiling error */ - strcpy(state->cur_event.name, "ceiling"); + state->cur_event.name = "ceiling"s; break; case 19: /* 19 Surfaced */ - strcpy(state->cur_event.name, "surface"); + state->cur_event.name = "surface"s; break; case 20: /* 20 Deco */ - strcpy(state->cur_event.name, "deco"); + state->cur_event.name = "deco"s; break; case 22: case 32: /* 22 Mandatory safety stop violation */ /* 32 Deep stop violation */ - strcpy(state->cur_event.name, "violation"); + state->cur_event.name = "violation"s; break; case 30: /* Tissue level warning */ - strcpy(state->cur_event.name, "tissue warning"); + state->cur_event.name = "tissue warning"s; break; case 37: /* Tank pressure alarm */ - strcpy(state->cur_event.name, "tank pressure"); + state->cur_event.name = "tank pressure"s; break; case 257: /* 257 Dive active */ /* This seems to be given after surface when * descending again. */ - strcpy(state->cur_event.name, "surface"); + state->cur_event.name = "surface"s; break; case 258: /* 258 Bookmark */ if (data[3]) { - strcpy(state->cur_event.name, "heading"); + state->cur_event.name = "heading"s; state->cur_event.value = atoi(data[3]); } else { - strcpy(state->cur_event.name, "bookmark"); + state->cur_event.name = "bookmark"s; } break; case 259: /* Deep stop */ - strcpy(state->cur_event.name, "Deep stop"); + state->cur_event.name = "Deep stop"s; break; case 260: /* Deep stop */ - strcpy(state->cur_event.name, "Deep stop cleared"); + state->cur_event.name = "Deep stop cleared"s; break; case 266: /* Mandatory safety stop activated */ - strcpy(state->cur_event.name, "safety stop (mandatory)"); + state->cur_event.name = "safety stop (mandatory)"s; break; case 267: /* Mandatory safety stop deactivated */ @@ -140,7 +140,7 @@ static int dm4_events(void *param, int, char **data, char **) * profile so skipping as well for now */ break; default: - strcpy(state->cur_event.name, "unknown"); + state->cur_event.name = "unknown"s; state->cur_event.value = atoi(data[2]); break; } @@ -155,7 +155,7 @@ static int dm4_tags(void *param, int, char **data, char **) struct parser_state *state = (struct parser_state *)param; if (data[0]) - taglist_add_tag(&state->cur_dive->tag_list, data[0]); + taglist_add_tag(state->cur_dive->tags, data[0]); return 0; } @@ -179,7 +179,7 @@ static int dm4_dive(void *param, int, char **data, char **) state->cur_dive->when = (time_t)(atol(data[1])); if (data[2]) - utf8_string(data[2], &state->cur_dive->notes); + utf8_string_std(data[2], &state->cur_dive->notes); /* * DM4 stores Duration and DiveTime. It looks like DiveTime is @@ -191,7 +191,7 @@ static int dm4_dive(void *param, int, char **data, char **) if (data[3]) state->cur_dive->duration.seconds = atoi(data[3]); if (data[15]) - state->cur_dive->dc.duration.seconds = atoi(data[15]); + state->cur_dive->dcs[0].duration.seconds = atoi(data[15]); /* * TODO: the deviceid hash should be calculated here. @@ -208,11 +208,11 @@ static int dm4_dive(void *param, int, char **data, char **) settings_end(state); if (data[6]) - state->cur_dive->dc.maxdepth.mm = lrint(strtod_flags(data[6], NULL, 0) * 1000); + state->cur_dive->dcs[0].maxdepth.mm = lrint(permissive_strtod(data[6], NULL) * 1000); if (data[8]) - state->cur_dive->dc.airtemp.mkelvin = C_to_mkelvin(atoi(data[8])); + state->cur_dive->dcs[0].airtemp.mkelvin = C_to_mkelvin(atoi(data[8])); if (data[9]) - state->cur_dive->dc.watertemp.mkelvin = C_to_mkelvin(atoi(data[9])); + state->cur_dive->dcs[0].watertemp.mkelvin = C_to_mkelvin(atoi(data[9])); /* * TODO: handle multiple cylinders @@ -227,7 +227,7 @@ static int dm4_dive(void *param, int, char **data, char **) if (data[11] && atoi(data[11]) > 0) cyl->end.mbar = (atoi(data[11])); if (data[12]) - cyl->type.size.mliter = lrint((strtod_flags(data[12], NULL, 0)) * 1000); + cyl->type.size.mliter = lrint((permissive_strtod(data[12], NULL)) * 1000); if (data[13]) cyl->type.workingpressure.mbar = (atoi(data[13])); if (data[20]) @@ -237,7 +237,7 @@ static int dm4_dive(void *param, int, char **data, char **) cylinder_end(state); if (data[14]) - state->cur_dive->dc.surface_pressure.mbar = (atoi(data[14]) * 1000); + state->cur_dive->dcs[0].surface_pressure.mbar = (atoi(data[14]) * 1000); interval = data[16] ? atoi(data[16]) : 0; profileBlob = (float *)data[17]; @@ -249,7 +249,7 @@ static int dm4_dive(void *param, int, char **data, char **) if (profileBlob) state->cur_sample->depth.mm = lrintf(profileBlob[i] * 1000.0f); else - state->cur_sample->depth.mm = state->cur_dive->dc.maxdepth.mm; + state->cur_sample->depth.mm = state->cur_dive->dcs[0].maxdepth.mm; if (data[18] && data[18][0]) state->cur_sample->temperature.mkelvin = C_to_mkelvin(tempBlob[i]); @@ -277,7 +277,7 @@ static int dm4_dive(void *param, int, char **data, char **) return SQLITE_OK; } -extern "C" int parse_dm4_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) +int parse_dm4_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) { int retval; char *err = NULL; @@ -293,7 +293,8 @@ extern "C" int parse_dm4_buffer(sqlite3 *handle, const char *url, const char *, retval = sqlite3_exec(handle, get_dives, &dm4_dive, &state, &err); if (retval != SQLITE_OK) { - report_info("Database query failed '%s'.", url); + report_info("Database query failed '%s': %s.", url, err); + sqlite3_free(err); return 1; } @@ -314,10 +315,10 @@ static int dm5_cylinders(void *param, int, char **data, char **) /* DM5 shows tank size of 12 liters when the actual * value is 0 (and using metric units). So we just use * the same 12 liters when size is not available */ - if (strtod_flags(data[6], NULL, 0) == 0.0 && cyl->start.mbar) - cyl->type.size.mliter = 12000; + if (permissive_strtod(data[6], NULL) == 0.0 && cyl->start.mbar) + cyl->type.size = 12_l; else - cyl->type.size.mliter = lrint((strtod_flags(data[6], NULL, 0)) * 1000); + cyl->type.size.mliter = lrint((permissive_strtod(data[6], NULL)) * 1000); } if (data[2]) cyl->gasmix.o2.permille = atoi(data[2]) * 10; @@ -329,19 +330,20 @@ static int dm5_cylinders(void *param, int, char **data, char **) static int dm5_gaschange(void *param, int, char **data, char **) { + using namespace std::string_literals; struct parser_state *state = (struct parser_state *)param; event_start(state); if (data[0]) state->cur_event.time.seconds = atoi(data[0]); if (data[1]) { - strcpy(state->cur_event.name, "gaschange"); - state->cur_event.value = lrint(strtod_flags(data[1], NULL, 0)); + state->cur_event.name = "gaschange"s; + state->cur_event.value = lrint(permissive_strtod(data[1], NULL)); } /* He part of the mix */ if (data[2]) - state->cur_event.value += lrint(strtod_flags(data[2], NULL, 0)) << 16; + state->cur_event.value += lrint(permissive_strtod(data[2], NULL)) << 16; event_end(state); return 0; @@ -366,12 +368,12 @@ static int dm5_dive(void *param, int, char **data, char **) state->cur_dive->when = (time_t)(atol(data[1])); if (data[2]) - utf8_string(data[2], &state->cur_dive->notes); + utf8_string_std(data[2], &state->cur_dive->notes); if (data[3]) state->cur_dive->duration.seconds = atoi(data[3]); if (data[15]) - state->cur_dive->dc.duration.seconds = atoi(data[15]); + state->cur_dive->dcs[0].duration.seconds = atoi(data[15]); /* * TODO: the deviceid hash should be calculated here. @@ -389,30 +391,28 @@ static int dm5_dive(void *param, int, char **data, char **) settings_end(state); if (data[6]) - state->cur_dive->dc.maxdepth.mm = lrint(strtod_flags(data[6], NULL, 0) * 1000); + state->cur_dive->dcs[0].maxdepth.mm = lrint(permissive_strtod(data[6], NULL) * 1000); if (data[8]) - state->cur_dive->dc.airtemp.mkelvin = C_to_mkelvin(atoi(data[8])); + state->cur_dive->dcs[0].airtemp.mkelvin = C_to_mkelvin(atoi(data[8])); if (data[9]) - state->cur_dive->dc.watertemp.mkelvin = C_to_mkelvin(atoi(data[9])); + state->cur_dive->dcs[0].watertemp.mkelvin = C_to_mkelvin(atoi(data[9])); if (data[4]) { - state->cur_dive->dc.deviceid = atoi(data[4]); + state->cur_dive->dcs[0].deviceid = atoi(data[4]); } - // Ugh. dc.model is const char * -> we are not supposed to write into it. This will - // change when we convert to std::string. if (data[5]) - utf8_string(data[5], (char **)&state->cur_dive->dc.model); + utf8_string_std(data[5], &state->cur_dive->dcs[0].model); if (data[25]) { switch(atoi(data[25])) { case 1: - state->cur_dive->dc.divemode = OC; + state->cur_dive->dcs[0].divemode = OC; break; case 5: - state->cur_dive->dc.divemode = CCR; + state->cur_dive->dcs[0].divemode = CCR; break; default: - state->cur_dive->dc.divemode = OC; + state->cur_dive->dcs[0].divemode = OC; break; } } @@ -425,7 +425,7 @@ static int dm5_dive(void *param, int, char **data, char **) } if (data[14]) - state->cur_dive->dc.surface_pressure.mbar = (atoi(data[14]) / 100); + state->cur_dive->dcs[0].surface_pressure.mbar = (atoi(data[14]) / 100); interval = data[16] ? atoi(data[16]) : 0; @@ -513,7 +513,7 @@ static int dm5_dive(void *param, int, char **data, char **) if (profileBlob) state->cur_sample->depth.mm = lrintf(profileBlob[i] * 1000.0f); else - state->cur_sample->depth.mm = state->cur_dive->dc.maxdepth.mm; + state->cur_sample->depth.mm = state->cur_dive->dcs[0].maxdepth.mm; if (data[18] && data[18][0]) state->cur_sample->temperature.mkelvin = C_to_mkelvin(tempBlob[i]); @@ -549,7 +549,7 @@ static int dm5_dive(void *param, int, char **data, char **) return SQLITE_OK; } -extern "C" int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) +int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *, int, struct divelog *log) { int retval; char *err = NULL; @@ -565,7 +565,8 @@ extern "C" int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *, retval = sqlite3_exec(handle, get_dives, &dm5_dive, &state, &err); if (retval != SQLITE_OK) { - report_info("Database query failed '%s'.", url); + report_info("Database query failed '%s': %s.", url, err); + sqlite3_free(err); return 1; } diff --git a/core/ios.cpp b/core/ios.cpp index 409ff63ca..2e0701da1 100644 --- a/core/ios.cpp +++ b/core/ios.cpp @@ -3,10 +3,10 @@ #include #include #include -#include "dive.h" #include "file.h" #include "device.h" -#include "core/qthelper.h" +#include "qthelper.h" +#include "subsurfacestartup.h" #include #if !defined(__IPHONE_5_0) #include @@ -32,33 +32,31 @@ static std::string make_default_filename() return system_default_path() + "/subsurface.xml"; } -extern "C" { - -const char mac_system_divelist_default_font[] = "Arial"; -const char *system_divelist_default_font = mac_system_divelist_default_font; +using namespace std::string_literals; +std::string system_divelist_default_font = "Arial"s; double system_divelist_default_font_size = -1.0; -void subsurface_OS_pref_setup(void) +void subsurface_OS_pref_setup() { // nothing } -bool subsurface_ignore_font(const char*) +bool subsurface_ignore_font(const std::string &) { // 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; } int enumerate_devices(device_callback_t, void *, unsigned int) @@ -109,12 +107,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 */ } @@ -123,4 +121,3 @@ bool subsurface_user_is_root() { return false; } -} diff --git a/core/libdivecomputer.cpp b/core/libdivecomputer.cpp index 74bde8c8c..f6dd7cac1 100644 --- a/core/libdivecomputer.cpp +++ b/core/libdivecomputer.cpp @@ -4,7 +4,7 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "ssrf.h" +#include #include #include #include @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include "gettext.h" #include "divelog.h" #include "divesite.h" @@ -26,7 +26,6 @@ #include "event.h" #include "sha1.h" #include "subsurface-time.h" -#include "timer.h" #include #include @@ -40,11 +39,12 @@ #include "core/qthelper.h" #include "core/file.h" #include +#include std::string dumpfile_name; std::string logfile_name; -const char *progress_bar_text = ""; -void (*progress_callback)(const char *text) = NULL; +std::string progress_bar_text; +void (*progress_callback)(const std::string &text) = NULL; double progress_bar_fraction = 0.0; static int stoptime, stopdepth, ndl, po2, cns, heartbeat, bearing; @@ -54,6 +54,16 @@ static int current_gas_index; #define INFO(fmt, ...) report_info("INFO: " fmt, ##__VA_ARGS__) #define ERROR(fmt, ...) report_info("ERROR: " fmt, ##__VA_ARGS__) +device_data_t::device_data_t() +{ +} + +device_data_t::~device_data_t() +{ + if (descriptor) + dc_descriptor_free(descriptor); +} + /* * Directly taken from libdivecomputer's examples/common.c to improve * the error messages resulting from libdc's return codes @@ -150,14 +160,14 @@ static dc_status_t parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_ } } bool no_volume = true; - struct gasmix bottom_gas = { {1000}, {0} }; /* Default to pure O2, or air if there are no mixes defined */ + struct gasmix bottom_gas = { 100_percent, 0_percent }; /* Default to pure O2, or air if there are no mixes defined */ if (ngases == 0) { bottom_gas = gasmix_air; } - clear_cylinder_table(&dive->cylinders); + dive->cylinders.clear(); for (i = 0; i < std::max(ngases, ntanks); i++) { - cylinder_t cyl = empty_cylinder; + cylinder_t cyl; cyl.cylinder_use = NOT_USED; if (i < ngases) { @@ -202,7 +212,7 @@ static dc_status_t parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_ break; default: - if (dive->dc.divemode == CCR) + if (dive->dcs[0].divemode == CCR) cyl.cylinder_use = DILUENT; else cyl.cylinder_use = OC_GAS; @@ -226,7 +236,7 @@ static dc_status_t parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_ cyl.type.workingpressure.mbar = lrint(tank.workpressure * 1000); if (tank.type & DC_TANKVOLUME_IMPERIAL) { - if (same_string(devdata->model, "Suunto EON Steel")) { + if (devdata->model == "Suunto EON Steel") { /* Suunto EON Steele gets this wrong. Badly. * but on the plus side it only supports a few imperial sizes, * so let's try and guess at least the most common ones. @@ -236,8 +246,8 @@ static dc_status_t parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_ cyl.type.workingpressure.mbar = lrint( cyl.type.workingpressure.mbar * 206.843 / 206.7 ); char name_buffer[17]; - int rounded_size = lrint(ml_to_cuft(gas_volume(&cyl, - cyl.type.workingpressure))); + int rounded_size = lrint(ml_to_cuft(cyl.gas_volume( + cyl.type.workingpressure).mliter)); rounded_size = (int)((rounded_size + 5) / 10) * 10; switch (cyl.type.workingpressure.mbar) { case 206843: @@ -256,7 +266,7 @@ static dc_status_t parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_ snprintf(name_buffer, sizeof(name_buffer), "%d cuft", rounded_size); break; } - cyl.type.description = copy_string(name_buffer); + cyl.type.description = name_buffer; cyl.type.size.mliter = lrint(cuft_to_l(rounded_size) * 1000 / mbar_to_atm(cyl.type.workingpressure.mbar)); } @@ -279,9 +289,9 @@ static dc_status_t parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_ if (!nearly_0(tank.endpressure)) { cyl.start.mbar = lrint(tank.beginpressure * 1000); cyl.end.mbar = lrint(tank.endpressure * 1000); - } else if (same_string(devdata->vendor, "Uwatec")) { + } else if (devdata->vendor == "Uwatec") { cyl.start.mbar = lrint(tank.beginpressure * 1000 + 30000); - cyl.end.mbar = 30000; + cyl.end = 30_bar; } } } @@ -291,15 +301,15 @@ static dc_status_t parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_ fill_default_cylinder(dive, &cyl); } /* whatever happens, make sure there is a name for the cylinder */ - if (empty_string(cyl.type.description)) - cyl.type.description = strdup(translate("gettextFromC", "unknown")); + if (cyl.type.description.empty()) + cyl.type.description = translate("gettextFromC", "unknown"); - add_cylinder(&dive->cylinders, dive->cylinders.nr, cyl); + dive->cylinders.push_back(std::move(cyl)); } return DC_STATUS_SUCCESS; } -static void handle_event(struct divecomputer *dc, struct sample *sample, dc_sample_value_t value) +static void handle_event(struct divecomputer *dc, const struct sample &sample, dc_sample_value_t value) { int type, time; struct event *ev; @@ -350,20 +360,19 @@ static void handle_event(struct divecomputer *dc, struct sample *sample, dc_samp #endif time = value.event.time; - if (sample) - time += sample->time.seconds; + time += sample.time.seconds; ev = add_event(dc, time, type, value.event.flags, value.event.value, name); - if (event_is_gaschange(ev) && ev->gas.index >= 0) + if (ev->is_gaschange() && ev->gas.index >= 0) current_gas_index = ev->gas.index; } -static void handle_gasmix(struct divecomputer *dc, struct sample *sample, int idx) +static void handle_gasmix(struct divecomputer *dc, const struct sample &sample, int idx) { /* TODO: Verify that index is not higher than the number of cylinders */ if (idx < 0) return; - add_event(dc, sample->time.seconds, SAMPLE_EVENT_GASCHANGE2, idx+1, 0, "gaschange"); + add_event(dc, sample.time.seconds, SAMPLE_EVENT_GASCHANGE2, idx+1, 0, "gaschange"); current_gas_index = idx; } @@ -371,30 +380,19 @@ void sample_cb(dc_sample_type_t type, const dc_sample_value_t *pvalue, void *userdata) { static unsigned int nsensor = 0; - dc_sample_value_t value = *pvalue; struct divecomputer *dc = (divecomputer *)userdata; - struct sample *sample; + dc_sample_value_t value = *pvalue; /* - * We fill in the "previous" sample - except for DC_SAMPLE_TIME, - * which creates a new one. + * DC_SAMPLE_TIME is special: it creates a new sample. + * Other types fill in an existing sample. */ - sample = dc->samples ? dc->sample + dc->samples - 1 : NULL; - - /* - * Ok, sanity check. - * If first sample is not a DC_SAMPLE_TIME, Allocate a sample for us - */ - if (sample == NULL && type != DC_SAMPLE_TIME) - sample = prepare_sample(dc); - - switch (type) { - case DC_SAMPLE_TIME: + if (type == DC_SAMPLE_TIME) { nsensor = 0; // Create a new sample. // Mark depth as negative - sample = prepare_sample(dc); + struct sample *sample = prepare_sample(dc); sample->time.seconds = value.time / 1000; sample->depth.mm = -1; // The current sample gets some sticky values @@ -408,40 +406,46 @@ sample_cb(dc_sample_type_t type, const dc_sample_value_t *pvalue, void *userdata sample->cns = cns; sample->heartbeat = heartbeat; sample->bearing.degrees = bearing; - finish_sample(dc); - break; + return; + } + + if (dc->samples.empty()) + prepare_sample(dc); + struct sample &sample = dc->samples.back(); + + switch (type) { case DC_SAMPLE_DEPTH: - sample->depth.mm = lrint(value.depth * 1000); + sample.depth.mm = lrint(value.depth * 1000); break; case DC_SAMPLE_PRESSURE: - add_sample_pressure(sample, value.pressure.tank, lrint(value.pressure.value * 1000)); + add_sample_pressure(&sample, value.pressure.tank, lrint(value.pressure.value * 1000)); break; case DC_SAMPLE_GASMIX: handle_gasmix(dc, sample, value.gasmix); break; case DC_SAMPLE_TEMPERATURE: - sample->temperature.mkelvin = C_to_mkelvin(value.temperature); + sample.temperature.mkelvin = C_to_mkelvin(value.temperature); break; case DC_SAMPLE_EVENT: handle_event(dc, sample, value); break; case DC_SAMPLE_RBT: - sample->rbt.seconds = (!strncasecmp(dc->model, "suunto", 6)) ? value.rbt : value.rbt * 60; + sample.rbt.seconds = (!strncasecmp(dc->model.c_str(), "suunto", 6)) ? value.rbt : value.rbt * 60; break; #ifdef DC_SAMPLE_TTS case DC_SAMPLE_TTS: - sample->tts.seconds = value.time; + sample.tts.seconds = value.time; break; #endif case DC_SAMPLE_HEARTBEAT: - sample->heartbeat = heartbeat = value.heartbeat; + sample.heartbeat = heartbeat = value.heartbeat; break; case DC_SAMPLE_BEARING: - sample->bearing.degrees = bearing = value.bearing; + sample.bearing.degrees = bearing = value.bearing; break; #ifdef DEBUG_DC_VENDOR case DC_SAMPLE_VENDOR: - printf(" ", FRACTION_TUPLE(sample->time.seconds, 60), + printf(" ", FRACTION_TUPLE(sample.time.seconds, 60), value.vendor.type, value.vendor.size); for (int i = 0; i < value.vendor.size; ++i) printf("%02X", ((unsigned char *)value.vendor.data)[i]); @@ -450,11 +454,11 @@ sample_cb(dc_sample_type_t type, const dc_sample_value_t *pvalue, void *userdata #endif case DC_SAMPLE_SETPOINT: /* for us a setpoint means constant pO2 from here */ - sample->setpoint.mbar = po2 = lrint(value.setpoint * 1000); + sample.setpoint.mbar = po2 = lrint(value.setpoint * 1000); break; case DC_SAMPLE_PPO2: if (nsensor < MAX_O2_SENSORS) - sample->o2sensor[nsensor].mbar = lrint(value.ppo2.value * 1000); + sample.o2sensor[nsensor].mbar = lrint(value.ppo2.value * 1000); else report_error("%d is more o2 sensors than we can handle", nsensor); nsensor++; @@ -463,57 +467,54 @@ sample_cb(dc_sample_type_t type, const dc_sample_value_t *pvalue, void *userdata dc->no_o2sensors = nsensor; break; case DC_SAMPLE_CNS: - sample->cns = cns = lrint(value.cns * 100); + sample.cns = cns = lrint(value.cns * 100); break; case DC_SAMPLE_DECO: if (value.deco.type == DC_DECO_NDL) { - sample->ndl.seconds = ndl = value.deco.time; - sample->stopdepth.mm = stopdepth = lrint(value.deco.depth * 1000.0); - sample->in_deco = in_deco = false; + sample.ndl.seconds = ndl = value.deco.time; + sample.stopdepth.mm = stopdepth = lrint(value.deco.depth * 1000.0); + sample.in_deco = in_deco = false; } else if (value.deco.type == DC_DECO_DECOSTOP || value.deco.type == DC_DECO_DEEPSTOP) { - sample->stopdepth.mm = stopdepth = lrint(value.deco.depth * 1000.0); - sample->stoptime.seconds = stoptime = value.deco.time; - sample->in_deco = in_deco = stopdepth > 0; + sample.stopdepth.mm = stopdepth = lrint(value.deco.depth * 1000.0); + sample.stoptime.seconds = stoptime = value.deco.time; + sample.in_deco = in_deco = stopdepth > 0; ndl = 0; } else if (value.deco.type == DC_DECO_SAFETYSTOP) { - sample->in_deco = in_deco = false; - sample->stopdepth.mm = stopdepth = lrint(value.deco.depth * 1000.0); - sample->stoptime.seconds = stoptime = value.deco.time; + sample.in_deco = in_deco = false; + sample.stopdepth.mm = stopdepth = lrint(value.deco.depth * 1000.0); + sample.stoptime.seconds = stoptime = value.deco.time; } - sample->tts.seconds = value.deco.tts; + sample.tts.seconds = value.deco.tts; default: break; } } -static void dev_info(device_data_t *, const char *fmt, ...) +static void dev_info(const char *fmt, ...) { - static char buffer[1024]; va_list ap; va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); + progress_bar_text = vformat_string_std(fmt, ap); va_end(ap); - progress_bar_text = buffer; if (verbose) - INFO("dev_info: %s", buffer); + INFO("dev_info: %s", progress_bar_text.c_str()); if (progress_callback) - (*progress_callback)(buffer); + (*progress_callback)(progress_bar_text); } static int import_dive_number = 0; static void download_error(const char *fmt, ...) { - static char buffer[1024]; va_list ap; va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); + std::string buffer = vformat_string_std(fmt, ap); va_end(ap); - report_error("Dive %d: %s", import_dive_number, buffer); + report_error("Dive %d: %s", import_dive_number, buffer.c_str()); } static dc_status_t parse_samples(device_data_t *, struct divecomputer *dc, dc_parser_t *parser) @@ -522,75 +523,46 @@ static dc_status_t parse_samples(device_data_t *, struct divecomputer *dc, dc_pa return dc_parser_samples_foreach(parser, sample_cb, dc); } -static int might_be_same_dc(struct divecomputer *a, struct divecomputer *b) +static int might_be_same_dc(const struct divecomputer &a, const struct divecomputer &b) { - if (!a->model || !b->model) + if (a.model.empty() || b.model.empty()) return 1; - if (strcasecmp(a->model, b->model)) + if (strcasecmp(a.model.c_str(), b.model.c_str())) return 0; - if (!a->deviceid || !b->deviceid) + if (!a.deviceid || !b.deviceid) return 1; - return a->deviceid == b->deviceid; + return a.deviceid == b.deviceid; } -static int match_one_dive(struct divecomputer *a, struct dive *dive) +static bool match_one_dive(const struct divecomputer &a, const struct dive &dive) { - struct divecomputer *b = &dive->dc; - /* * Walk the existing dive computer data, * see if we have a match (or an anti-match: * the same dive computer but a different * dive ID). */ - do { - int match = match_one_dc(a, b); - if (match) - return match > 0; - b = b->next; - } while (b); + for (auto &b: dive.dcs) { + if (match_one_dc(a, b) > 0) + return true; + } /* Ok, no exact dive computer match. Does the date match? */ - b = &dive->dc; - do { - if (a->when == b->when && might_be_same_dc(a, b)) - return 1; - b = b->next; - } while (b); + for (auto &b: dive.dcs) { + if (a.when == b.when && might_be_same_dc(a, b)) + return true; + } - return 0; + return false; } /* * Check if this dive already existed before the import */ -static int find_dive(struct divecomputer *match) +static bool find_dive(const struct divecomputer &match) { - int i; - - for (i = divelog.dives->nr - 1; i >= 0; i--) { - struct dive *old = divelog.dives->dives[i]; - - if (match_one_dive(match, old)) - return 1; - } - return 0; -} - -/* - * Like g_strdup_printf(), but without the stupid g_malloc/g_free confusion. - * And we limit the string to some arbitrary size. - */ -static char *str_printf(const char *fmt, ...) -{ - va_list args; - char buf[1024]; - - va_start(args, fmt); - vsnprintf(buf, sizeof(buf) - 1, fmt, args); - va_end(args); - buf[sizeof(buf) - 1] = 0; - return strdup(buf); + return std::any_of(divelog.dives.rbegin(), divelog.dives.rend(), + [&match] (auto &old) { return match_one_dive(match, *old);} ); } /* @@ -601,13 +573,10 @@ static char *str_printf(const char *fmt, ...) */ static uint32_t calculate_diveid(const unsigned char *fingerprint, unsigned int fsize) { - uint32_t csum[5]; - if (!fingerprint || !fsize) return 0; - SHA1(fingerprint, fsize, (unsigned char *)csum); - return csum[0]; + return SHA1_uint32(fingerprint, fsize); } uint32_t calculate_string_hash(const char *str) @@ -619,13 +588,13 @@ static void parse_string_field(device_data_t *devdata, struct dive *dive, dc_fie { // Our dive ID is the string hash of the "Dive ID" string if (!strcmp(str->desc, "Dive ID")) { - if (!dive->dc.diveid) - dive->dc.diveid = calculate_string_hash(str->value); + if (!dive->dcs[0].diveid) + dive->dcs[0].diveid = calculate_string_hash(str->value); return; } // This will pick up serial number and firmware data - add_extra_data(&dive->dc, str->desc, str->value); + add_extra_data(&dive->dcs[0], str->desc, str->value); /* GPS data? */ if (!strncmp(str->desc, "GPS", 3)) { @@ -645,7 +614,7 @@ static void parse_string_field(device_data_t *devdata, struct dive *dive, dc_fie if (location.lat.udeg && location.lon.udeg) { unregister_dive_from_dive_site(dive); - add_dive_to_dive_site(dive, create_dive_site_with_gps(str->value, &location, devdata->log->sites)); + devdata->log->sites.create(std::string(str->value), location)->add_dive(dive); } } } @@ -663,7 +632,7 @@ static dc_status_t libdc_header_parser(dc_parser_t *parser, device_data_t *devda } // Our deviceid is the hash of the serial number - dive->dc.deviceid = 0; + dive->dcs[0].deviceid = 0; if (rc == DC_STATUS_SUCCESS) { tm.tm_year = dt.year; @@ -672,12 +641,12 @@ static dc_status_t libdc_header_parser(dc_parser_t *parser, device_data_t *devda tm.tm_hour = dt.hour; tm.tm_min = dt.minute; tm.tm_sec = dt.second; - dive->when = dive->dc.when = utc_mktime(&tm); + dive->when = dive->dcs[0].when = utc_mktime(&tm); } // Parse the divetime. std::string date_string = get_dive_date_c_string(dive->when); - dev_info(devdata, translate("gettextFromC", "Dive %d: %s"), import_dive_number, date_string.c_str()); + dev_info(translate("gettextFromC", "Dive %d: %s"), import_dive_number, date_string.c_str()); unsigned int divetime = 0; rc = dc_parser_get_field(parser, DC_FIELD_DIVETIME, 0, &divetime); @@ -686,7 +655,7 @@ static dc_status_t libdc_header_parser(dc_parser_t *parser, device_data_t *devda return rc; } if (rc == DC_STATUS_SUCCESS) - dive->dc.duration.seconds = divetime; + dive->dcs[0].duration.seconds = divetime; // Parse the maxdepth. double maxdepth = 0.0; @@ -696,7 +665,7 @@ static dc_status_t libdc_header_parser(dc_parser_t *parser, device_data_t *devda return rc; } if (rc == DC_STATUS_SUCCESS) - dive->dc.maxdepth.mm = lrint(maxdepth * 1000); + dive->dcs[0].maxdepth.mm = lrint(maxdepth * 1000); // Parse temperatures double temperature; @@ -712,11 +681,11 @@ static dc_status_t libdc_header_parser(dc_parser_t *parser, device_data_t *devda if (rc == DC_STATUS_SUCCESS) switch(i) { case 0: - dive->dc.airtemp.mkelvin = C_to_mkelvin(temperature); + dive->dcs[0].airtemp.mkelvin = C_to_mkelvin(temperature); break; case 1: // we don't distinguish min and max water temp here, so take min if given, max otherwise case 2: - dive->dc.watertemp.mkelvin = C_to_mkelvin(temperature); + dive->dcs[0].watertemp.mkelvin = C_to_mkelvin(temperature); break; } } @@ -740,16 +709,16 @@ static dc_status_t libdc_header_parser(dc_parser_t *parser, device_data_t *devda return rc; } if (rc == DC_STATUS_SUCCESS) { - dive->dc.salinity = lrint(salinity.density * 10.0); - if (dive->dc.salinity == 0) { + dive->dcs[0].salinity = lrint(salinity.density * 10.0); + if (dive->dcs[0].salinity == 0) { // sometimes libdivecomputer gives us density values, sometimes just // a water type and a density of zero; let's make this work as best as we can switch (salinity.type) { case DC_WATER_FRESH: - dive->dc.salinity = FRESHWATER_SALINITY; + dive->dcs[0].salinity = FRESHWATER_SALINITY; break; default: - dive->dc.salinity = SEAWATER_SALINITY; + dive->dcs[0].salinity = SEAWATER_SALINITY; break; } } @@ -762,7 +731,7 @@ static dc_status_t libdc_header_parser(dc_parser_t *parser, device_data_t *devda return rc; } if (rc == DC_STATUS_SUCCESS) - dive->dc.surface_pressure.mbar = lrint(surface_pressure * 1000.0); + dive->dcs[0].surface_pressure.mbar = lrint(surface_pressure * 1000.0); // The dive parsing may give us more device information int idx; @@ -786,17 +755,17 @@ static dc_status_t libdc_header_parser(dc_parser_t *parser, device_data_t *devda if (rc == DC_STATUS_SUCCESS) switch(divemode) { case DC_DIVEMODE_FREEDIVE: - dive->dc.divemode = FREEDIVE; + dive->dcs[0].divemode = FREEDIVE; break; case DC_DIVEMODE_GAUGE: case DC_DIVEMODE_OC: /* Open circuit */ - dive->dc.divemode = OC; + dive->dcs[0].divemode = OC; break; case DC_DIVEMODE_CCR: /* Closed circuit rebreather*/ - dive->dc.divemode = CCR; + dive->dcs[0].divemode = CCR; break; case DC_DIVEMODE_SCR: /* Semi-closed circuit rebreather */ - dive->dc.divemode = PSCR; + dive->dcs[0].divemode = PSCR; break; } @@ -818,7 +787,6 @@ static int dive_cb(const unsigned char *data, unsigned int size, dc_status_t rc; dc_parser_t *parser = NULL; device_data_t *devdata = (device_data_t *)userdata; - struct dive *dive = NULL; /* reset static data, that is only valid per dive */ stoptime = stopdepth = po2 = cns = heartbeat = 0; @@ -830,25 +798,25 @@ static int dive_cb(const unsigned char *data, unsigned int size, rc = dc_parser_new(&parser, devdata->device, data, size); if (rc != DC_STATUS_SUCCESS) { - download_error(translate("gettextFromC", "Unable to create parser for %s %s: %d"), devdata->vendor, devdata->product, errmsg(rc)); + download_error(translate("gettextFromC", "Unable to create parser for %s %s: %d"), devdata->vendor.c_str(), devdata->product.c_str(), errmsg(rc)); return true; } - dive = alloc_dive(); + auto dive = std::make_unique(); // Fill in basic fields - dive->dc.model = strdup(devdata->model); - dive->dc.diveid = calculate_diveid(fingerprint, fsize); + dive->dcs[0].model = devdata->model; + dive->dcs[0].diveid = calculate_diveid(fingerprint, fsize); // Parse the dive's header data - rc = libdc_header_parser (parser, devdata, dive); + rc = libdc_header_parser (parser, devdata, dive.get()); if (rc != DC_STATUS_SUCCESS) { download_error(translate("getextFromC", "Error parsing the header: %s"), errmsg(rc)); goto error_exit; } // Initialize the sample data. - rc = parse_samples(devdata, &dive->dc, parser); + rc = parse_samples(devdata, &dive->dcs[0], parser); if (rc != DC_STATUS_SUCCESS) { download_error(translate("gettextFromC", "Error parsing the samples: %s"), errmsg(rc)); goto error_exit; @@ -866,42 +834,39 @@ static int dive_cb(const unsigned char *data, unsigned int size, devdata->fingerprint = (unsigned char *)calloc(fsize, 1); if (devdata->fingerprint) { devdata->fsize = fsize; - devdata->fdeviceid = dive->dc.deviceid; - devdata->fdiveid = dive->dc.diveid; + devdata->fdeviceid = dive->dcs[0].deviceid; + devdata->fdiveid = dive->dcs[0].diveid; memcpy(devdata->fingerprint, fingerprint, fsize); } } /* If we already saw this dive, abort. */ - if (!devdata->force_download && find_dive(&dive->dc)) { + if (!devdata->force_download && find_dive(dive->dcs[0])) { std::string date_string = get_dive_date_c_string(dive->when); - dev_info(devdata, translate("gettextFromC", "Already downloaded dive at %s"), date_string.c_str()); - free_dive(dive); + dev_info(translate("gettextFromC", "Already downloaded dive at %s"), date_string.c_str()); return false; } /* Various libdivecomputer interface fixups */ - if (dive->dc.airtemp.mkelvin == 0 && first_temp_is_air && dive->dc.samples) { - dive->dc.airtemp = dive->dc.sample[0].temperature; - dive->dc.sample[0].temperature.mkelvin = 0; + if (dive->dcs[0].airtemp.mkelvin == 0 && first_temp_is_air && !dive->dcs[0].samples.empty()) { + dive->dcs[0].airtemp = dive->dcs[0].samples[0].temperature; + dive->dcs[0].samples[0].temperature = 0_K; } /* special case for bug in Tecdiving DiveComputer.eu * often the first sample has a water temperature of 0C, followed by the correct * temperature in the next sample */ - if (same_string(dive->dc.model, "Tecdiving DiveComputer.eu") && - dive->dc.sample[0].temperature.mkelvin == ZERO_C_IN_MKELVIN && - dive->dc.sample[1].temperature.mkelvin > dive->dc.sample[0].temperature.mkelvin) - dive->dc.sample[0].temperature.mkelvin = dive->dc.sample[1].temperature.mkelvin; + if (dive->dcs[0].model == "Tecdiving DiveComputer.eu" && !dive->dcs[0].samples.empty() && + dive->dcs[0].samples[0].temperature.mkelvin == ZERO_C_IN_MKELVIN && + dive->dcs[0].samples[1].temperature.mkelvin > dive->dcs[0].samples[0].temperature.mkelvin) + dive->dcs[0].samples[0].temperature.mkelvin = dive->dcs[0].samples[1].temperature.mkelvin; - record_dive_to_table(dive, devdata->log->dives); + devdata->log->dives.record_dive(std::move(dive)); return true; error_exit: dc_parser_destroy(parser); - free_dive(dive); return true; - } #ifndef O_BINARY @@ -916,7 +881,7 @@ static void do_save_fingerprint(device_data_t *devdata, const char *tmp, const c return; if (verbose) - dev_info(devdata, "Saving fingerprint for %08x:%08x to '%s'", + dev_info("Saving fingerprint for %08x:%08x to '%s'", devdata->fdeviceid, devdata->fdiveid, final); /* The fingerprint itself.. */ @@ -942,11 +907,11 @@ static std::string fingerprint_file(device_data_t *devdata) uint32_t model, serial; // Model hash and libdivecomputer 32-bit 'serial number' for the file name - model = calculate_string_hash(devdata->model); + model = calculate_string_hash(devdata->model.c_str()); serial = devdata->devinfo.serial; return format_string_std("%s/fingerprints/%04x.%u", - system_default_directory(), + system_default_directory().c_str(), model, serial); } @@ -987,7 +952,7 @@ static void save_fingerprint(device_data_t *devdata) return; // Make sure the fingerprints directory exists - std::string dir = format_string_std("%s/fingerprints", system_default_directory()); + std::string dir = system_default_directory() + "/fingerprints"; subsurface_mkdir(dir.c_str()); std::string final = fingerprint_file(devdata); @@ -1016,16 +981,16 @@ static void verify_fingerprint(dc_device_t *device, device_data_t *devdata, cons memcpy(&diveid, buffer + size + 4, 4); if (verbose) - dev_info(devdata, " ... fingerprinted dive %08x:%08x", deviceid, diveid); + dev_info(" ... fingerprinted dive %08x:%08x", deviceid, diveid); /* Only use it if we *have* that dive! */ - if (!has_dive(deviceid, diveid)) { + if (!divelog.dives.has_dive(deviceid, diveid)) { if (verbose) - dev_info(devdata, " ... dive not found"); + dev_info(" ... dive not found"); return; } dc_device_set_fingerprint(device, buffer, size); if (verbose) - dev_info(devdata, " ... fingerprint of size %zu", size); + dev_info(" ... fingerprint of size %zu", size); } /* @@ -1035,27 +1000,25 @@ static void verify_fingerprint(dc_device_t *device, device_data_t *devdata, cons */ static void lookup_fingerprint(dc_device_t *device, device_data_t *devdata) { - const unsigned char *raw_data; - if (devdata->force_download) return; /* first try our in memory data - raw_data is owned by the table, the dc_device_set_fingerprint function copies the data */ - int fsize = get_fingerprint_data(&fingerprint_table, calculate_string_hash(devdata->model), devdata->devinfo.serial, &raw_data); + auto [fsize, raw_data] = get_fingerprint_data(fingerprints, calculate_string_hash(devdata->model.c_str()), devdata->devinfo.serial); if (fsize) { if (verbose) - dev_info(devdata, "... found fingerprint in dive table"); + dev_info("... found fingerprint in dive table"); dc_device_set_fingerprint(device, raw_data, fsize); return; } /* now check if we have a fingerprint on disk */ std::string cachename = fingerprint_file(devdata); if (verbose) - dev_info(devdata, "Looking for fingerprint in '%s'", cachename.c_str()); + dev_info("Looking for fingerprint in '%s'", cachename.c_str()); auto [mem, err] = readfile(cachename.c_str()); if (err > 0) { if (verbose) - dev_info(devdata, " ... got %zu bytes", mem.size()); + dev_info(" ... got %zu bytes", mem.size()); verify_fingerprint(device, devdata, (unsigned char *)mem.data(), mem.size()); } } @@ -1071,7 +1034,7 @@ static void event_cb(dc_device_t *device, dc_event_type_t event, const void *dat switch (event) { case DC_EVENT_WAITING: - dev_info(devdata, translate("gettextFromC", "Event: waiting for user action")); + dev_info(translate("gettextFromC", "Event: waiting for user action")); break; case DC_EVENT_PROGRESS: /* this seems really dumb... but having no idea what is happening on long @@ -1083,7 +1046,7 @@ static void event_cb(dc_device_t *device, dc_event_type_t event, const void *dat last = progress->current; if (progress->current > last + 10240) { last = progress->current; - dev_info(NULL, translate("gettextFromC", "read %dkb"), progress->current / 1024); + dev_info(translate("gettextFromC", "read %dkb"), progress->current / 1024); } if (progress->maximum) progress_bar_fraction = (double)progress->current / (double)progress->maximum; @@ -1097,14 +1060,14 @@ static void event_cb(dc_device_t *device, dc_event_type_t event, const void *dat devdata->descriptor = better_descriptor; devdata->product = dc_descriptor_get_product(better_descriptor); devdata->vendor = dc_descriptor_get_vendor(better_descriptor); - devdata->model = str_printf("%s %s", devdata->vendor, devdata->product); + devdata->model = devdata->vendor + " " + devdata->product; } else { report_info("EVENT_DEVINFO gave us a different detected product (model %d instead of %d), but that one is unknown.", devinfo->model, dc_descriptor_get_model(devdata->descriptor)); } } - dev_info(devdata, translate("gettextFromC", "model=%s firmware=%u serial=%u"), - devdata->product, devinfo->firmware, devinfo->serial); + dev_info(translate("gettextFromC", "model=%s firmware=%u serial=%u"), + devdata->product.c_str(), devinfo->firmware, devinfo->serial); if (devdata->libdc_logfile) { fprintf(devdata->libdc_logfile, "Event: model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)\n", devinfo->model, devinfo->model, @@ -1117,7 +1080,7 @@ static void event_cb(dc_device_t *device, dc_event_type_t event, const void *dat break; case DC_EVENT_CLOCK: - dev_info(devdata, translate("gettextFromC", "Event: systime=%" PRId64 ", devtime=%u\n"), + dev_info(translate("gettextFromC", "Event: systime=%" PRId64 ", devtime=%u\n"), (uint64_t)clock->systime, clock->devtime); if (devdata->libdc_logfile) { fprintf(devdata->libdc_logfile, "Event: systime=%" PRId64 ", devtime=%u\n", @@ -1144,18 +1107,18 @@ static int cancel_cb(void *) return import_thread_cancelled; } -static const char *do_device_import(device_data_t *data) +static std::string do_device_import(device_data_t *data) { dc_status_t rc; dc_device_t *device = data->device; - data->model = str_printf("%s %s", data->vendor, data->product); + data->model = data->vendor + " " + data->product; // Register the event handler. int events = DC_EVENT_WAITING | DC_EVENT_PROGRESS | DC_EVENT_DEVINFO | DC_EVENT_CLOCK | DC_EVENT_VENDOR; rc = dc_device_set_events(device, events, event_cb, data); if (rc != DC_STATUS_SUCCESS) { - dev_info(data, "Import error: %s", errmsg(rc)); + dev_info("Import error: %s", errmsg(rc)); return translate("gettextFromC", "Error registering the event handler."); } @@ -1163,7 +1126,7 @@ static const char *do_device_import(device_data_t *data) // Register the cancellation handler. rc = dc_device_set_cancel(device, cancel_cb, data); if (rc != DC_STATUS_SUCCESS) { - dev_info(data, "Import error: %s", errmsg(rc)); + dev_info("Import error: %s", errmsg(rc)); return translate("gettextFromC", "Error registering the cancellation handler."); } @@ -1188,7 +1151,7 @@ static const char *do_device_import(device_data_t *data) if (rc == DC_STATUS_UNSUPPORTED) return translate("gettextFromC", "Dumping not supported on this device"); - dev_info(data, "Import error: %s", errmsg(rc)); + dev_info("Import error: %s", errmsg(rc)); return translate("gettextFromC", "Dive data dumping error"); } @@ -1198,38 +1161,32 @@ static const char *do_device_import(device_data_t *data) if (rc != DC_STATUS_SUCCESS) { progress_bar_fraction = 0.0; - dev_info(data, "Import error: %s", errmsg(rc)); + dev_info("Import error: %s", errmsg(rc)); return translate("gettextFromC", "Dive data import error"); } } /* All good */ - return NULL; + return std::string(); } -static dc_timer_t *logfunc_timer = NULL; void logfunc(dc_context_t *, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata) { const char *loglevels[] = { "NONE", "ERROR", "WARNING", "INFO", "DEBUG", "ALL" }; - if (logfunc_timer == NULL) - dc_timer_new(&logfunc_timer); + static const auto start(std::chrono::steady_clock::now()); + auto now(std::chrono::steady_clock::now()); + double elapsed_seconds = std::chrono::duration(now - start).count(); FILE *fp = (FILE *)userdata; - dc_usecs_t now = 0; - dc_timer_now(logfunc_timer, &now); - - unsigned long seconds = now / 1000000; - unsigned long microseconds = now % 1000000; - if (loglevel == DC_LOGLEVEL_ERROR || loglevel == DC_LOGLEVEL_WARNING) { - fprintf(fp, "[%li.%06li] %s: %s [in %s:%d (%s)]\n", - seconds, microseconds, + fprintf(fp, "[%.6f] %s: %s [in %s:%d (%s)]\n", + elapsed_seconds, loglevels[loglevel], msg, file, line, function); } else { - fprintf(fp, "[%li.%06li] %s: %s\n", seconds, microseconds, loglevels[loglevel], msg); + fprintf(fp, "[%6f] %s: %s\n", elapsed_seconds, loglevels[loglevel], msg); } } @@ -1269,7 +1226,7 @@ unsigned int get_supported_transports(device_data_t *data) */ if (data->bluetooth_mode) { supported &= (DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE); - if (!strncmp(data->devname, "LE:", 3)) + if (starts_with(data->devname, "LE:")) supported &= DC_TRANSPORT_BLE; } else { supported &= ~(DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE); @@ -1294,7 +1251,7 @@ static dc_status_t usbhid_device_open(dc_iostream_t **iostream, dc_context_t *co ERROR("didn't find HID device"); return DC_STATUS_NODEVICE; } - dev_info(data, "Opening USB HID device for %04x:%04x", + dev_info("Opening USB HID device for %04x:%04x", dc_usbhid_device_get_vid(device), dc_usbhid_device_get_pid(device)); rc = dc_usbhid_open(iostream, context, device); @@ -1317,7 +1274,7 @@ static dc_status_t usb_device_open(dc_iostream_t **iostream, dc_context_t *conte if (!device) return DC_STATUS_NODEVICE; - dev_info(data, "Opening USB device for %04x:%04x", + dev_info("Opening USB device for %04x:%04x", dc_usb_device_get_vid(device), dc_usb_device_get_pid(device)); rc = dc_usb_open(iostream, context, device); @@ -1343,9 +1300,9 @@ static dc_status_t irda_device_open(dc_iostream_t **iostream, dc_context_t *cont // If that fails, use the device name. This will // use address 0 if it's not a number. if (!address) - address = strtoul(data->devname, NULL, 0); + std::from_chars(data->devname.c_str(), data->devname.c_str() + data->devname.size(), address); - dev_info(data, "Opening IRDA address %u", address); + dev_info("Opening IRDA address %u", address); return dc_irda_open(&data->iostream, context, address, 1); } @@ -1366,11 +1323,11 @@ static dc_status_t bluetooth_device_open(dc_context_t *context, device_data_t *d dc_iterator_free (iterator); if (!address) { - dev_info(data, "No rfcomm device found"); + dev_info("No rfcomm device found"); return DC_STATUS_NODEVICE; } - dev_info(data, "Opening rfcomm address %llu", address); + dev_info("Opening rfcomm address %llu", address); return dc_bluetooth_open(&data->iostream, context, address, 0); } #endif @@ -1386,16 +1343,16 @@ dc_status_t divecomputer_device_open(device_data_t *data) transports &= supported; if (!transports) { - dev_info(data, "Dive computer transport not supported"); + dev_info("Dive computer transport not supported"); return DC_STATUS_UNSUPPORTED; } #ifdef BT_SUPPORT if (transports & DC_TRANSPORT_BLUETOOTH) { - dev_info(data, "Opening rfcomm stream %s", data->devname); + dev_info("Opening rfcomm stream %s", data->devname.c_str()); #if defined(__ANDROID__) || defined(__APPLE__) // we don't have BT on iOS in the first place, so this is for Android and macOS - rc = rfcomm_stream_open(&data->iostream, context, data->devname); + rc = rfcomm_stream_open(&data->iostream, context, data->devname.c_str()); #else rc = bluetooth_device_open(context, data); #endif @@ -1406,53 +1363,53 @@ dc_status_t divecomputer_device_open(device_data_t *data) #ifdef BLE_SUPPORT if (transports & DC_TRANSPORT_BLE) { - dev_info(data, "Connecting to BLE device %s", data->devname); - rc = ble_packet_open(&data->iostream, context, data->devname, data); + dev_info("Connecting to BLE device %s", data->devname.c_str()); + rc = ble_packet_open(&data->iostream, context, data->devname.c_str(), data); if (rc == DC_STATUS_SUCCESS) return rc; } #endif if (transports & DC_TRANSPORT_USBHID) { - dev_info(data, "Connecting to USB HID device"); + dev_info("Connecting to USB HID device"); rc = usbhid_device_open(&data->iostream, context, data); if (rc == DC_STATUS_SUCCESS) return rc; } if (transports & DC_TRANSPORT_USB) { - dev_info(data, "Connecting to native USB device"); + dev_info("Connecting to native USB device"); rc = usb_device_open(&data->iostream, context, data); if (rc == DC_STATUS_SUCCESS) return rc; } if (transports & DC_TRANSPORT_SERIAL) { - dev_info(data, "Opening serial device %s", data->devname); + dev_info("Opening serial device %s", data->devname.c_str()); #ifdef SERIAL_FTDI - if (!strcasecmp(data->devname, "ftdi")) + if (!strcasecmp(data->devname.c_str(), "ftdi")) return ftdi_open(&data->iostream, context); #endif #ifdef __ANDROID__ if (data->androidUsbDeviceDescriptor) return serial_usb_android_open(&data->iostream, context, data->androidUsbDeviceDescriptor); #endif - rc = dc_serial_open(&data->iostream, context, data->devname); + rc = dc_serial_open(&data->iostream, context, data->devname.c_str()); if (rc == DC_STATUS_SUCCESS) return rc; } if (transports & DC_TRANSPORT_IRDA) { - dev_info(data, "Connecting to IRDA device"); + dev_info("Connecting to IRDA device"); rc = irda_device_open(&data->iostream, context, data); if (rc == DC_STATUS_SUCCESS) return rc; } if (transports & DC_TRANSPORT_USBSTORAGE) { - dev_info(data, "Opening USB storage at %s", data->devname); - rc = dc_usb_storage_open(&data->iostream, context, data->devname); + dev_info("Opening USB storage at %s", data->devname.c_str()); + rc = dc_usb_storage_open(&data->iostream, context, data->devname.c_str()); if (rc == DC_STATUS_SUCCESS) return rc; } @@ -1468,10 +1425,9 @@ static dc_status_t sync_divecomputer_time(dc_device_t *device) return dc_device_timesync(device, &now); } -const char *do_libdivecomputer_import(device_data_t *data) +std::string do_libdivecomputer_import(device_data_t *data) { dc_status_t rc; - const char *err; FILE *fp = NULL; import_dive_number = 0; @@ -1498,44 +1454,44 @@ const char *do_libdivecomputer_import(device_data_t *data) fprintf(data->libdc_logfile, "built with libdivecomputer v%s\n", dc_version(NULL)); } - err = translate("gettextFromC", "Unable to open %s %s (%s)"); + std::string err = translate("gettextFromC", "Unable to open %s %s (%s)"); rc = divecomputer_device_open(data); if (rc != DC_STATUS_SUCCESS) { - dev_info(data, "Import error: %s", errmsg(rc)); + dev_info("Import error: %s", errmsg(rc)); } else { - dev_info(data, "Connecting ..."); + dev_info("Connecting ..."); rc = dc_device_open(&data->device, data->context, data->descriptor, data->iostream); if (rc != DC_STATUS_SUCCESS) { INFO("dc_device_open error value of %d", rc); - if (subsurface_access(data->devname, R_OK | W_OK) != 0) + if (subsurface_access(data->devname.c_str(), R_OK | W_OK) != 0) #if defined(SUBSURFACE_MOBILE) err = translate("gettextFromC", "Error opening the device %s %s (%s).\nIn most cases, in order to debug this issue, it is useful to send the developers the log files. You can copy them to the clipboard in the About dialog."); #else err = translate("gettextFromC", "Error opening the device %s %s (%s).\nIn most cases, in order to debug this issue, a libdivecomputer logfile will be useful.\nYou can create this logfile by selecting the corresponding checkbox in the download dialog."); #endif } else { - dev_info(data, "Starting import ..."); + dev_info("Starting import ..."); err = do_device_import(data); /* TODO: Show the logfile to the user on error. */ - dev_info(data, "Import complete"); + dev_info("Import complete"); - if (!err && data->sync_time) { - dev_info(data, "Syncing dive computer time ..."); + if (err.empty() && data->sync_time) { + dev_info("Syncing dive computer time ..."); rc = sync_divecomputer_time(data->device); switch (rc) { case DC_STATUS_SUCCESS: - dev_info(data, "Time sync complete"); + dev_info("Time sync complete"); break; case DC_STATUS_UNSUPPORTED: - dev_info(data, "Time sync not supported by dive computer"); + dev_info("Time sync not supported by dive computer"); break; default: - dev_info(data, "Time sync failed"); + dev_info("Time sync failed"); break; } @@ -1543,8 +1499,8 @@ const char *do_libdivecomputer_import(device_data_t *data) dc_device_close(data->device); data->device = NULL; - if (!data->log->dives->nr) - dev_info(data, translate("gettextFromC", "No new dives downloaded from dive computer")); + if (data->log->dives.empty()) + dev_info(translate("gettextFromC", "No new dives downloaded from dive computer")); } dc_iostream_close(data->iostream); data->iostream = NULL; @@ -1570,7 +1526,7 @@ const char *do_libdivecomputer_import(device_data_t *data) */ save_fingerprint(data); if (data->fingerprint && data->fdiveid) - create_fingerprint_node(&fingerprint_table, calculate_string_hash(data->model), data->devinfo.serial, + create_fingerprint_node(fingerprints, calculate_string_hash(data->model.c_str()), data->devinfo.serial, data->fingerprint, data->fsize, data->fdeviceid, data->fdiveid); free(data->fingerprint); data->fingerprint = NULL; @@ -1619,7 +1575,7 @@ dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned report_error("Error parsing the dive header data. Dive # %d: %s", dive->number, errmsg(rc)); } } - rc = dc_parser_samples_foreach (parser, sample_cb, &dive->dc); + rc = dc_parser_samples_foreach (parser, sample_cb, &dive->dcs[0]); if (rc != DC_STATUS_SUCCESS) { report_error("Error parsing the sample data. Dive # %d: %s", dive->number, errmsg(rc)); dc_parser_destroy (parser); diff --git a/core/libdivecomputer.h b/core/libdivecomputer.h index 78306a637..8a9dccf82 100644 --- a/core/libdivecomputer.h +++ b/core/libdivecomputer.h @@ -4,6 +4,7 @@ #include #include +#include /* libdivecomputer */ @@ -20,47 +21,46 @@ #define dc_usb_storage_open(stream, context, devname) (DC_STATUS_UNSUPPORTED) #endif -#ifdef __cplusplus -extern "C" { -#else -#include -#endif - struct dive; struct divelog; struct devices; -typedef struct { - dc_descriptor_t *descriptor; - const char *vendor, *product, *devname; - const char *model, *btname; - unsigned char *fingerprint; - unsigned int fsize, fdeviceid, fdiveid; - struct dc_event_devinfo_t devinfo; - uint32_t diveid; - dc_device_t *device; - dc_context_t *context; - dc_iostream_t *iostream; - bool force_download; - bool libdc_log; - bool libdc_dump; - bool bluetooth_mode; - bool sync_time; - FILE *libdc_logfile; - struct divelog *log; - void *androidUsbDeviceDescriptor; -} device_data_t; +struct device_data_t { + dc_descriptor_t *descriptor = nullptr; + std::string vendor, product, devname; + std::string model, btname; + unsigned char *fingerprint = nullptr; + unsigned int fsize = 0, fdeviceid = 0, fdiveid = 0; + struct dc_event_devinfo_t devinfo = { }; + uint32_t diveid = 0; + dc_device_t *device = nullptr; + dc_context_t *context = nullptr; + dc_iostream_t *iostream = nullptr; + bool force_download = false; + bool libdc_log = false; + bool libdc_dump = false; + bool bluetooth_mode = false; + bool sync_time = false; + FILE *libdc_logfile = nullptr; + struct divelog *log = nullptr; + void *androidUsbDeviceDescriptor = nullptr; + device_data_t(); + ~device_data_t(); + device_data_t(const device_data_t &) = default; + device_data_t(device_data_t &&) = default; + device_data_t &operator=(const device_data_t &) = default; + device_data_t &operator=(device_data_t &&) = default; +}; const char *errmsg (dc_status_t rc); -const char *do_libdivecomputer_import(device_data_t *data); -const char *do_uemis_import(device_data_t *data); +std::string do_libdivecomputer_import(device_data_t *data); dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size); void logfunc(dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata); dc_descriptor_t *get_descriptor(dc_family_t type, unsigned int model); extern int import_thread_cancelled; -extern const char *progress_bar_text; -extern void (*progress_callback)(const char *text); +extern std::string progress_bar_text; +extern void (*progress_callback)(const std::string &text); extern double progress_bar_fraction; dc_status_t ble_packet_open(dc_iostream_t **iostream, dc_context_t *context, const char* devaddr, void *userdata); @@ -72,13 +72,7 @@ dc_status_t divecomputer_device_open(device_data_t *data); unsigned int get_supported_transports(device_data_t *data); -#ifdef __cplusplus -} - -#include extern std::string logfile_name; extern std::string dumpfile_name; -#endif - #endif // LIBDIVECOMPUTER_H diff --git a/core/liquivision.cpp b/core/liquivision.cpp index f748a2de6..9146d966e 100644 --- a/core/liquivision.cpp +++ b/core/liquivision.cpp @@ -2,7 +2,6 @@ #include #include -#include "ssrf.h" #include "divesite.h" #include "dive.h" #include "divelog.h" @@ -132,43 +131,41 @@ static int handle_event_ver3(int code, const unsigned char *ps, unsigned int ps_ return skip; } -static void parse_dives(int log_version, const unsigned char *buf, unsigned int buf_size, struct dive_table *table, struct dive_site_table *sites) +static void parse_dives(int log_version, const unsigned char *buf, unsigned int buf_size, struct dive_table &table, dive_site_table &sites) { unsigned int ptr = 0; unsigned char model; - struct dive *dive; struct divecomputer *dc; struct sample *sample; while (ptr < buf_size) { int i; - dive = alloc_dive(); + auto dive = std::make_unique(); memset(&sensor_ids, 0, sizeof(sensor_ids)); - dc = &dive->dc; + dc = &dive->dcs[0]; /* Just the main cylinder until we can handle the buddy cylinder porperly */ for (i = 0; i < 1; i++) { - cylinder_t cyl = empty_cylinder; - fill_default_cylinder(dive, &cyl); - add_cylinder(&dive->cylinders, i, cyl); + cylinder_t cyl = default_cylinder(dive.get()); + dive->cylinders.add(i, std::move(cyl)); } // Model 0=Xen, 1,2=Xeo, 4=Lynx, other=Liquivision model = *(buf + ptr); switch (model) { case 0: - dc->model = strdup("Xen"); + dc->model = "Xen"; break; case 1: case 2: - dc->model = strdup("Xeo"); + dc->model = "Xeo"; break; case 4: - dc->model = strdup("Lynx"); + dc->model = "Lynx"; break; default: - dc->model = strdup("Liquivision"); + dc->model = "Liquivision"; break; } ptr++; @@ -191,7 +188,7 @@ static void parse_dives(int log_version, const unsigned char *buf, unsigned int /* Store the location only if we have one */ if (!location.empty()) - add_dive_to_dive_site(dive, find_or_create_dive_site_with_name(location.c_str(), sites)); + sites.find_or_create(location)->add_dive(dive.get()); ptr += len + 4 + place_len; @@ -202,7 +199,7 @@ static void parse_dives(int log_version, const unsigned char *buf, unsigned int // Blank notes are better than the default text std::string notes((char *)buf + ptr, len); if (!starts_with(notes, "Comment ...")) - dive->notes = strdup(notes.c_str()); + dive->notes = std::move(notes); ptr += len; dive->id = array_uint32_le(buf + ptr); @@ -336,7 +333,6 @@ static void parse_dives(int log_version, const unsigned char *buf, unsigned int sample->depth.mm = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm sample->temperature.mkelvin = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK add_sample_pressure(sample, event.pressure.sensor, event.pressure.mbar); - finish_sample(dc); break; } else if (event.time > sample_time) { @@ -344,7 +340,6 @@ static void parse_dives(int log_version, const unsigned char *buf, unsigned int sample->time.seconds = sample_time; sample->depth.mm = depth_mm; sample->temperature.mkelvin = temp_mk; - finish_sample(dc); d++; continue; @@ -353,7 +348,6 @@ static void parse_dives(int log_version, const unsigned char *buf, unsigned int sample->depth.mm = depth_mm; sample->temperature.mkelvin = temp_mk; add_sample_pressure(sample, event.pressure.sensor, event.pressure.mbar); - finish_sample(dc); d++; break; @@ -372,7 +366,6 @@ static void parse_dives(int log_version, const unsigned char *buf, unsigned int sample->temperature.mkelvin = last_temp + (temp_mk - last_temp) * ((int)event.time - (int)last_time) / sample_interval; } - finish_sample(dc); break; } @@ -387,7 +380,6 @@ static void parse_dives(int log_version, const unsigned char *buf, unsigned int sample->depth.mm = array_uint16_le(ds + d * 2) * 10; // cm->mm sample->temperature.mkelvin = C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10); - finish_sample(dc); } if (log_version == 3 && model == 4) { @@ -415,17 +407,11 @@ static void parse_dives(int log_version, const unsigned char *buf, unsigned int } // End dive - record_dive_to_table(dive, table); - dive = NULL; + table.record_dive(std::move(dive)); // Advance ptr for next dive ptr += ps_ptr + 4; } // while - - //DEBUG save_dives("/tmp/test.xml"); - - // if we bailed out of the loop, the dive hasn't been recorded and dive hasn't been set to NULL - free_dive(dive); } int try_to_open_liquivision(const char *, std::string &mem, struct divelog *log) diff --git a/core/load-git.cpp b/core/load-git.cpp index ba25794e1..d7233e768 100644 --- a/core/load-git.cpp +++ b/core/load-git.cpp @@ -1,5 +1,4 @@ // SPDX-License-Identifier: GPL-2.0 -#include "ssrf.h" #include #include #include @@ -16,21 +15,22 @@ #include "gettext.h" +#include "device.h" #include "dive.h" #include "divelog.h" #include "divesite.h" -#include "event.h" #include "errorhelper.h" -#include "sample.h" -#include "subsurface-string.h" +#include "event.h" #include "format.h" -#include "trip.h" -#include "device.h" #include "git-access.h" #include "picture.h" #include "qthelper.h" -#include "tag.h" +#include "sample.h" +#include "subsurface-string.h" #include "subsurface-time.h" +#include "tag.h" +#include "trip.h" +#include "version.h" // TODO: Should probably be moved to struct divelog to allow for multi-document std::string saved_git_id; @@ -38,8 +38,8 @@ std::string saved_git_id; struct git_parser_state { git_repository *repo = nullptr; struct divecomputer *active_dc = nullptr; - struct dive *active_dive = nullptr; - dive_trip_t *active_trip = nullptr; + std::unique_ptr active_dive; + std::unique_ptr active_trip; std::string fulltext_mode; std::string fulltext_query; std::string filter_constraint_type; @@ -47,7 +47,7 @@ struct git_parser_state { std::string filter_constraint_range_mode; bool filter_constraint_negate = false; std::string filter_constraint_data; - struct picture active_pic = { 0 }; + struct picture active_pic; struct dive_site *active_site = nullptr; std::unique_ptr active_filter; struct divelog *log = nullptr; @@ -93,16 +93,17 @@ static weight_t get_weight(const char *line) static pressure_t get_airpressure(const char *line) { - pressure_t p; - p.mbar = lrint(ascii_strtod(line, NULL)); - return p; + return pressure_t { .mbar = int_cast(ascii_strtod(line, NULL)) }; } static pressure_t get_pressure(const char *line) { - pressure_t p; - p.mbar = lrint(1000 * ascii_strtod(line, NULL)); - return p; + return pressure_t { .mbar = int_cast(1000 * ascii_strtod(line, NULL)) }; +} + +static o2pressure_t get_o2pressure(const char *line) +{ + return o2pressure_t { .mbar = int_cast(1000 * ascii_strtod(line, NULL)) }; } static int get_salinity(const char *line) @@ -172,16 +173,16 @@ static int get_hex(const char *line) static void parse_dive_gps(char *line, struct git_parser_state *state) { location_t location; - struct dive_site *ds = get_dive_site_for_dive(state->active_dive); + struct dive_site *ds = state->active_dive->dive_site; parse_location(line, &location); if (!ds) { - ds = get_dive_site_by_gps(&location, state->log->sites); + ds = state->log->sites.get_by_gps(&location); if (!ds) - ds = create_dive_site_with_gps("", &location, state->log->sites); - add_dive_to_dive_site(state->active_dive, ds); + ds = state->log->sites.create(std::string(), location); + ds->add_dive(state->active_dive.get()); } else { - if (dive_site_has_gps_location(ds) && !same_location(&ds->location, &location)) { + if (ds->has_gps_location() && ds->location != location) { std::string coords = printGPSCoordsC(&location); // we have a dive site that already has GPS coordinates // note 1: there will be much less copying once the core @@ -189,10 +190,8 @@ static void parse_dive_gps(char *line, struct git_parser_state *state) // note 2: we could include the first newline in the // translation string, but that would be weird and cause // a new string. - std::string new_text = std::string(ds->notes) + '\n' + - format_string_std(translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords.c_str()); - free(ds->notes); - ds->notes = strdup(new_text.c_str()); + ds->notes += '\n'; + ds->notes += format_string_std(translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords.c_str()); } ds->location = location; } @@ -210,54 +209,43 @@ static std::string get_first_converted_string(struct git_parser_state *state) return std::move(state->converted_strings.front()); } -// This is a dummy function that converts the first -// converted string to a newly allocated C-string. -// Will be removed when the core data structures are -// converted to std::string. -static char *get_first_converted_string_c(struct git_parser_state *state) -{ - return strdup(get_first_converted_string(state).c_str()); -} - static void parse_dive_location(char *, struct git_parser_state *state) { std::string name = get_first_converted_string(state); - struct dive_site *ds = get_dive_site_for_dive(state->active_dive); + struct dive_site *ds = state->active_dive->dive_site; if (!ds) { - ds = get_dive_site_by_name(name.c_str(), state->log->sites); + ds = state->log->sites.get_by_name(name); if (!ds) - ds = create_dive_site(name.c_str(), state->log->sites); - add_dive_to_dive_site(state->active_dive, ds); + ds = state->log->sites.create(name); + ds->add_dive(state->active_dive.get()); } else { // we already had a dive site linked to the dive - if (empty_string(ds->name)) { - free(ds->name); // empty_string could mean pointer to a 0-byte! - ds->name = strdup(name.c_str()); + if (ds->name.empty()) { + ds->name = name.c_str(); } else { // and that dive site had a name. that's weird - if our name is different, add it to the notes - if (!same_string(ds->name, name.c_str())) { - std::string new_string = std::string(ds->notes) + '\n' + - format_string_std(translate("gettextFromC", "additional name for site: %s\n"), name.c_str()); - ds->notes = strdup(new_string.c_str()); + if (ds->name == name) { + ds->notes += '\n'; + ds->notes += format_string_std(translate("gettextFromC", "additional name for site: %s\n"), name.c_str()); } } } } static void parse_dive_diveguide(char *, struct git_parser_state *state) -{ state->active_dive->diveguide = get_first_converted_string_c(state); } +{ state->active_dive->diveguide = get_first_converted_string(state); } static void parse_dive_buddy(char *, struct git_parser_state *state) -{ state->active_dive->buddy = get_first_converted_string_c(state); } +{ state->active_dive->buddy = get_first_converted_string(state); } static void parse_dive_suit(char *, struct git_parser_state *state) -{ state->active_dive->suit = get_first_converted_string_c(state); } +{ state->active_dive->suit = get_first_converted_string(state); } static void parse_dive_notes(char *, struct git_parser_state *state) -{ state->active_dive->notes = get_first_converted_string_c(state); } +{ state->active_dive->notes = get_first_converted_string(state); } static void parse_dive_divesiteid(char *line, struct git_parser_state *state) -{ add_dive_to_dive_site(state->active_dive, get_dive_site_by_uuid(get_hex(line), state->log->sites)); } +{ state->log->sites.get_by_uuid(get_hex(line))->add_dive(state->active_dive.get()); } /* * We can have multiple tags. @@ -266,7 +254,7 @@ static void parse_dive_tags(char *, struct git_parser_state *state) { for (const std::string &tag: state->converted_strings) { if (!tag.empty()) - taglist_add_tag(&state->active_dive->tag_list, tag.c_str()); + taglist_add_tag(state->active_dive->tags, tag.c_str()); } } @@ -314,13 +302,13 @@ static void parse_dive_invalid(char *, struct git_parser_state *state) } static void parse_site_description(char *, struct git_parser_state *state) -{ state->active_site->description = get_first_converted_string_c(state); } +{ state->active_site->description = get_first_converted_string(state); } static void parse_site_name(char *, struct git_parser_state *state) -{ state->active_site->name = get_first_converted_string_c(state); } +{ state->active_site->name = get_first_converted_string(state); } static void parse_site_notes(char *, struct git_parser_state *state) -{ state->active_site->notes = get_first_converted_string_c(state); } +{ state->active_site->notes = get_first_converted_string(state); } static void parse_site_gps(char *line, struct git_parser_state *state) { @@ -332,8 +320,8 @@ static void parse_site_geo(char *line, struct git_parser_state *state) int origin; int category; sscanf(line, "cat %d origin %d \"", &category, &origin); - taxonomy_set_category(&state->active_site->taxonomy, (taxonomy_category)category, - get_first_converted_string(state).c_str(), (taxonomy_origin)origin); + taxonomy_set_category(state->active_site->taxonomy, (taxonomy_category)category, + get_first_converted_string(state), (taxonomy_origin)origin); } static std::string pop_cstring(struct git_parser_state *state, const char *err) @@ -394,7 +382,7 @@ static void parse_cylinder_keyvalue(void *_cylinder, const char *key, const std: return; } if (!strcmp(key, "description")) { - cylinder->type.description = strdup(value.c_str()); + cylinder->type.description = value; return; } if (!strcmp(key, "o2")) { @@ -438,7 +426,7 @@ static void parse_cylinder_keyvalue(void *_cylinder, const char *key, const std: static void parse_dive_cylinder(char *line, struct git_parser_state *state) { - cylinder_t cylinder = empty_cylinder; + cylinder_t cylinder; for (;;) { char c; @@ -449,9 +437,9 @@ static void parse_dive_cylinder(char *line, struct git_parser_state *state) line = parse_keyvalue_entry(parse_cylinder_keyvalue, &cylinder, line, state); } if (cylinder.cylinder_use == OXYGEN) - state->o2pressure_sensor = state->active_dive->cylinders.nr; + state->o2pressure_sensor = static_cast(state->active_dive->cylinders.size()); - add_cylinder(&state->active_dive->cylinders, state->active_dive->cylinders.nr, cylinder); + state->active_dive->cylinders.push_back(std::move(cylinder)); } static void parse_weightsystem_keyvalue(void *_ws, const char *key, const std::string &value) @@ -462,7 +450,7 @@ static void parse_weightsystem_keyvalue(void *_ws, const char *key, const std::s return; } if (!strcmp(key, "description")) { - ws->description = strdup(value.c_str()); + ws->description = value; return; } report_error("Unknown weightsystem key/value pair (%s/%s)", key, value.c_str()); @@ -470,7 +458,7 @@ static void parse_weightsystem_keyvalue(void *_ws, const char *key, const std::s static void parse_dive_weightsystem(char *line, struct git_parser_state *state) { - weightsystem_t ws = empty_weightsystem; + weightsystem_t ws; for (;;) { char c; @@ -481,7 +469,7 @@ static void parse_dive_weightsystem(char *line, struct git_parser_state *state) line = parse_keyvalue_entry(parse_weightsystem_keyvalue, &ws, line, state); } - add_to_weightsystem_table(&state->active_dive->weightsystems, state->active_dive->weightsystems.nr, ws); + state->active_dive->weightsystems.push_back(std::move(ws)); } static int match_action(char *line, void *data, @@ -570,43 +558,35 @@ static void parse_sample_keyvalue(void *_sample, const char *key, const std::str } if (!strcmp(key, "po2")) { - pressure_t p = get_pressure(value.c_str()); - sample->setpoint.mbar = p.mbar; + sample->setpoint = get_o2pressure(value.c_str()); return; } if (!strcmp(key, "sensor1")) { - pressure_t p = get_pressure(value.c_str()); - sample->o2sensor[0].mbar = p.mbar; + sample->o2sensor[0] = get_o2pressure(value.c_str()); return; } if (!strcmp(key, "sensor2")) { - pressure_t p = get_pressure(value.c_str()); - sample->o2sensor[1].mbar = p.mbar; + sample->o2sensor[1] = get_o2pressure(value.c_str()); return; } if (!strcmp(key, "sensor3")) { - pressure_t p = get_pressure(value.c_str()); - sample->o2sensor[2].mbar = p.mbar; + sample->o2sensor[2] = get_o2pressure(value.c_str()); return; } if (!strcmp(key, "sensor4")) { - pressure_t p = get_pressure(value.c_str()); - sample->o2sensor[3].mbar = p.mbar; + sample->o2sensor[3] = get_o2pressure(value.c_str()); return; } if (!strcmp(key, "sensor5")) { - pressure_t p = get_pressure(value.c_str()); - sample->o2sensor[4].mbar = p.mbar; + sample->o2sensor[4] = get_o2pressure(value.c_str()); return; } if (!strcmp(key, "sensor6")) { - pressure_t p = get_pressure(value.c_str()); - sample->o2sensor[5].mbar = p.mbar; + sample->o2sensor[5] = get_o2pressure(value.c_str()); return; } if (!strcmp(key, "o2pressure")) { - pressure_t p = get_pressure(value.c_str()); - sample->pressure[1].mbar = p.mbar; + sample->pressure[1] = get_pressure(value.c_str()); return; } if (!strcmp(key, "heartbeat")) { @@ -660,7 +640,7 @@ static char *parse_sample_unit(struct sample *sample, double val, char *unit) */ static int sanitize_sensor_id(const struct dive *d, int nr) { - return d && nr >= 0 && nr < d->cylinders.nr ? nr : NO_SENSOR; + return d && nr >= 0 && static_cast(nr) < d->cylinders.size() ? nr : NO_SENSOR; } /* @@ -684,13 +664,14 @@ static int sanitize_sensor_id(const struct dive *d, int nr) static struct sample *new_sample(struct git_parser_state *state) { struct sample *sample = prepare_sample(state->active_dc); - if (sample != state->active_dc->sample) { - memcpy(sample, sample - 1, sizeof(struct sample)); - sample->pressure[0].mbar = 0; - sample->pressure[1].mbar = 0; + size_t num_samples = state->active_dc->samples.size(); + if (num_samples >= 2) { + *sample = state->active_dc->samples[num_samples - 2]; + sample->pressure[0] = 0_bar; + sample->pressure[1] = 0_bar; } else { - sample->sensor[0] = sanitize_sensor_id(state->active_dive, !state->o2pressure_sensor); - sample->sensor[1] = sanitize_sensor_id(state->active_dive, state->o2pressure_sensor); + sample->sensor[0] = sanitize_sensor_id(state->active_dive.get(), !state->o2pressure_sensor); + sample->sensor[1] = sanitize_sensor_id(state->active_dive.get(), state->o2pressure_sensor); } return sample; } @@ -726,7 +707,6 @@ static void sample_parser(char *line, struct git_parser_state *state) line = parse_sample_unit(sample, val, line); } } - finish_sample(state->active_dc); } static void parse_dc_airtemp(char *line, struct git_parser_state *state) @@ -759,7 +739,7 @@ static void parse_dc_meandepth(char *line, struct git_parser_state *state) { state->active_dc->meandepth = get_depth(line); } static void parse_dc_model(char *, struct git_parser_state *state) -{ state->active_dc->model = get_first_converted_string_c(state); } +{ state->active_dc->model = get_first_converted_string(state); } static void parse_dc_numberofoxygensensors(char *line, struct git_parser_state *state) { state->active_dc->no_o2sensors = get_index(line); } @@ -799,7 +779,7 @@ static int get_divemode(const char *divemodestring) { struct parse_event { std::string name; int has_divemode = false; - struct event ev = { 0 }; + struct event ev; }; static void parse_event_keyvalue(void *_parse, const char *key, const std::string &value) @@ -837,14 +817,13 @@ static void parse_dc_keyvalue(char *line, struct git_parser_state *state) if (state->converted_strings.size() != 2) return; - add_extra_data(state->active_dc, state->converted_strings[0].c_str(), state->converted_strings[1].c_str()); + add_extra_data(state->active_dc, state->converted_strings[0], state->converted_strings[1]); } static void parse_dc_event(char *line, struct git_parser_state *state) { int m, s = 0; struct parse_event p; - struct event *ev; m = strtol(line, &line, 10); if (*line == ':') @@ -864,17 +843,17 @@ static void parse_dc_event(char *line, struct git_parser_state *state) if (p.has_divemode && p.name != "modechange") p.name = "modechange"; - ev = add_event(state->active_dc, p.ev.time.seconds, p.ev.type, p.ev.flags, p.ev.value, p.name.c_str()); + struct event *ev = add_event(state->active_dc, p.ev.time.seconds, p.ev.type, p.ev.flags, p.ev.value, p.name.c_str()); /* * Older logs might mark the dive to be CCR by having an "SP change" event at time 0:00. * Better to mark them being CCR on import so no need for special treatments elsewhere on * the code. */ - if (ev && p.ev.time.seconds == 0 && p.ev.type == SAMPLE_EVENT_PO2 && p.ev.value && state->active_dc->divemode==OC) + if (p.ev.time.seconds == 0 && p.ev.type == SAMPLE_EVENT_PO2 && p.ev.value && state->active_dc->divemode==OC) state->active_dc->divemode = CCR; - if (ev && event_is_gaschange(ev)) { + if (ev->is_gaschange()) { /* * We subtract one here because "0" is "no index", * and the parsing will add one for actual cylinder @@ -895,10 +874,10 @@ static void parse_trip_time(char *, struct git_parser_state *) { } static void parse_trip_location(char *, struct git_parser_state *state) -{ state->active_trip->location = get_first_converted_string_c(state); } +{ state->active_trip->location = get_first_converted_string(state); } static void parse_trip_notes(char *, struct git_parser_state *state) -{ state->active_trip->notes = get_first_converted_string_c(state); } +{ state->active_trip->notes = get_first_converted_string(state); } static void parse_settings_autogroup(char *, struct git_parser_state *state) { @@ -935,8 +914,8 @@ static void parse_settings_version(char *line, struct git_parser_state *) { int version = atoi(line); report_datafile_version(version); - if (version > DATAFORMAT_VERSION) - report_error("Git save file version %d is newer than version %d I know about", version, DATAFORMAT_VERSION); + if (version > dataformat_version) + report_error("Git save file version %d is newer than version %d I know about", version, dataformat_version); } /* The argument string is the version string of subsurface that saved things, just FYI */ @@ -1048,13 +1027,13 @@ static void parse_settings_fingerprint(char *line, struct git_parser_state *stat } if (verbose > 1) report_info("fingerprint %08x %08x %08x %08x %s\n", fph.model, fph.serial, fph.fdeviceid, fph.fdiveid, fph.hex_data.c_str()); - create_fingerprint_node_from_hex(&fingerprint_table, fph.model, fph.serial, - fph.hex_data.c_str(), fph.fdeviceid, fph.fdiveid); + create_fingerprint_node_from_hex(fingerprints, fph.model, fph.serial, + fph.hex_data, fph.fdeviceid, fph.fdiveid); } static void parse_picture_filename(char *, struct git_parser_state *state) { - state->active_pic.filename = get_first_converted_string_c(state); + state->active_pic.filename = get_first_converted_string(state); } static void parse_picture_gps(char *line, struct git_parser_state *state) @@ -1189,10 +1168,10 @@ static void parse_filter_preset_constraint(char *line, struct git_parser_state * line = parse_keyvalue_entry(parse_filter_preset_constraint_keyvalue, state, line, state); } - filter_preset_add_constraint(state->active_filter.get(), state->filter_constraint_type.c_str(), - state->filter_constraint_string_mode.c_str(), - state->filter_constraint_range_mode.c_str(), - state->filter_constraint_negate, state->filter_constraint_data.c_str()); + state->active_filter->add_constraint(state->filter_constraint_type, + state->filter_constraint_string_mode, + state->filter_constraint_range_mode, + state->filter_constraint_negate, state->filter_constraint_data); state->filter_constraint_type.clear(); state->filter_constraint_string_mode.clear(); state->filter_constraint_range_mode.clear(); @@ -1226,14 +1205,14 @@ static void parse_filter_preset_fulltext(char *line, struct git_parser_state *st line = parse_keyvalue_entry(parse_filter_preset_fulltext_keyvalue, state, line, state); } - filter_preset_set_fulltext(state->active_filter.get(), state->fulltext_query.c_str(), state->fulltext_mode.c_str()); + state->active_filter.get()->set_fulltext(std::move(state->fulltext_query), state->fulltext_mode); state->fulltext_mode.clear(); state->fulltext_query.clear(); } static void parse_filter_preset_name(char *, struct git_parser_state *state) { - filter_preset_set_name(state->active_filter.get(), get_first_converted_string_c(state)); + state->active_filter->name = get_first_converted_string(state); } /* These need to be sorted! */ @@ -1387,33 +1366,27 @@ static void for_each_line(git_blob *blob, line_fn_t *fn, struct git_parser_state static void finish_active_trip(struct git_parser_state *state) { - dive_trip_t *trip = state->active_trip; + auto &trip = state->active_trip; - if (trip) { - state->active_trip = NULL; - insert_trip(trip, state->log->trips); - } + if (trip) + state->log->trips.put(std::move(trip)); } static void finish_active_dive(struct git_parser_state *state) { - struct dive *dive = state->active_dive; - - if (dive) { - state->active_dive = NULL; - record_dive_to_table(dive, state->log->dives); - } + if (state->active_dive) + state->log->dives.record_dive(std::move(state->active_dive)); } static void create_new_dive(timestamp_t when, struct git_parser_state *state) { - state->active_dive = alloc_dive(); + state->active_dive = std::make_unique(); /* We'll fill in more data from the dive file */ state->active_dive->when = when; if (state->active_trip) - add_dive_to_trip(state->active_dive, state->active_trip); + state->active_trip->add_dive(state->active_dive.get()); } static bool validate_date(int yyyy, int mm, int dd) @@ -1443,7 +1416,7 @@ static int dive_trip_directory(const char *root, const char *name, struct git_pa if (!validate_date(yyyy, mm, dd)) return GIT_WALK_SKIP; finish_active_trip(state); - state->active_trip = alloc_trip(); + state->active_trip = std::make_unique(); return GIT_WALK_OK; } @@ -1533,7 +1506,7 @@ static int dive_directory(const char *root, const git_tree_entry *entry, const c finish_active_dive(state); create_new_dive(utc_mktime(&tm), state); - memcpy(state->active_dive->git_id, git_tree_entry_id(entry)->id, 20); + memcpy(state->active_dive->git_id.data(), git_tree_entry_id(entry)->id, 20); return GIT_WALK_OK; } @@ -1653,17 +1626,12 @@ static git_blob *git_tree_entry_blob(git_repository *repo, const git_tree_entry static struct divecomputer *create_new_dc(struct dive *dive) { - struct divecomputer *dc = &dive->dc; + struct divecomputer *dc = &dive->dcs.back(); - while (dc->next) - dc = dc->next; /* Did we already fill that in? */ - if (dc->samples || dc->model || dc->when) { - struct divecomputer *newdc = (divecomputer *)calloc(1, sizeof(*newdc)); - if (!newdc) - return NULL; - dc->next = newdc; - dc = newdc; + if (!dc->samples.empty() || !dc->model.empty() || dc->when) { + dive->dcs.emplace_back(); + dc = &dive->dcs.back(); } dc->when = dive->when; dc->duration = dive->duration; @@ -1683,7 +1651,7 @@ static int parse_divecomputer_entry(struct git_parser_state *state, const git_tr if (!blob) return report_error("Unable to read divecomputer file"); - state->active_dc = create_new_dc(state->active_dive); + state->active_dc = create_new_dc(state->active_dive.get()); for_each_line(blob, divecomputer_parser, state); git_blob_free(blob); state->active_dc = NULL; @@ -1698,13 +1666,12 @@ static int parse_divecomputer_entry(struct git_parser_state *state, const git_tr */ static int parse_dive_entry(struct git_parser_state *state, const git_tree_entry *entry, const char *suffix) { - struct dive *dive = state->active_dive; git_blob *blob = git_tree_entry_blob(state->repo, entry); if (!blob) return report_error("Unable to read dive file"); if (*suffix) - dive->number = atoi(suffix + 1); - clear_weightsystem_table(&state->active_dive->weightsystems); + state->active_dive->number = atoi(suffix + 1); + state->active_dive->weightsystems.clear(); state->o2pressure_sensor = 1; for_each_line(blob, dive_parser, state); git_blob_free(blob); @@ -1716,7 +1683,7 @@ static int parse_site_entry(struct git_parser_state *state, const git_tree_entry if (*suffix == '\0') return report_error("Dive site without uuid"); uint32_t uuid = strtoul(suffix, NULL, 16); - state->active_site = alloc_or_get_dive_site(uuid, state->log->sites); + state->active_site = state->log->sites.alloc_or_get(uuid); git_blob *blob = git_tree_entry_blob(state->repo, entry); if (!blob) return report_error("Unable to read dive site file"); @@ -1772,12 +1739,12 @@ static int parse_picture_entry(struct git_parser_state *state, const git_tree_en state->active_pic.offset.seconds = offset; for_each_line(blob, picture_parser, state); - add_picture(&state->active_dive->pictures, state->active_pic); + add_picture(state->active_dive->pictures, std::move(state->active_pic)); git_blob_free(blob); /* add_picture took ownership of the data - * clear out our copy just to be sure. */ - state->active_pic = empty_picture; + state->active_pic = picture(); return 0; } @@ -1792,7 +1759,7 @@ static int parse_filter_preset(struct git_parser_state *state, const git_tree_en git_blob_free(blob); - add_filter_preset_to_table(state->active_filter.get(), state->log->filter_presets); + state->log->filter_presets.add(*state->active_filter); state->active_filter.reset(); return 0; @@ -1800,8 +1767,8 @@ static int parse_filter_preset(struct git_parser_state *state, const git_tree_en static int walk_tree_file(const char *root, const git_tree_entry *entry, struct git_parser_state *state) { - struct dive *dive = state->active_dive; - dive_trip_t *trip = state->active_trip; + auto &dive = state->active_dive; + auto &trip = state->active_trip; const char *name = git_tree_entry_name(entry); if (verbose > 1) report_info("git load handling file %s\n", name); @@ -1831,7 +1798,7 @@ static int walk_tree_file(const char *root, const git_tree_entry *entry, struct return parse_settings_entry(state, entry); break; } - report_error("Unknown file %s%s (%p %p)", root, name, dive, trip); + report_error("Unknown file %s%s (%p %p)", root, name, dive.get(), trip.get()); return GIT_WALK_SKIP; } @@ -1854,12 +1821,12 @@ static int load_dives_from_tree(git_repository *repo, git_tree *tree, struct git return 0; } -extern "C" void clear_git_id(void) +void clear_git_id() { saved_git_id.clear(); } -extern "C" void set_git_id(const struct git_oid *id) +void set_git_id(const struct git_oid *id) { char git_id_buffer[GIT_OID_HEXSZ + 1]; diff --git a/core/macos.cpp b/core/macos.cpp index 1bec49fda..b38b07da7 100644 --- a/core/macos.cpp +++ b/core/macos.cpp @@ -1,13 +1,12 @@ // SPDX-License-Identifier: GPL-2.0 /* macos.c */ /* implements Mac OS X specific functions */ -#include "ssrf.h" #include #include #include -#include "dive.h" -#include "subsurface-string.h" #include "device.h" +#include "subsurfacestartup.h" +#include "subsurface-string.h" #include "libdivecomputer.h" #include #if !defined(__IPHONE_5_0) @@ -48,33 +47,31 @@ static std::string make_default_filename() return system_default_path() + "/" + user + ".xml"; } -extern "C" { - -const char mac_system_divelist_default_font[] = "Arial"; -const char *system_divelist_default_font = mac_system_divelist_default_font; +using namespace std::string_literals; +std::string system_divelist_default_font = "Arial"s; double system_divelist_default_font_size = -1.0; -void subsurface_OS_pref_setup(void) +void subsurface_OS_pref_setup() { // nothing } -bool subsurface_ignore_font(const char *) +bool subsurface_ignore_font(const std::string &) { // there are no old default fonts 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(); } -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; } int enumerate_devices(device_callback_t callback, void *userdata, unsigned int transport) @@ -185,12 +182,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 */ } @@ -199,5 +196,3 @@ bool subsurface_user_is_root() { return geteuid() == 0; } - -} diff --git a/core/membuffer.cpp b/core/membuffer.cpp index 2b8b7c8bc..dfb10ca90 100644 --- a/core/membuffer.cpp +++ b/core/membuffer.cpp @@ -12,42 +12,23 @@ #include "units.h" #include "membuffer.h" -membufferpp::membufferpp() - : membuffer{0, 0, nullptr} +membuffer::membuffer() { } -membufferpp::~membufferpp() +membuffer::~membuffer() { - free_buffer(this); -} - -/* Only for internal use */ -static char *detach_buffer(struct membuffer *b) -{ - char *result = b->buffer; - b->buffer = NULL; - b->len = 0; - b->alloc = 0; - return result; -} - -char *detach_cstring(struct membuffer *b) -{ - mb_cstring(b); - return detach_buffer(b); -} - -void free_buffer(struct membuffer *b) -{ - free(detach_buffer(b)); + free(buffer); } void flush_buffer(struct membuffer *b, FILE *f) { if (b->len) { fwrite(b->buffer, 1, b->len, f); - free_buffer(b); + free(b->buffer); + b->buffer = NULL; + b->len = 0; + b->alloc = 0; } } @@ -63,7 +44,7 @@ void strip_mb(struct membuffer *b) * interface very complex, we'll just die. It won't happen * unless you're running on a potato. */ -static void oom(void) +static void oom() { fprintf(stderr, "Out of memory\n"); exit(1); @@ -141,25 +122,6 @@ void put_vformat(struct membuffer *b, const char *fmt, va_list args) } } -/* Silly helper using membuffer */ -char *vformat_string(const char *fmt, va_list args) -{ - struct membuffer mb = { 0 }; - put_vformat(&mb, fmt, args); - return detach_cstring(&mb); -} - -char *format_string(const char *fmt, ...) -{ - va_list args; - char *result; - - va_start(args, fmt); - result = vformat_string(fmt, args); - va_end(args); - return result; -} - void put_format(struct membuffer *b, const char *fmt, ...) { va_list args; @@ -296,30 +258,3 @@ void put_quoted(struct membuffer *b, const char *text, int is_attribute, int is_ text = p; } } - -char *add_to_string_va(char *old, const char *fmt, va_list args) -{ - char *res; - struct membufferpp o, n; - put_vformat(&n, fmt, args); - put_format(&o, "%s\n%s", old ?: "", mb_cstring(&n)); - res = strdup(mb_cstring(&o)); - free((void *)old); - return res; -} - -/* this is a convenience function that cleverly adds text to a string, using our membuffer - * infrastructure. - * WARNING - this will free(old), the intended pattern is - * string = add_to_string(string, fmt, ...) - */ -char *add_to_string(char *old, const char *fmt, ...) -{ - char *res; - va_list args; - - va_start(args, fmt); - res = add_to_string_va(old, fmt, args); - va_end(args); - return res; -} diff --git a/core/membuffer.h b/core/membuffer.h index 042675c4a..3c56d057e 100644 --- a/core/membuffer.h +++ b/core/membuffer.h @@ -4,10 +4,6 @@ * 'membuffer' functions will manage memory allocation avoiding performance * issues related to superfluous re-allocation. See 'make_room' function * - * Before using it membuffer struct should be properly initialized - * - * struct membuffer mb = { 0 }; - * * Internal membuffer buffer will not by default contain null terminator, * adding it should be done using 'mb_cstring' function * @@ -23,15 +19,7 @@ * * "something, something else" * - * Unless ownership to the buffer is given away by using "detach_cstring()": - * - * ptr = detach_cstring(); - * * where the caller now has a C string and is supposed to free it. - * - * Otherwise allocated memory should be freed - * - * free_buffer(&mb); */ #ifndef MEMBUFFER_H #define MEMBUFFER_H @@ -42,29 +30,18 @@ #include "units.h" struct membuffer { - unsigned int len, alloc; - char *buffer; + unsigned int len = 0, alloc = 0; + char *buffer = nullptr; + membuffer(); + ~membuffer(); }; -#ifdef __cplusplus - -// In C++ code use this - it automatically frees the buffer, when going out of scope. -struct membufferpp : public membuffer { - membufferpp(); - ~membufferpp(); -}; - -extern "C" { -#endif - #ifdef __GNUC__ #define __printf(x, y) __attribute__((__format__(__printf__, x, y))) #else #define __printf(x, y) #endif -extern char *detach_cstring(struct membuffer *b); -extern void free_buffer(struct membuffer *); extern void make_room(struct membuffer *b, unsigned int size); extern void flush_buffer(struct membuffer *, FILE *); extern void put_bytes(struct membuffer *, const char *, int); @@ -76,13 +53,6 @@ extern void strip_mb(struct membuffer *); extern const char *mb_cstring(struct membuffer *); extern __printf(2, 0) void put_vformat(struct membuffer *, const char *, va_list); extern __printf(2, 3) void put_format(struct membuffer *, const char *fmt, ...); -extern __printf(2, 0) char *add_to_string_va(char *old, const char *fmt, va_list args); -extern __printf(2, 3) char *add_to_string(char *old, const char *fmt, ...); - -/* Helpers that use membuffers internally */ -extern __printf(1, 0) char *vformat_string(const char *, va_list); -extern __printf(1, 2) char *format_string(const char *, ...); - /* Output one of our "milli" values with type and pre/post data */ extern void put_milli(struct membuffer *, const char *, int, const char *); @@ -116,8 +86,4 @@ extern void put_salinity(struct membuffer *, int, const char *, const char *); extern void put_degrees(struct membuffer *b, degrees_t value, const char *, const char *); extern void put_location(struct membuffer *b, const location_t *, const char *, const char *); -#ifdef __cplusplus -} -#endif - #endif diff --git a/core/metadata.cpp b/core/metadata.cpp index 2596276c6..085ee7bcd 100644 --- a/core/metadata.cpp +++ b/core/metadata.cpp @@ -7,6 +7,9 @@ #include #include #include +#ifdef LIBRAW_SUPPORT +#include +#endif // Weirdly, android builds fail owing to undefined UINT64_MAX #ifndef UINT64_MAX @@ -135,7 +138,7 @@ static bool parseMP4(QFile &f, metadata *metadata) std::vector atom_stack; atom_stack.reserve(10); - // For the outmost level, set the atom-size the the maximum value representable in + // For the outmost level, set the atom-size to the maximum value representable in // 64-bits, which effectively means parse to the end of file. atom_stack.push_back(UINT64_MAX); @@ -528,13 +531,50 @@ static bool parseASF(QFile &f, metadata *metadata) return false; } -extern "C" mediatype_t get_metadata(const char *filename_in, metadata *data) +// Transform a (deg, min, sec) float triple into microdegrees +degrees_t degminsec_to_udeg(float a[3]) +{ + if (a[0] == 0.0 && a[1] == 0.0 && a[2] == 0.0) + return { .udeg = 0 }; + return { .udeg = static_cast(round(a[0] * 1'000'000.0 + + a[1] * (1'000'000.0/60.0) + + a[2] * (1'000'000.0/3600.0))) }; +} + +#ifdef LIBRAW_SUPPORT +static bool parseRaw(const char *fn, metadata *metadata) +{ + LibRaw raw; // Might think about reusing that + + // TODO: Convert filename to UTF-16 for windows + if (raw.open_file(fn) != LIBRAW_SUCCESS) + return false; + + metadata->timestamp = raw.imgdata.other.timestamp; + metadata->location.lat = degminsec_to_udeg(raw.imgdata.other.parsed_gps.latitude); +#if LIBRAW_MINOR_VERSION < 20 + // what a funny typo in the structure... + metadata->location.lon = degminsec_to_udeg(raw.imgdata.other.parsed_gps.longtitude); +#else + metadata->location.lon = degminsec_to_udeg(raw.imgdata.other.parsed_gps.longitude); +#endif + + return true; +} +#endif + +mediatype_t get_metadata(const char *filename_in, metadata *data) { data->timestamp = 0; - data->duration.seconds = 0; + data->duration = 0_sec; data->location.lat.udeg = 0; data->location.lon.udeg = 0; +#ifdef LIBRAW_SUPPORT + if (parseRaw(filename_in, data)) + return MEDIATYPE_PICTURE; +#endif + QString filename = localFilePath(QString(filename_in)); QFile f(filename); if (!f.open(QIODevice::ReadOnly)) @@ -561,7 +601,7 @@ extern "C" mediatype_t get_metadata(const char *filename_in, metadata *data) return res; } -extern "C" timestamp_t picture_get_timestamp(const char *filename) +timestamp_t picture_get_timestamp(const char *filename) { struct metadata data; get_metadata(filename, &data); diff --git a/core/metadata.h b/core/metadata.h index 253763ad8..a3d854c5f 100644 --- a/core/metadata.h +++ b/core/metadata.h @@ -17,15 +17,7 @@ enum mediatype_t { MEDIATYPE_STILL_LOADING, // Still processing in the background }; -#ifdef __cplusplus -extern "C" { -#endif - enum mediatype_t get_metadata(const char *filename, struct metadata *data); timestamp_t picture_get_timestamp(const char *filename); -#ifdef __cplusplus -} -#endif - #endif // METADATA_H diff --git a/core/namecmp.h b/core/namecmp.h index f0781bb50..773fe57b9 100644 --- a/core/namecmp.h +++ b/core/namecmp.h @@ -2,8 +2,6 @@ #ifndef NAMECMP_H #define NAMECMP_H -#ifdef __cplusplus - #include // this is annoying Qt5 / Qt6 incompatibility where we can't compare against string literals anymore @@ -12,5 +10,4 @@ static inline int nameCmp(QXmlStreamReader &r, const char * cs) return r.name().compare(QLatin1String(cs)); } -#endif #endif // NAMECMP_H diff --git a/core/ostctools.c b/core/ostctools.cpp similarity index 66% rename from core/ostctools.c rename to core/ostctools.cpp index 72db14977..6af6bb121 100644 --- a/core/ostctools.c +++ b/core/ostctools.cpp @@ -4,7 +4,6 @@ #include #include "errorhelper.h" -#include "ssrf.h" #include "subsurface-string.h" #include "gettext.h" #include "dive.h" @@ -12,23 +11,24 @@ #include "divelog.h" #include "extradata.h" #include "file.h" +#include "format.h" #include "libdivecomputer.h" /* * Fills a device_data_t structure with known dc data and a descriptor. */ -static int ostc_prepare_data(int data_model, dc_family_t dc_fam, device_data_t *dev_data) +static int ostc_prepare_data(int data_model, dc_family_t dc_fam, device_data_t &dev_data) { dc_descriptor_t *data_descriptor; - dev_data->device = NULL; - dev_data->context = NULL; + dev_data.device = NULL; + dev_data.context = NULL; data_descriptor = get_descriptor(dc_fam, data_model); if (data_descriptor) { - dev_data->descriptor = data_descriptor; - dev_data->vendor = copy_string(dc_descriptor_get_vendor(data_descriptor)); - dev_data->model = copy_string(dc_descriptor_get_product(data_descriptor)); + dev_data.descriptor = data_descriptor; + dev_data.vendor = dc_descriptor_get_vendor(data_descriptor); + dev_data.model = dc_descriptor_get_product(data_descriptor); } else { return 0; } @@ -43,56 +43,53 @@ static int ostc_prepare_data(int data_model, dc_family_t dc_fam, device_data_t * void ostctools_import(const char *file, struct divelog *log) { FILE *archive; - device_data_t *devdata = calloc(1, sizeof(device_data_t)); + device_data_t devdata; dc_family_t dc_fam; - unsigned char *buffer = calloc(65536, 1); + std::vector buffer(65536, 0); unsigned char uc_tmp[2]; - char *tmp; - struct dive *ostcdive = alloc_dive(); - dc_status_t rc = 0; + auto ostcdive = std::make_unique(); + dc_status_t rc = DC_STATUS_SUCCESS; int model, ret, i = 0, c; unsigned int serial; - struct extra_data *ptr; const char *failed_to_read_msg = translate("gettextFromC", "Failed to read '%s'"); // Open the archive if ((archive = subsurface_fopen(file, "rb")) == NULL) { report_error(failed_to_read_msg, file); - free_dive(ostcdive); - goto out; + return; } // Read dive number from the log if (fseek(archive, 258, 0) == -1) { report_error(failed_to_read_msg, file); - free_dive(ostcdive); - goto close_out; + fclose(archive); + return; } if (fread(uc_tmp, 1, 2, archive) != 2) { report_error(failed_to_read_msg, file); - free_dive(ostcdive); - goto close_out; + fclose(archive); + return; } ostcdive->number = uc_tmp[0] + (uc_tmp[1] << 8); // Read device's serial number if (fseek(archive, 265, 0) == -1) { report_error(failed_to_read_msg, file); - free_dive(ostcdive); - goto close_out; + fclose(archive); + return; } if (fread(uc_tmp, 1, 2, archive) != 2) { report_error(failed_to_read_msg, file); - free_dive(ostcdive); - goto close_out; + fclose(archive); + return; } serial = uc_tmp[0] + (uc_tmp[1] << 8); // Read dive's raw data, header + profile if (fseek(archive, 456, 0) == -1) { report_error(failed_to_read_msg, file); - free_dive(ostcdive); - goto close_out; + fclose(archive); + return; } while ((c = getc(archive)) != EOF) { buffer[i] = c; @@ -102,9 +99,10 @@ void ostctools_import(const char *file, struct divelog *log) } if (ferror(archive)) { report_error(failed_to_read_msg, file); - free_dive(ostcdive); - goto close_out; + fclose(archive); + return; } + fclose(archive); // Try to determine the dc family based on the header type if (buffer[2] == 0x20 || buffer[2] == 0x21) { @@ -120,8 +118,7 @@ void ostctools_import(const char *file, struct divelog *log) break; default: report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); - free_dive(ostcdive); - goto close_out; + return; } } @@ -151,44 +148,25 @@ void ostctools_import(const char *file, struct divelog *log) ret = ostc_prepare_data(model, dc_fam, devdata); if (ret == 0) { report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); - free_dive(ostcdive); - goto close_out; + return; } - tmp = calloc(strlen(devdata->vendor) + strlen(devdata->model) + 28, 1); - sprintf(tmp, "%s %s (Imported from OSTCTools)", devdata->vendor, devdata->model); - ostcdive->dc.model = copy_string(tmp); - free(tmp); + ostcdive->dcs[0].model = devdata.vendor + " " + devdata.model + " (Imported from OSTCTools)"; // Parse the dive data - rc = libdc_buffer_parser(ostcdive, devdata, buffer, i + 1); + rc = libdc_buffer_parser(ostcdive.get(), &devdata, buffer.data(), i + 1); if (rc != DC_STATUS_SUCCESS) report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), ostcdive->number); // Serial number is not part of the header nor the profile, so libdc won't - // catch it. If Serial is part of the extra_data, and set to zero, remove - // it from the list and add again. - tmp = calloc(12, 1); - sprintf(tmp, "%d", serial); - ostcdive->dc.serial = copy_string(tmp); - free(tmp); + // catch it. If Serial is part of the extra_data, and set to zero, replace it. + ostcdive->dcs[0].serial = std::to_string(serial); - if (ostcdive->dc.extra_data) { - ptr = ostcdive->dc.extra_data; - while (strcmp(ptr->key, "Serial")) - ptr = ptr->next; - if (!strcmp(ptr->value, "0")) { - add_extra_data(&ostcdive->dc, "Serial", ostcdive->dc.serial); - *ptr = *(ptr)->next; - } - } else { - add_extra_data(&ostcdive->dc, "Serial", ostcdive->dc.serial); - } - record_dive_to_table(ostcdive, log->dives); - sort_dive_table(log->dives); + auto it = find_if(ostcdive->dcs[0].extra_data.begin(), ostcdive->dcs[0].extra_data.end(), + [](auto &ed) { return ed.key == "Serial"; }); + if (it != ostcdive->dcs[0].extra_data.end() && it->value == "0") + it->value = ostcdive->dcs[0].serial.c_str(); + else if (it == ostcdive->dcs[0].extra_data.end()) + add_extra_data(&ostcdive->dcs[0], "Serial", ostcdive->dcs[0].serial); -close_out: - fclose(archive); -out: - free(devdata); - free(buffer); + log->dives.record_dive(std::move(ostcdive)); } diff --git a/core/owning_ptrs.h b/core/owning_ptrs.h deleted file mode 100644 index 6f591d144..000000000 --- a/core/owning_ptrs.h +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Convenience classes defining owning pointers to C-objects that -// automatically clean up the objects if the pointers go out of -// scope. Based on unique_ptr<>. -// In the future, we should replace these by real destructors. -#ifndef OWNING_PTR_H -#define OWNING_PTR_H - -#include -#include - -struct dive; -struct dive_trip; -struct dive_site; -struct event; - -extern "C" void free_dive(struct dive *); -extern "C" void free_trip(struct dive_trip *); -extern "C" void free_dive_site(struct dive_site *); - -// Classes used to automatically call the appropriate free_*() function for owning pointers that go out of scope. -struct DiveDeleter { - void operator()(dive *d) { free_dive(d); } -}; -struct TripDeleter { - void operator()(dive_trip *t) { free_trip(t); } -}; -struct DiveSiteDeleter { - void operator()(dive_site *ds) { free_dive_site(ds); } -}; -struct EventDeleter { - void operator()(event *ev) { free(ev); } -}; - -// Owning pointers to dive, dive_trip, dive_site and event objects. -using OwningDivePtr = std::unique_ptr; -using OwningTripPtr = std::unique_ptr; -using OwningDiveSitePtr = std::unique_ptr; -using OwningEventPtr = std::unique_ptr; - -#endif diff --git a/core/owning_table.h b/core/owning_table.h new file mode 100644 index 000000000..d3636c858 --- /dev/null +++ b/core/owning_table.h @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Traditionally, most of our data structures are collected as tables + * (=arrays) of pointers. The advantage is that pointers (or references) + * to objects are stable, even if the array grows and reallocates. + * Implicitly, the table is the owner of the objects and the objets + * are deleted (freed) when removed from the table. + * This header defines an std::vector<> of unique_ptr<>s to make this + * explicit. + * + * The state I want to reach across the code base: whenever a part of the + * code owns a heap allocated object, it *always* possesses a unique_ptr<> + * to that object. All naked pointers are invariably considered as + * "non owning". + * + * There are two ways to end ownership: + * 1) The std::unique_ptr<> goes out of scope and the object is + * automatically deleted. + * 2) Ownership is passed to another std::unique_ptr<> using std::move(). + * + * This means that when adding an object to an owning_table, + * ownership of a std::unique_ptr<> is given up with std::move(). + * The table then returns a non-owning pointer to the object and + * optionally the insertion index. + * + * In converse, when removing an object, one provides a non-owning + * pointer, which is turned into an owning std::unique_ptr<> and + * the index where the object was removed. + * When ignoring the returned owning pointer, the object is + * automatically freed. + * + * The functions to add an entry to the table are called "put()", + * potentially with a suffix. The functions to remove an entry + * are called "pull()", likewise with an optional suffix. + * + * There are two versions of the table: + * 1) An unordered version, where the caller is responsible for + * adding at specified positions (either given by an index or at the end). + * Removal via a non-owning pointer is implemented by a linear search + * over the whole table. + * 2) An ordered version, where a comparison function that returns -1, 0, 1 + * is used to add objects. In that case, the caller must make sure that + * no two ojects that compare equal are added to the table. + * Obviously, the compare function is supposed to be replaced by the + * "spaceship operator" in due course. + * Here, adding and removing via non-owning pointers is implemented + * using a binary search. + * + * Note that, since the table contains std::unique_ptr<>s, to loop over + * all entries, it is best to use something such as + * for (const auto &ptr: table) ... + * I plan to add iterator adapters, that autometically dereference + * the unique_ptr<>s and provide const-references for const-tables. + * + * Time will tell how useful this class is. + */ +#ifndef CORE_OWNING_TABLE_H +#define CORE_OWNING_TABLE_H + +#include "errorhelper.h" + +#include +#include +#include +#include + +template +class owning_table : public std::vector> { +public: + struct put_result { + T *ptr; + size_t idx; + }; + struct pull_result { + std::unique_ptr ptr; + size_t idx; + }; + size_t get_idx(const T *item) const { + auto it = std::find_if(this->begin(), this->end(), + [item] (auto &i1) {return i1.get() == item; }); + return it != this->end() ? it - this->begin() : std::string::npos; + } + T *put_at(std::unique_ptr item, size_t idx) { + T *res = item.get(); + insert(this->begin() + idx, std::move(item)); + return res; + } + // Returns index of added item + put_result put_back(std::unique_ptr item) { + T *ptr = item.get(); + push_back(std::move(item)); + return { ptr, this->size() - 1 }; + } + std::unique_ptr pull_at(size_t idx) { + auto it = this->begin() + idx; + std::unique_ptr res = std::move(*it); + this->erase(it); + return res; + } + pull_result pull(const T *item) { + size_t idx = get_idx(item); + if (idx == std::string::npos) { + report_info("Warning: removing unexisting item in %s", __func__); + return { std::unique_ptr(), std::string::npos }; + } + return { pull_at(idx), idx }; + } +}; + +// Note: there must not be any elements that compare equal! +template +class sorted_owning_table : public owning_table { +public: + using typename owning_table::put_result; + using typename owning_table::pull_result; + // Returns index of added item + put_result put(std::unique_ptr item) { + auto it = std::lower_bound(this->begin(), this->end(), item, + [] (const auto &i1, const auto &i2) + { return CMP(*i1, *i2) < 0; }); + if (it != this->end() && CMP(**it, *item) == 0) + report_info("Warning: adding duplicate item in %s", __func__); + size_t idx = it - this->begin(); + T *ptr = item.get(); + this->insert(it, std::move(item)); + return { ptr, idx }; + } + + // Optimized version of get_idx(), which uses binary search + // If not found, fall back to linear search and emit a warning. + // Note: this is probaly slower than a linesr search. But for now, + // it helps finding consistency problems. + size_t get_idx(const T *item) const { + auto it = std::lower_bound(this->begin(), this->end(), item, + [] (const auto &i1, const auto &i2) + { return CMP(*i1, *i2) < 0; }); + if (it == this->end() || CMP(**it, *item) != 0) { + size_t idx = owning_table::get_idx(item); + if (idx != std::string::npos) + report_info("Warning: index found by linear but not by binary search in %s", __func__); + return idx; + } + return it - this->begin(); + } + + // Get place where insertion would take place + size_t get_insertion_index(const T *item) const { + auto it = std::lower_bound(this->begin(), this->end(), item, + [] (const auto &i1, const auto &i2) + { return CMP(*i1, *i2) < 0; }); + return it - this->begin(); + } + + // Note: this is silly - finding the pointer by a linear search + // is probably significantly faster than doing a binary search. + // But it helps finding consistency problems for now. Remove in + // due course. + pull_result pull(const T *item) { + size_t idx = get_idx(item); + if (idx == std::string::npos) { + report_info("Warning: removing unexisting item in %s", __func__); + return { std::unique_ptr(), std::string::npos }; + } + return { this->pull_at(idx), idx }; + } + + void sort() { + std::sort(this->begin(), this->end(), [](const auto &a, const auto &b) { return CMP(*a, *b) < 0; }); + } +}; + +#endif diff --git a/core/parse-xml.cpp b/core/parse-xml.cpp index d3428758d..cd20599cf 100644 --- a/core/parse-xml.cpp +++ b/core/parse-xml.cpp @@ -4,13 +4,10 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "ssrf.h" - #include #include #include #include -#include #include #include #include @@ -35,8 +32,10 @@ #include "membuffer.h" #include "picture.h" #include "qthelper.h" +#include "range.h" #include "sample.h" #include "tag.h" +#include "version.h" #include "xmlparams.h" int last_xml_version = -1; @@ -103,7 +102,7 @@ enum ParseState { FINDSTART, FINDEND }; -static void divetags(const char *buffer, struct tag_entry **tags) +static void divetags(const char *buffer, tag_list *tags) { int i = 0, start = 0, end = 0; enum ParseState state = FINDEND; @@ -118,7 +117,7 @@ static void divetags(const char *buffer, struct tag_entry **tags) if (i > 0 && buffer[i - 1] != '\\') { std::string s(buffer + start, i - start); state = FINDSTART; - taglist_add_tag(tags, s.c_str()); + taglist_add_tag(*tags, s.c_str()); } else { state = FINDSTART; } @@ -141,7 +140,7 @@ static void divetags(const char *buffer, struct tag_entry **tags) end = len - 1; if (len > 0) { std::string s(buffer + start, i - start); - taglist_add_tag(tags, buffer + start); + taglist_add_tag(*tags, buffer + start); } } } @@ -151,16 +150,15 @@ enum number_type { FLOATVAL }; -static enum number_type parse_float(const char *buffer, double *res, const char **endp) +static enum number_type parse_float(const char *buffer, double &res, const char *&endp) { double val; static bool first_time = true; - errno = 0; - val = ascii_strtod(buffer, endp); - if (errno || *endp == buffer) + val = ascii_strtod(buffer, &endp); + if (endp == buffer) return NEITHER; - if (**endp == ',') { + if (*endp == ',') { if (nearly_equal(val, rint(val))) { /* we really want to send an error if this is a Subsurface native file * as this is likely indication of a bug - but right now we don't have @@ -170,46 +168,42 @@ static enum number_type parse_float(const char *buffer, double *res, const char first_time = false; } /* Try again in permissive mode*/ - val = strtod_flags(buffer, endp, 0); + val = permissive_strtod(buffer, &endp); } } - *res = val; + res = val; return FLOATVAL; } -union int_or_float { - double fp; -}; - -static enum number_type integer_or_float(const char *buffer, union int_or_float *res) +static enum number_type parse_float(const char *buffer, double &res) { const char *end; - return parse_float(buffer, &res->fp, &end); + return parse_float(buffer, res, end); } static void pressure(const char *buffer, pressure_t *pressure, struct parser_state *state) { double mbar = 0.0; - union int_or_float val; + double val; - switch (integer_or_float(buffer, &val)) { + switch (parse_float(buffer, val)) { case FLOATVAL: /* Just ignore zero values */ - if (!val.fp) + if (!val) break; switch (state->xml_parsing_units.pressure) { case units::PASCALS: - mbar = val.fp / 100; + mbar = val / 100; break; case units::BAR: /* Assume mbar, but if it's really small, it's bar */ - mbar = val.fp; + mbar = val; if (fabs(mbar) < 5000) mbar = mbar * 1000; break; case units::PSI: - mbar = psi_to_mbar(val.fp); + mbar = psi_to_mbar(val); break; } if (fabs(mbar) > 5 && fabs(mbar) < 5000000) { @@ -241,16 +235,16 @@ static void cylinder_use(const char *buffer, enum cylinderuse *cyl_use, struct p enum cylinderuse use = cylinderuse_from_text(trimmed.c_str()); *cyl_use = use; if (use == OXYGEN) - state->o2pressure_sensor = state->cur_dive->cylinders.nr - 1; + state->o2pressure_sensor = static_cast(state->cur_dive->cylinders.size()) - 1; } } static void salinity(const char *buffer, int *salinity) { - union int_or_float val; - switch (integer_or_float(buffer, &val)) { + double val; + switch (parse_float(buffer, val)) { case FLOATVAL: - *salinity = lrint(val.fp * 10.0); + *salinity = lrint(val * 10.0); break; default: report_info("Strange salinity reading %s", buffer); @@ -259,16 +253,16 @@ static void salinity(const char *buffer, int *salinity) static void depth(const char *buffer, depth_t *depth, struct parser_state *state) { - union int_or_float val; + double val; - switch (integer_or_float(buffer, &val)) { + switch (parse_float(buffer, val)) { case FLOATVAL: switch (state->xml_parsing_units.length) { case units::METERS: - depth->mm = lrint(val.fp * 1000); + depth->mm = lrint(val * 1000.0); break; case units::FEET: - depth->mm = feet_to_mm(val.fp); + depth->mm = feet_to_mm(val); break; } break; @@ -292,16 +286,16 @@ static void extra_data_end(struct parser_state *state) static void weight(const char *buffer, weight_t *weight, struct parser_state *state) { - union int_or_float val; + double val; - switch (integer_or_float(buffer, &val)) { + switch (parse_float(buffer, val)) { case FLOATVAL: switch (state->xml_parsing_units.weight) { case units::KG: - weight->grams = lrint(val.fp * 1000); + weight->grams = lrint(val * 1000.0); break; case units::LBS: - weight->grams = lbs_to_grams(val.fp); + weight->grams = lbs_to_grams(val); break; } break; @@ -312,19 +306,19 @@ static void weight(const char *buffer, weight_t *weight, struct parser_state *st static void temperature(const char *buffer, temperature_t *temperature, struct parser_state *state) { - union int_or_float val; + double val; - switch (integer_or_float(buffer, &val)) { + switch (parse_float(buffer, val)) { case FLOATVAL: switch (state->xml_parsing_units.temperature) { case units::KELVIN: - temperature->mkelvin = lrint(val.fp * 1000); + temperature->mkelvin = lrint(val * 1000.0); break; case units::CELSIUS: - temperature->mkelvin = C_to_mkelvin(val.fp); + temperature->mkelvin = C_to_mkelvin(val); break; case units::FAHRENHEIT: - temperature->mkelvin = F_to_mkelvin(val.fp); + temperature->mkelvin = F_to_mkelvin(val); break; } break; @@ -334,7 +328,7 @@ static void temperature(const char *buffer, temperature_t *temperature, struct p /* temperatures outside -40C .. +70C should be ignored */ if (temperature->mkelvin < ZERO_C_IN_MKELVIN - 40000 || temperature->mkelvin > ZERO_C_IN_MKELVIN + 70000) - temperature->mkelvin = 0; + *temperature = 0_K; } static void sampletime(const char *buffer, duration_t *time) @@ -357,7 +351,7 @@ static void sampletime(const char *buffer, duration_t *time) time->seconds = (hr * 60 + min) * 60 + sec; break; default: - time->seconds = 0; + *time = 0_sec; report_info("Strange sample time reading %s", buffer); } } @@ -396,7 +390,7 @@ static void percent(const char *buffer, fraction_t *fraction) double val; const char *end; - switch (parse_float(buffer, &val, &end)) { + switch (parse_float(buffer, val, end)) { case FLOATVAL: /* Turn fractions into percent unless explicit.. */ if (val <= 1.0) { @@ -432,11 +426,11 @@ static void gasmix_nitrogen(const char *, struct gasmix *) static void cylindersize(const char *buffer, volume_t *volume) { - union int_or_float val; + double val; - switch (integer_or_float(buffer, &val)) { + switch (parse_float(buffer, val)) { case FLOATVAL: - volume->mliter = lrint(val.fp * 1000); + volume->mliter = lrint(val * 1000.0); break; default: @@ -445,14 +439,6 @@ static void cylindersize(const char *buffer, volume_t *volume) } } -static void event_name(const char *buffer, char *name) -{ - std::string trimmed = trimspace(buffer); - size_t size = std::min(trimmed.size(), (size_t)MAX_EVENT_NAME); - memcpy(name, trimmed.data(), size); - name[size] = 0; -} - // We don't use gauge as a mode, and pscr doesn't exist as a libdc divemode static const char *libdc_divemode_text[] = { "oc", "cc", "pscr", "freedive", "gauge"}; @@ -575,7 +561,7 @@ static void dive_site(const char *buffer, struct dive *d, struct parser_state *s { uint32_t uuid; hex_value(buffer, &uuid); - add_dive_to_dive_site(d, get_dive_site_by_uuid(uuid, state->log->sites)); + state->log->sites.get_by_uuid(uuid)->add_dive(d); } static void get_notrip(const char *buffer, bool *notrip) @@ -608,16 +594,16 @@ static void get_notrip(const char *buffer, bool *notrip) */ static void fahrenheit(const char *buffer, temperature_t *temperature) { - union int_or_float val; + double val; - switch (integer_or_float(buffer, &val)) { + switch (parse_float(buffer, val)) { case FLOATVAL: - if (nearly_equal(val.fp, 32.0)) + if (nearly_equal(val, 32.0)) break; - if (val.fp < 32.0) - temperature->mkelvin = C_to_mkelvin(val.fp); + if (val < 32.0) + temperature->mkelvin = C_to_mkelvin(val); else - temperature->mkelvin = F_to_mkelvin(val.fp); + temperature->mkelvin = F_to_mkelvin(val); break; default: report_info("Crazy Diving Log temperature reading %s", buffer); @@ -646,14 +632,14 @@ static void fahrenheit(const char *buffer, temperature_t *temperature) */ static void psi_or_bar(const char *buffer, pressure_t *pressure) { - union int_or_float val; + double val; - switch (integer_or_float(buffer, &val)) { + switch (parse_float(buffer, val)) { case FLOATVAL: - if (val.fp > 400) - pressure->mbar = psi_to_mbar(val.fp); + if (val > 400) + pressure->mbar = psi_to_mbar(val); else - pressure->mbar = lrint(val.fp * 1000); + pressure->mbar = lrint(val * 1000); break; default: report_info("Crazy Diving Log PSI reading %s", buffer); @@ -673,10 +659,9 @@ static void uddf_gasswitch(const char *buffer, struct sample *sample, struct par { int idx = atoi(buffer); int seconds = sample->time.seconds; - struct dive *dive = state->cur_dive; struct divecomputer *dc = get_dc(state); - add_gas_switch_event(dive, dc, seconds, idx); + add_gas_switch_event(state->cur_dive.get(), dc, seconds, idx); } static int uddf_fill_sample(struct sample *sample, const char *name, char *buf, struct parser_state *state) @@ -693,7 +678,7 @@ static void eventtime(const char *buffer, duration_t *duration, struct parser_st { sampletime(buffer, duration); if (state->cur_sample) - duration->seconds += state->cur_sample->time.seconds; + *duration += state->cur_sample->time; } static void try_to_match_autogroup(const char *name, char *buf, struct parser_state *state) @@ -712,7 +697,7 @@ static void get_cylinderindex(const char *buffer, int16_t *i, struct parser_stat { *i = atoi(buffer); if (state->lastcylinderindex != *i) { - add_gas_switch_event(state->cur_dive, get_dc(state), state->cur_sample->time.seconds, *i); + add_gas_switch_event(state->cur_dive.get(), get_dc(state), state->cur_sample->time.seconds, *i); state->lastcylinderindex = *i; } } @@ -730,7 +715,7 @@ static void parse_libdc_deco(const char *buffer, struct sample *s) s->in_deco = false; // The time wasn't stoptime, it was ndl s->ndl = s->stoptime; - s->stoptime.seconds = 0; + s->stoptime = 0_sec; } } @@ -770,9 +755,9 @@ static void try_to_fill_fingerprint(const char *name, char *buf, struct parser_s static void try_to_fill_event(const char *name, char *buf, struct parser_state *state) { start_match("event", name, buf); - if (MATCH("event", event_name, state->cur_event.name)) + if (MATCH("event", utf8_string_std, &state->cur_event.name)) return; - if (MATCH("name", event_name, state->cur_event.name)) + if (MATCH("name", utf8_string_std, &state->cur_event.name)) return; if (MATCH_STATE("time", eventtime, &state->cur_event.time)) return; @@ -852,7 +837,7 @@ static void try_to_fill_dc(struct divecomputer *dc, const char *name, char *buf, return; if (MATCH_STATE("time", divetime, &dc->when)) return; - if (MATCH("model", utf8_string, (char **)&dc->model)) + if (MATCH("model", utf8_string_std, &dc->model)) return; if (MATCH("deviceid", hex_value, &deviceid)) return; @@ -984,20 +969,19 @@ static void try_to_fill_sample(struct sample *sample, const char *name, char *bu static void divinglog_place(const char *place, struct dive *d, struct parser_state *state) { - char buffer[1024]; struct dive_site *ds; - snprintf(buffer, sizeof(buffer), + std::string buffer = format_string_std( "%s%s%s%s%s", place, !state->city.empty() ? ", " : "", !state->city.empty() ? state->city.c_str() : "", !state->country.empty() ? ", " : "", !state->country.empty() ? state->country.c_str() : ""); - ds = get_dive_site_by_name(buffer, state->log->sites); + ds = state->log->sites.get_by_name(buffer); if (!ds) - ds = create_dive_site(buffer, state->log->sites); - add_dive_to_dive_site(d, ds); + ds = state->log->sites.create(buffer); + ds->add_dive(d); // TODO: capture the country / city info in the taxonomy instead state->city.clear(); @@ -1007,36 +991,34 @@ static void divinglog_place(const char *place, struct dive *d, struct parser_sta static int divinglog_dive_match(struct dive *dive, const char *name, char *buf, struct parser_state *state) { /* For cylinder related fields, we might have to create a cylinder first. */ - cylinder_t cyl = empty_cylinder; - if (MATCH("tanktype", utf8_string, (char **)&cyl.type.description)) { - cylinder_t *cyl0 = get_or_create_cylinder(dive, 0); - free((void *)cyl0->type.description); - cyl0->type.description = cyl.type.description; + cylinder_t cyl; + if (MATCH("tanktype", utf8_string_std, &cyl.type.description)) { + dive->get_or_create_cylinder(0)->type.description = std::move(cyl.type.description); return 1; } if (MATCH("tanksize", cylindersize, &cyl.type.size)) { - get_or_create_cylinder(dive, 0)->type.size = cyl.type.size; + dive->get_or_create_cylinder(0)->type.size = cyl.type.size; return 1; } if (MATCH_STATE("presw", pressure, &cyl.type.workingpressure)) { - get_or_create_cylinder(dive, 0)->type.workingpressure = cyl.type.workingpressure; + dive->get_or_create_cylinder(0)->type.workingpressure = cyl.type.workingpressure; return 1; } if (MATCH_STATE("press", pressure, &cyl.start)) { - get_or_create_cylinder(dive, 0)->start = cyl.start; + dive->get_or_create_cylinder(0)->start = cyl.start; return 1; } if (MATCH_STATE("prese", pressure, &cyl.end)) { - get_or_create_cylinder(dive, 0)->end = cyl.end; + dive->get_or_create_cylinder(0)->end = cyl.end; return 1; } return MATCH_STATE("divedate", divedate, &dive->when) || MATCH_STATE("entrytime", divetime, &dive->when) || - MATCH("divetime", duration, &dive->dc.duration) || - MATCH_STATE("depth", depth, &dive->dc.maxdepth) || - MATCH_STATE("depthavg", depth, &dive->dc.meandepth) || - MATCH("comments", utf8_string, &dive->notes) || - MATCH("names.buddy", utf8_string, &dive->buddy) || + MATCH("divetime", duration, &dive->dcs[0].duration) || + MATCH_STATE("depth", depth, &dive->dcs[0].maxdepth) || + MATCH_STATE("depthavg", depth, &dive->dcs[0].meandepth) || + MATCH("comments", utf8_string_std, &dive->notes) || + MATCH("names.buddy", utf8_string_std, &dive->buddy) || MATCH("name.country", utf8_string_std, &state->country) || MATCH("name.city", utf8_string_std, &state->city) || MATCH_STATE("name.place", divinglog_place, dive) || @@ -1100,8 +1082,8 @@ uddf_datedata(min, 0) static int uddf_dive_match(struct dive *dive, const char *name, char *buf, struct parser_state *state) { return MATCH_STATE("datetime", uddf_datetime, &dive->when) || - MATCH("diveduration", duration, &dive->dc.duration) || - MATCH_STATE("greatestdepth", depth, &dive->dc.maxdepth) || + MATCH("diveduration", duration, &dive->dcs[0].duration) || + MATCH_STATE("greatestdepth", depth, &dive->dcs[0].maxdepth) || MATCH_STATE("year.date", uddf_year, &dive->when) || MATCH_STATE("month.date", uddf_mon, &dive->when) || MATCH_STATE("day.date", uddf_mday, &dive->when) || @@ -1162,14 +1144,15 @@ static void gps_lat(const char *buffer, struct dive *dive, struct parser_state * { const char *end; location_t location = { }; - struct dive_site *ds = get_dive_site_for_dive(dive); + struct dive_site *ds = dive->dive_site; location.lat = parse_degrees(buffer, &end); if (!ds) { - add_dive_to_dive_site(dive, create_dive_site_with_gps(NULL, &location, state->log->sites)); + state->log->sites.create(std::string(), location)->add_dive(dive); } else { if (ds->location.lat.udeg && ds->location.lat.udeg != location.lat.udeg) - report_info("Oops, changing the latitude of existing dive site id %8x name %s; not good", ds->uuid, ds->name ?: "(unknown)"); + report_info("Oops, changing the latitude of existing dive site id %8x name %s; not good", ds->uuid, + ds->name.empty() ? "(unknown)" : ds->name.c_str()); ds->location.lat = location.lat; } } @@ -1178,20 +1161,21 @@ static void gps_long(const char *buffer, struct dive *dive, struct parser_state { const char *end; location_t location = { }; - struct dive_site *ds = get_dive_site_for_dive(dive); + struct dive_site *ds = dive->dive_site; location.lon = parse_degrees(buffer, &end); if (!ds) { - add_dive_to_dive_site(dive, create_dive_site_with_gps(NULL, &location, state->log->sites)); + state->log->sites.create(std::string(), location)->add_dive(dive); } else { if (ds->location.lon.udeg && ds->location.lon.udeg != location.lon.udeg) - report_info("Oops, changing the longitude of existing dive site id %8x name %s; not good", ds->uuid, ds->name ?: "(unknown)"); + report_info("Oops, changing the longitude of existing dive site id %8x name %s; not good", ds->uuid, + ds->name.empty() ? "(unknown)" : ds->name.c_str()); ds->location.lon = location.lon; } } /* We allow either spaces or a comma between the decimal degrees */ -extern "C" void parse_location(const char *buffer, location_t *loc) +void parse_location(const char *buffer, location_t *loc) { const char *end; loc->lat = parse_degrees(buffer, &end); @@ -1212,25 +1196,26 @@ static void gps_in_dive(const char *buffer, struct dive *dive, struct parser_sta parse_location(buffer, &location); if (!ds) { // check if we have a dive site within 20 meters of that gps fix - ds = get_dive_site_by_gps_proximity(&location, 20, state->log->sites); + ds = state->log->sites.get_by_gps_proximity(location, 20); if (ds) { // found a site nearby; in case it turns out this one had a different name let's // remember the original coordinates so we can create the correct dive site later state->cur_location = location; } else { - ds = create_dive_site_with_gps("", &location, state->log->sites); + ds = state->log->sites.create(std::string(), location); } - add_dive_to_dive_site(dive, ds); + ds->add_dive(dive); } else { - if (dive_site_has_gps_location(ds) && - has_location(&location) && !same_location(&ds->location, &location)) { + if (ds->has_gps_location() && + has_location(&location) && ds->location != location) { // Houston, we have a problem report_info("dive site uuid in dive, but gps location (%10.6f/%10.6f) different from dive location (%10.6f/%10.6f)", ds->location.lat.udeg / 1000000.0, ds->location.lon.udeg / 1000000.0, location.lat.udeg / 1000000.0, location.lon.udeg / 1000000.0); std::string coords = printGPSCoordsC(&location); - ds->notes = add_to_string(ds->notes, translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords.c_str()); + ds->notes += '\n'; + ds->notes += format_string_std(translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords.c_str()); } else { ds->location = location; } @@ -1245,9 +1230,9 @@ static void gps_picture_location(const char *buffer, struct picture *pic) /* We're in the top-level dive xml. Try to convert whatever value to a dive value */ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, struct parser_state *state) { - cylinder_t *cyl = dive->cylinders.nr > 0 ? get_cylinder(dive, dive->cylinders.nr - 1) : NULL; - weightsystem_t *ws = dive->weightsystems.nr > 0 ? - &dive->weightsystems.weightsystems[dive->weightsystems.nr - 1] : NULL; + cylinder_t *cyl = !dive->cylinders.empty() ? &dive->cylinders.back() : NULL; + weightsystem_t *ws = !dive->weightsystems.empty() > 0 ? + &dive->weightsystems.back() : NULL; pressure_t p; weight_t w; start_match("dive", name, buf); @@ -1270,7 +1255,7 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str return; if (MATCH("number", get_index, &dive->number)) return; - if (MATCH("tags", divetags, &dive->tag_list)) + if (MATCH("tags", divetags, &dive->tags)) return; if (MATCH("tripflag", get_notrip, &dive->notrip)) return; @@ -1284,10 +1269,10 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str * Legacy format note: per-dive depths and duration get saved * in the first dive computer entry */ - if (match_dc_data_fields(&dive->dc, name, buf, state)) + if (match_dc_data_fields(&dive->dcs[0], name, buf, state)) return; - if (MATCH("filename.picture", utf8_string, &state->cur_picture.filename)) + if (MATCH("filename.picture", utf8_string_std, &state->cur_picture.filename)) return; if (MATCH("offset.picture", offsettime, &state->cur_picture.offset)) return; @@ -1298,11 +1283,11 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str return; } if (MATCH_STATE("cylinderstartpressure", pressure, &p)) { - get_or_create_cylinder(dive, 0)->start = p; + dive->get_or_create_cylinder(0)->start = p; return; } if (MATCH_STATE("cylinderendpressure", pressure, &p)) { - get_or_create_cylinder(dive, 0)->end = p; + dive->get_or_create_cylinder(0)->end = p; return; } if (MATCH_STATE("gps", gps_in_dive, dive)) @@ -1325,18 +1310,18 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str return; if (MATCH_STATE("name.dive", add_dive_site, dive)) return; - if (MATCH("suit", utf8_string, &dive->suit)) + if (MATCH("suit", utf8_string_std, &dive->suit)) return; - if (MATCH("divesuit", utf8_string, &dive->suit)) + if (MATCH("divesuit", utf8_string_std, &dive->suit)) return; - if (MATCH("notes", utf8_string, &dive->notes)) + if (MATCH("notes", utf8_string_std, &dive->notes)) return; // For historic reasons, we accept dive guide as well as dive master - if (MATCH("diveguide", utf8_string, &dive->diveguide)) + if (MATCH("diveguide", utf8_string_std, &dive->diveguide)) return; - if (MATCH("divemaster", utf8_string, &dive->diveguide)) + if (MATCH("divemaster", utf8_string_std, &dive->diveguide)) return; - if (MATCH("buddy", utf8_string, &dive->buddy)) + if (MATCH("buddy", utf8_string_std, &dive->buddy)) return; if (MATCH("watersalinity", salinity, &dive->user_salinity)) return; @@ -1355,15 +1340,15 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str if (MATCH_STATE("airpressure.dive", pressure, &dive->surface_pressure)) return; if (ws) { - if (MATCH("description.weightsystem", utf8_string, (char **)&ws->description)) + if (MATCH("description.weightsystem", utf8_string_std, &ws->description)) return; if (MATCH_STATE("weight.weightsystem", weight, &ws->weight)) return; } if (MATCH_STATE("weight", weight, &w)) { - weightsystem_t ws = empty_weightsystem; + weightsystem_t ws; ws.weight = w; - add_cloned_weightsystem(&dive->weightsystems, ws); + dive->weightsystems.push_back(std::move(ws)); return; } if (cyl) { @@ -1371,7 +1356,7 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str return; if (MATCH_STATE("workpressure.cylinder", pressure, &cyl->type.workingpressure)) return; - if (MATCH("description.cylinder", utf8_string, (char **)&cyl->type.description)) + if (MATCH("description.cylinder", utf8_string_std, &cyl->type.description)) return; if (MATCH_STATE("start.cylinder", pressure, &cyl->start)) return; @@ -1401,13 +1386,13 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str } /* We're in the top-level trip xml. Try to convert whatever value to a trip value */ -static void try_to_fill_trip(dive_trip_t *dive_trip, const char *name, char *buf, struct parser_state *state) +static void try_to_fill_trip(dive_trip *dive_trip, const char *name, char *buf, struct parser_state *state) { start_match("trip", name, buf); - if (MATCH("location", utf8_string, &dive_trip->location)) + if (MATCH("location", utf8_string_std, &dive_trip->location)) return; - if (MATCH("notes", utf8_string, &dive_trip->notes)) + if (MATCH("notes", utf8_string_std, &dive_trip->notes)) return; nonmatch("trip", name, buf); @@ -1416,20 +1401,20 @@ static void try_to_fill_trip(dive_trip_t *dive_trip, const char *name, char *buf /* We're processing a divesite entry - try to fill the components */ static void try_to_fill_dive_site(struct parser_state *state, const char *name, char *buf) { - struct dive_site *ds = state->cur_dive_site; + auto &ds = state->cur_dive_site; std::string taxonomy_value; start_match("divesite", name, buf); if (MATCH("uuid", hex_value, &ds->uuid)) return; - if (MATCH("name", utf8_string, &ds->name)) + if (MATCH("name", utf8_string_std, &ds->name)) return; - if (MATCH("description", utf8_string, &ds->description)) + if (MATCH("description", utf8_string_std, &ds->description)) return; - if (MATCH("notes", utf8_string, &ds->notes)) + if (MATCH("notes", utf8_string_std, &ds->notes)) return; - if (MATCH("gps", gps_location, ds)) + if (MATCH("gps", gps_location, ds.get())) return; if (MATCH("cat.geo", get_index, &state->taxonomy_category)) return; @@ -1442,8 +1427,8 @@ static void try_to_fill_dive_site(struct parser_state *state, const char *name, if (state->taxonomy_category < 0 || state->taxonomy_origin < 0) { report_error("Warning: taxonomy value without origin or category"); } else { - taxonomy_set_category(&ds->taxonomy, (taxonomy_category)state->taxonomy_category, - taxonomy_value.c_str(), (taxonomy_origin)state->taxonomy_origin); + taxonomy_set_category(ds->taxonomy, (taxonomy_category)state->taxonomy_category, + taxonomy_value, (taxonomy_origin)state->taxonomy_origin); } state->taxonomy_category = state->taxonomy_origin = -1; return; @@ -1456,11 +1441,8 @@ static void try_to_fill_filter(struct filter_preset *filter, const char *name, c { start_match("filterpreset", name, buf); - std::string s; - if (MATCH("name", utf8_string_std, &s)) { - filter_preset_set_name(filter, s.c_str()); + if (MATCH("name", utf8_string_std, &filter->name)) return; - } nonmatch("filterpreset", name, buf); } @@ -1540,11 +1522,11 @@ static bool entry(const char *name, char *buf, struct parser_state *state) return true; } if (state->cur_dive) { - try_to_fill_dive(state->cur_dive, name, buf, state); + try_to_fill_dive(state->cur_dive.get(), name, buf, state); return true; } if (state->cur_trip) { - try_to_fill_trip(state->cur_trip, name, buf, state); + try_to_fill_trip(state->cur_trip.get(), name, buf, state); return true; } return true; @@ -1756,7 +1738,7 @@ static const char *preprocess_divelog_de(const char *buffer) return buffer; } -extern "C" int parse_xml_buffer(const char *url, const char *buffer, int, struct divelog *log, +int parse_xml_buffer(const char *url, const char *buffer, int, struct divelog *log, const struct xml_params *params) { xmlDoc *doc; @@ -1765,7 +1747,7 @@ extern "C" int parse_xml_buffer(const char *url, const char *buffer, int, struct struct parser_state state; state.log = log; - state.fingerprints = &fingerprint_table; // simply use the global table for now + state.fingerprints = &fingerprints; // simply use the global table for now doc = xmlReadMemory(res, strlen(res), url, NULL, XML_PARSE_HUGE); if (!doc) doc = xmlReadMemory(res, strlen(res), url, "latin1", XML_PARSE_HUGE); @@ -1806,13 +1788,13 @@ static timestamp_t parse_dlf_timestamp(unsigned char *buffer) return offset + 946684800; } -extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divelog *log) +int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divelog *log) { + using namespace std::string_literals; unsigned char *ptr = buffer; unsigned char event; bool found; unsigned int time = 0; - int i; char serial[6]; struct battery_status { uint16_t volt1; @@ -1835,10 +1817,10 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel dive_start(&state); divecomputer_start(&state); - state.cur_dc->model = strdup("DLF import"); + state.cur_dc->model = "DLF import"; // (ptr[7] << 8) + ptr[6] Is "Serial" snprintf(serial, sizeof(serial), "%d", (ptr[7] << 8) + ptr[6]); - state.cur_dc->serial = strdup(serial); + state.cur_dc->serial = serial; state.cur_dc->when = parse_dlf_timestamp(ptr + 8); state.cur_dive->when = state.cur_dc->when; @@ -1876,7 +1858,7 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel state.cur_dc->surface_pressure.mbar = ((ptr[25] << 8) + ptr[24]) / 10; // Declare initial mix as first cylinder - cyl = get_or_create_cylinder(state.cur_dive, 0); + cyl = state.cur_dive->get_or_create_cylinder(0); cyl->gasmix.o2.permille = ptr[26] * 10; cyl->gasmix.he.permille = ptr[27] * 10; @@ -1934,39 +1916,39 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel state.cur_event.time.seconds = time; switch (ptr[4]) { case 1: - strcpy(state.cur_event.name, "Setpoint Manual"); + state.cur_event.name = "Setpoint Manual"s; state.cur_event.value = ptr[6]; sample_start(&state); state.cur_sample->setpoint.mbar = ptr[6] * 10; sample_end(&state); break; case 2: - strcpy(state.cur_event.name, "Setpoint Auto"); + state.cur_event.name = "Setpoint Auto"s; state.cur_event.value = ptr[6]; sample_start(&state); state.cur_sample->setpoint.mbar = ptr[6] * 10; sample_end(&state); switch (ptr[7]) { case 0: - strcat(state.cur_event.name, " Manual"); + state.cur_event.name += " Manual"s; break; case 1: - strcat(state.cur_event.name, " Auto Start"); + state.cur_event.name += " Auto Start"s; break; case 2: - strcat(state.cur_event.name, " Auto Hypox"); + state.cur_event.name += " Auto Hypox"s; break; case 3: - strcat(state.cur_event.name, " Auto Timeout"); + state.cur_event.name += " Auto Timeout"s; break; case 4: - strcat(state.cur_event.name, " Auto Ascent"); + state.cur_event.name += " Auto Ascent"s; break; case 5: - strcat(state.cur_event.name, " Auto Stall"); + state.cur_event.name += " Auto Stall"s; break; case 6: - strcat(state.cur_event.name, " Auto SP Low"); + state.cur_event.name += " Auto SP Low"s; break; default: break; @@ -1974,22 +1956,21 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel break; case 3: // obsolete - strcpy(state.cur_event.name, "OC"); + state.cur_event.name = "OC"s; break; case 4: // obsolete - strcpy(state.cur_event.name, "CCR"); + state.cur_event.name = "CCR"s; break; case 5: - strcpy(state.cur_event.name, "gaschange"); + state.cur_event.name = "gaschange"s; state.cur_event.type = SAMPLE_EVENT_GASCHANGE2; state.cur_event.value = ptr[7] << 8 ^ ptr[6]; - found = false; - for (i = 0; i < state.cur_dive->cylinders.nr; ++i) { - const cylinder_t *cyl = get_cylinder(state.cur_dive, i); - if (cyl->gasmix.o2.permille == ptr[6] * 10 && cyl->gasmix.he.permille == ptr[7] * 10) { + for (const auto [i, cyl]: enumerated_range(state.cur_dive->cylinders)) { + if (cyl.gasmix.o2.permille == ptr[6] * 10 && cyl.gasmix.he.permille == ptr[7] * 10) { found = true; + state.cur_event.gas.index = i; break; } } @@ -1998,46 +1979,44 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel cyl->gasmix.o2.permille = ptr[6] * 10; cyl->gasmix.he.permille = ptr[7] * 10; cylinder_end(&state); - state.cur_event.gas.index = state.cur_dive->cylinders.nr - 1; - } else { - state.cur_event.gas.index = i; + state.cur_event.gas.index = static_cast(state.cur_dive->cylinders.size()) - 1; } break; case 6: - strcpy(state.cur_event.name, "Start"); + state.cur_event.name = "Start"s; break; case 7: - strcpy(state.cur_event.name, "Too Fast"); + state.cur_event.name = "Too Fast"s; break; case 8: - strcpy(state.cur_event.name, "Above Ceiling"); + state.cur_event.name = "Above Ceiling"s; break; case 9: - strcpy(state.cur_event.name, "Toxic"); + state.cur_event.name = "Toxic"s; break; case 10: - strcpy(state.cur_event.name, "Hypox"); + state.cur_event.name = "Hypox"s; break; case 11: - strcpy(state.cur_event.name, "Critical"); + state.cur_event.name = "Critical"s; break; case 12: - strcpy(state.cur_event.name, "Sensor Disabled"); + state.cur_event.name = "Sensor Disabled"s; break; case 13: - strcpy(state.cur_event.name, "Sensor Enabled"); + state.cur_event.name = "Sensor Enabled"s; break; case 14: - strcpy(state.cur_event.name, "O2 Backup"); + state.cur_event.name = "O2 Backup"s; break; case 15: - strcpy(state.cur_event.name, "Peer Down"); + state.cur_event.name = "Peer Down"s; break; case 16: - strcpy(state.cur_event.name, "HS Down"); + state.cur_event.name = "HS Down"s; break; case 17: - strcpy(state.cur_event.name, "Inconsistent"); + state.cur_event.name = "Inconsistent"s; break; case 18: // key pressed - It should never get in here @@ -2045,53 +2024,53 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel break; case 19: // obsolete - strcpy(state.cur_event.name, "SCR"); + state.cur_event.name = "SCR"s; break; case 20: - strcpy(state.cur_event.name, "Above Stop"); + state.cur_event.name = "Above Stop"s; break; case 21: - strcpy(state.cur_event.name, "Safety Miss"); + state.cur_event.name = "Safety Miss"s; break; case 22: - strcpy(state.cur_event.name, "Fatal"); + state.cur_event.name = "Fatal"s; break; case 23: - strcpy(state.cur_event.name, "gaschange"); + state.cur_event.name = "gaschange"s; state.cur_event.type = SAMPLE_EVENT_GASCHANGE2; state.cur_event.value = ptr[7] << 8 ^ ptr[6]; event_end(&state); break; case 24: - strcpy(state.cur_event.name, "gaschange"); + state.cur_event.name = "gaschange"s; state.cur_event.type = SAMPLE_EVENT_GASCHANGE2; state.cur_event.value = ptr[7] << 8 ^ ptr[6]; event_end(&state); // This is both a mode change and a gas change event // so we encode it as two separate events. event_start(&state); - strcpy(state.cur_event.name, "Change Mode"); + state.cur_event.name = "Change Mode"s; switch (ptr[8]) { case 1: - strcat(state.cur_event.name, ": OC"); + state.cur_event.name += ": OC"s; break; case 2: - strcat(state.cur_event.name, ": CCR"); + state.cur_event.name += ": CCR"s; break; case 3: - strcat(state.cur_event.name, ": mCCR"); + state.cur_event.name += ": mCCR"s; break; case 4: - strcat(state.cur_event.name, ": Free"); + state.cur_event.name += ": Free"s; break; case 5: - strcat(state.cur_event.name, ": Gauge"); + state.cur_event.name += ": Gauge"s; break; case 6: - strcat(state.cur_event.name, ": ASCR"); + state.cur_event.name += ": ASCR"s; break; case 7: - strcat(state.cur_event.name, ": PSCR"); + state.cur_event.name += ": PSCR"s; break; default: break; @@ -2101,22 +2080,22 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel case 25: // uint16_t solenoid_bitmap = (ptr[7] << 8) + (ptr[6] << 0); // uint32_t time = (ptr[11] << 24) + (ptr[10] << 16) + (ptr[9] << 8) + (ptr[8] << 0); - snprintf(state.cur_event.name, MAX_EVENT_NAME, "CCR O2 solenoid %s", ptr[12] ? "opened": "closed"); + state.cur_event.name = format_string_std("CCR O2 solenoid %s", ptr[12] ? "opened": "closed"); break; case 26: - strcpy(state.cur_event.name, "User mark"); + state.cur_event.name = "User mark"s; break; case 27: - snprintf(state.cur_event.name, MAX_EVENT_NAME, "%sGF Switch (%d/%d)", ptr[6] ? "Bailout, ": "", ptr[7], ptr[8]); + state.cur_event.name = format_string_std("%sGF Switch (%d/%d)", ptr[6] ? "Bailout, ": "", ptr[7], ptr[8]); break; case 28: - strcpy(state.cur_event.name, "Peer Up"); + state.cur_event.name = "Peer Up"s; break; case 29: - strcpy(state.cur_event.name, "HS Up"); + state.cur_event.name = "HS Up"s; break; case 30: - snprintf(state.cur_event.name, MAX_EVENT_NAME, "CNS %d%%", ptr[6]); + state.cur_event.name = format_string_std("CNS %d%%", ptr[6]); break; default: // No values above 30 had any description @@ -2128,20 +2107,21 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel /* device configuration */ switch (((ptr[3] & 0x7f) << 3) + ((ptr[2] & 0xe0) >> 5)) { // Buffer to print extra string into - char config_buf[256]; // Local variables to temporary decode into struct tm tm; const char *device; const char *deep_stops; case 0: // TEST_CCR_FULL_1 utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm); - snprintf(config_buf, sizeof(config_buf), "START=%04u-%02u-%02u %02u:%02u:%02u,TEST=%02X%02X%02X%02X,RESULT=%02X%02X%02X%02X", tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ptr[7], ptr[6], ptr[5], ptr[4], ptr[11], ptr[10], ptr[9], ptr[8]); - add_extra_data(state.cur_dc, "TEST_CCR_FULL_1", config_buf); + add_extra_data(state.cur_dc, "TEST_CCR_FULL_1", + format_string_std("START=%04u-%02u-%02u %02u:%02u:%02u,TEST=%02X%02X%02X%02X,RESULT=%02X%02X%02X%02X", + tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ptr[7], ptr[6], ptr[5], ptr[4], ptr[11], ptr[10], ptr[9], ptr[8])); break; case 1: // TEST_CCR_PARTIAL_1 utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm); - snprintf(config_buf, sizeof(config_buf), "START=%04u-%02u-%02u %02u:%02u:%02u,TEST=%02X%02X%02X%02X,RESULT=%02X%02X%02X%02X", tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ptr[7], ptr[6], ptr[5], ptr[4], ptr[11], ptr[10], ptr[9], ptr[8]); - add_extra_data(state.cur_dc, "TEST_CCR_PARTIAL_1", config_buf); + add_extra_data(state.cur_dc, "TEST_CCR_PARTIAL_1", + format_string_std("START=%04u-%02u-%02u %02u:%02u:%02u,TEST=%02X%02X%02X%02X,RESULT=%02X%02X%02X%02X", + tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ptr[7], ptr[6], ptr[5], ptr[4], ptr[11], ptr[10], ptr[9], ptr[8])); break; case 2: // CFG_OXYGEN_CALIBRATION utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm); @@ -2149,12 +2129,14 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel o2_sensor_calibration_values[1] = (ptr[7] << 8) + ptr[6]; o2_sensor_calibration_values[2] = (ptr[9] << 8) + ptr[8]; o2_sensor_calibration_values[3] = (ptr[11] << 8) + ptr[10]; - snprintf(config_buf, sizeof(config_buf), "%04u,%04u,%04u,%04u,TIME=%04u-%02u-%02u %02u:%02u:%02u", o2_sensor_calibration_values[0], o2_sensor_calibration_values[1], o2_sensor_calibration_values[2], o2_sensor_calibration_values[3], tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); - add_extra_data(state.cur_dc, "CFG_OXYGEN_CALIBRATION", config_buf); + add_extra_data(state.cur_dc, "CFG_OXYGEN_CALIBRATION", + format_string_std("%04u,%04u,%04u,%04u,TIME=%04u-%02u-%02u %02u:%02u:%02u", + o2_sensor_calibration_values[0], o2_sensor_calibration_values[1], o2_sensor_calibration_values[2], o2_sensor_calibration_values[3], tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec)); break; case 3: // CFG_SERIAL - snprintf(config_buf, sizeof(config_buf), "PRODUCT=%c%c%c%c,SERIAL=%c%c%c%c%c%c%c%c", ptr[4], ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]); - add_extra_data(state.cur_dc, "CFG_SERIAL", config_buf); + add_extra_data(state.cur_dc, "CFG_SERIAL", + format_string_std("PRODUCT=%c%c%c%c,SERIAL=%c%c%c%c%c%c%c%c", + ptr[4], ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15])); break; case 4: // CFG_CONFIG_DECO switch ((ptr[5] & 0xC0) >> 6) { @@ -2172,12 +2154,15 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel break; } - snprintf(config_buf, sizeof(config_buf), "%s,%s,%s,safety stop required=%s,last_stop=%s,deco_algorithm=%s,stop_rounding=%u,deep_stops=%s", (ptr[4] & 0x80) ? "imperial" : "metric", (ptr[4] & 0x40) ? "sea" : "fresh", (ptr[4] & 0x30) ? "stops" : "ceiling", (ptr[4] & 0x10) ? "yes" : "no", (ptr[4] & 0x08) ? "6m" : "3m", (ptr[4] & 0x04) ? "VPM" : "Buhlmann+GF", (ptr[4] & 0x03) ? (ptr[4] & 0x03) * 30 : 1, deep_stops); - add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 1", config_buf); - snprintf(config_buf, sizeof(config_buf), "deep_stop_len=%u min,gas_switch_len=%u min,gf_low=%u,gf_high=%u,gf_low_bailout=%u,gf_high_bailout=%u,ppO2_low=%4.2f,ppO2_high=%4.2f", (ptr[5] & 0x38) >> 3, ptr[5] & 0x07, ptr[6], ptr[7], ptr[8], ptr[9], ptr[10] / 100.0f, ptr[11] / 100.0f); - add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 2", config_buf); - snprintf(config_buf, sizeof(config_buf), "alarm_global=%u,alarm_cns=%u,alarm_ppO2=%u,alarm_ceiling=%u,alarm_stop_miss=%u,alarm_decentrate=%u,alarm_ascentrate=%u", (ptr[12] & 0x80) >> 7, (ptr[12] & 0x40) >> 6, (ptr[12] & 0x20) >> 5, (ptr[12] & 0x10) >> 4, (ptr[12] & 0x08) >> 3, (ptr[12] & 0x04) >> 2, (ptr[12] & 0x02) >> 1); - add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 3", config_buf); + add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 1", + format_string_std("%s,%s,%s,safety stop required=%s,last_stop=%s,deco_algorithm=%s,stop_rounding=%u,deep_stops=%s", + (ptr[4] & 0x80) ? "imperial" : "metric", (ptr[4] & 0x40) ? "sea" : "fresh", (ptr[4] & 0x30) ? "stops" : "ceiling", (ptr[4] & 0x10) ? "yes" : "no", (ptr[4] & 0x08) ? "6m" : "3m", (ptr[4] & 0x04) ? "VPM" : "Buhlmann+GF", (ptr[4] & 0x03) ? (ptr[4] & 0x03) * 30 : 1, deep_stops)); + add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 2", + format_string_std("deep_stop_len=%u min,gas_switch_len=%u min,gf_low=%u,gf_high=%u,gf_low_bailout=%u,gf_high_bailout=%u,ppO2_low=%4.2f,ppO2_high=%4.2f", + (ptr[5] & 0x38) >> 3, ptr[5] & 0x07, ptr[6], ptr[7], ptr[8], ptr[9], ptr[10] / 100.0f, ptr[11] / 100.0f)); + add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 3", + format_string_std("alarm_global=%u,alarm_cns=%u,alarm_ppO2=%u,alarm_ceiling=%u,alarm_stop_miss=%u,alarm_decentrate=%u,alarm_ascentrate=%u", + (ptr[12] & 0x80) >> 7, (ptr[12] & 0x40) >> 6, (ptr[12] & 0x20) >> 5, (ptr[12] & 0x10) >> 4, (ptr[12] & 0x08) >> 3, (ptr[12] & 0x04) >> 2, (ptr[12] & 0x02) >> 1)); break; case 5: // CFG_VERSION switch (ptr[4]) { @@ -2194,8 +2179,9 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel device = "UNKNOWN"; break; } - snprintf(config_buf, sizeof(config_buf), "DEVICE=%s,HW=%d.%d,FW=%d.%d.%d.%d,FLAGS=%04X", device, ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], (ptr[15] << 24) + (ptr[14] << 16) + (ptr[13] << 8) + (ptr[12]), (ptr[11] << 8) + ptr[10]); - add_extra_data(state.cur_dc, "CFG_VERSION", config_buf); + add_extra_data(state.cur_dc, "CFG_VERSION", + format_string_std("DEVICE=%s,HW=%d.%d,FW=%d.%d.%d.%d,FLAGS=%04X", + device, ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], (ptr[15] << 24) + (ptr[14] << 16) + (ptr[13] << 8) + (ptr[12]), (ptr[11] << 8) + ptr[10])); break; } break; @@ -2238,7 +2224,7 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel /* Measure GPS */ state.cur_location.lat.udeg = (int)((ptr[7] << 24) + (ptr[6] << 16) + (ptr[5] << 8) + (ptr[4] << 0)); state.cur_location.lon.udeg = (int)((ptr[11] << 24) + (ptr[10] << 16) + (ptr[9] << 8) + (ptr[8] << 0)); - add_dive_to_dive_site(state.cur_dive, create_dive_site_with_gps("DLF imported", &state.cur_location, state.log->sites)); + state.log->sites.create("DLF imported"s, state.cur_location)->add_dive(state.cur_dive.get()); break; default: break; @@ -2277,12 +2263,12 @@ extern "C" int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divel } -extern "C" void parse_xml_init(void) +void parse_xml_init() { LIBXML_TEST_VERSION } -extern "C" void parse_xml_exit(void) +void parse_xml_exit() { xmlCleanupParser(); } diff --git a/core/parse.cpp b/core/parse.cpp index bd48157d9..e6a3fbc25 100644 --- a/core/parse.cpp +++ b/core/parse.cpp @@ -1,4 +1,3 @@ -#include "ssrf.h" #include #include #include @@ -12,6 +11,7 @@ #include "divelog.h" #include "divesite.h" #include "errorhelper.h" +#include "format.h" #include "sample.h" #include "subsurface-string.h" #include "picture.h" @@ -19,12 +19,8 @@ #include "device.h" #include "gettext.h" -parser_state::~parser_state() -{ - free_dive(cur_dive); - free_trip(cur_trip); - free_dive_site(cur_dive_site); -} +parser_state::parser_state() = default; +parser_state::~parser_state() = default; /* * If we don't have an explicit dive computer, @@ -32,25 +28,17 @@ parser_state::~parser_state() */ struct divecomputer *get_dc(struct parser_state *state) { - return state->cur_dc ?: &state->cur_dive->dc; + return state->cur_dc ?: &state->cur_dive->dcs[0]; } -/* - * Add a dive into the dive_table array - */ -extern "C" void record_dive_to_table(struct dive *dive, struct dive_table *table) -{ - add_to_dive_table(table, table->nr, fixup_dive(dive)); -} - -extern "C" void start_match(const char *type, const char *name, char *buffer) +void start_match(const char *type, const char *name, char *buffer) { if (verbose > 2) printf("Matching %s '%s' (%s)\n", type, name, buffer); } -extern "C" void nonmatch(const char *type, const char *name, char *buffer) +void nonmatch(const char *type, const char *name, char *buffer) { if (verbose > 1) printf("Unable to match %s '%s' (%s)\n", @@ -59,7 +47,7 @@ extern "C" void nonmatch(const char *type, const char *name, char *buffer) void event_start(struct parser_state *state) { - memset(&state->cur_event, 0, sizeof(state->cur_event)); + state->cur_event = event(); state->event_active = true; /* Active */ } @@ -67,36 +55,36 @@ void event_end(struct parser_state *state) { struct divecomputer *dc = get_dc(state); if (state->cur_event.type == 123) { - struct picture pic = empty_picture; - pic.filename = strdup(state->cur_event.name); + struct picture pic; + pic.filename = state->cur_event.name; /* theoretically this could fail - but we didn't support multi year offsets */ pic.offset.seconds = state->cur_event.time.seconds; - add_picture(&state->cur_dive->pictures, pic); /* Takes ownership. */ + add_picture(state->cur_dive->pictures, std::move(pic)); } else { - struct event *ev; /* At some point gas change events did not have any type. Thus we need to add * one on import, if we encounter the type one missing. */ - if (state->cur_event.type == 0 && strcmp(state->cur_event.name, "gaschange") == 0) + if (state->cur_event.type == 0 && state->cur_event.name == "gaschange") state->cur_event.type = state->cur_event.value >> 16 > 0 ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE; - ev = add_event(dc, state->cur_event.time.seconds, - state->cur_event.type, state->cur_event.flags, - state->cur_event.value, state->cur_event.name); + + struct event ev(state->cur_event.time.seconds, state->cur_event.type, state->cur_event.flags, + state->cur_event.value, state->cur_event.name); /* * Older logs might mark the dive to be CCR by having an "SP change" event at time 0:00. Better * to mark them being CCR on import so no need for special treatments elsewhere on the code. */ - if (ev && state->cur_event.time.seconds == 0 && state->cur_event.type == SAMPLE_EVENT_PO2 && state->cur_event.value && dc->divemode==OC) { + if (ev.time.seconds == 0 && ev.type == SAMPLE_EVENT_PO2 && ev.value && dc->divemode==OC) dc->divemode = CCR; + + if (ev.is_gaschange()) { + /* See try_to_fill_event() on why the filled-in index is one too big */ + ev.gas.index = state->cur_event.gas.index-1; + if (state->cur_event.gas.mix.o2.permille || state->cur_event.gas.mix.he.permille) + ev.gas.mix = state->cur_event.gas.mix; } - if (ev && event_is_gaschange(ev)) { - /* See try_to_fill_event() on why the filled-in index is one too big */ - ev->gas.index = state->cur_event.gas.index-1; - if (state->cur_event.gas.mix.o2.permille || state->cur_event.gas.mix.he.permille) - ev->gas.mix = state->cur_event.gas.mix; - } + add_event_to_dc(dc, std::move(ev)); } state->event_active = false; /* No longer active */ } @@ -116,7 +104,7 @@ void event_end(struct parser_state *state) bool is_dive(struct parser_state *state) { return state->cur_dive && - (state->cur_dive->dive_site || state->cur_dive->when || state->cur_dive->dc.samples); + (state->cur_dive->dive_site || state->cur_dive->when || !state->cur_dive->dcs[0].samples.empty()); } void reset_dc_info(struct divecomputer *, struct parser_state *state) @@ -160,10 +148,10 @@ void fingerprint_settings_start(struct parser_state *state) void fingerprint_settings_end(struct parser_state *state) { - create_fingerprint_node_from_hex(state->fingerprints, + create_fingerprint_node_from_hex(*state->fingerprints, state->cur_settings.fingerprint.model, state->cur_settings.fingerprint.serial, - state->cur_settings.fingerprint.data.c_str(), + state->cur_settings.fingerprint.data, state->cur_settings.fingerprint.fdeviceid, state->cur_settings.fingerprint.fdiveid); } @@ -176,9 +164,9 @@ void dc_settings_start(struct parser_state *state) void dc_settings_end(struct parser_state *state) { create_device_node(state->log->devices, - state->cur_settings.dc.model.c_str(), - state->cur_settings.dc.serial_nr.c_str(), - state->cur_settings.dc.nickname.c_str()); + state->cur_settings.dc.model, + state->cur_settings.dc.serial_nr, + state->cur_settings.dc.nickname); reset_dc_settings(state); } @@ -188,7 +176,7 @@ void dive_site_start(struct parser_state *state) return; state->taxonomy_category = -1; state->taxonomy_origin = -1; - state->cur_dive_site = (dive_site *)calloc(1, sizeof(struct dive_site)); + state->cur_dive_site = std::make_unique(); } void dive_site_end(struct parser_state *state) @@ -196,14 +184,13 @@ void dive_site_end(struct parser_state *state) if (!state->cur_dive_site) return; - struct dive_site *ds = alloc_or_get_dive_site(state->cur_dive_site->uuid, state->log->sites); - merge_dive_site(ds, state->cur_dive_site); + struct dive_site *ds = state->log->sites.alloc_or_get(state->cur_dive_site->uuid); + ds->merge(*state->cur_dive_site); if (verbose > 3) - printf("completed dive site uuid %x8 name {%s}\n", ds->uuid, ds->name); + printf("completed dive site uuid %x8 name {%s}\n", ds->uuid, ds->name.c_str()); - free_dive_site(state->cur_dive_site); - state->cur_dive_site = NULL; + state->cur_dive_site.reset(); } void filter_preset_start(struct parser_state *state) @@ -215,7 +202,7 @@ void filter_preset_start(struct parser_state *state) void filter_preset_end(struct parser_state *state) { - add_filter_preset_to_table(state->cur_filter.get(), state->log->filter_presets); + state->log->filter_presets.add(*state->cur_filter); state->cur_filter.reset(); } @@ -230,7 +217,7 @@ void fulltext_end(struct parser_state *state) { if (!state->in_fulltext) return; - filter_preset_set_fulltext(state->cur_filter.get(), state->fulltext.c_str(), state->fulltext_string_mode.c_str()); + state->cur_filter->set_fulltext(std::move(state->fulltext), state->fulltext_string_mode); state->fulltext.clear(); state->fulltext_string_mode.clear(); state->in_fulltext = false; @@ -247,8 +234,8 @@ void filter_constraint_end(struct parser_state *state) { if (!state->in_filter_constraint) return; - filter_preset_add_constraint(state->cur_filter.get(), state->filter_constraint_type.c_str(), state->filter_constraint_string_mode.c_str(), - state->filter_constraint_range_mode.c_str(), state->filter_constraint_negate, state->filter_constraint.c_str()); + state->cur_filter->add_constraint(state->filter_constraint_type, state->filter_constraint_string_mode, + state->filter_constraint_range_mode, state->filter_constraint_negate, state->filter_constraint); state->filter_constraint_type.clear(); state->filter_constraint_string_mode.clear(); @@ -262,8 +249,8 @@ void dive_start(struct parser_state *state) { if (state->cur_dive) return; - state->cur_dive = alloc_dive(); - reset_dc_info(&state->cur_dive->dc, state); + state->cur_dive = std::make_unique(); + reset_dc_info(&state->cur_dive->dcs[0], state); memset(&state->cur_tm, 0, sizeof(state->cur_tm)); state->o2pressure_sensor = 1; } @@ -272,17 +259,15 @@ void dive_end(struct parser_state *state) { if (!state->cur_dive) return; - if (!is_dive(state)) { - free_dive(state->cur_dive); - } else { - record_dive_to_table(state->cur_dive, state->log->dives); + if (is_dive(state)) { if (state->cur_trip) - add_dive_to_trip(state->cur_dive, state->cur_trip); + state->cur_trip->add_dive(state->cur_dive.get()); + // This would add dives in a sorted way: + state->log->dives.record_dive(std::move(state->cur_dive)); } - state->cur_dive = NULL; + state->cur_dive.reset(); state->cur_dc = NULL; - state->cur_location.lat.udeg = 0; - state->cur_location.lon.udeg = 0; + state->cur_location = location_t(); } void trip_start(struct parser_state *state) @@ -290,7 +275,7 @@ void trip_start(struct parser_state *state) if (state->cur_trip) return; dive_end(state); - state->cur_trip = alloc_trip(); + state->cur_trip = std::make_unique(); memset(&state->cur_tm, 0, sizeof(state->cur_tm)); } @@ -298,8 +283,7 @@ void trip_end(struct parser_state *state) { if (!state->cur_trip) return; - insert_trip(state->cur_trip, state->log->trips); - state->cur_trip = NULL; + state->log->trips.put(std::move(state->cur_trip)); } void picture_start(struct parser_state *state) @@ -308,14 +292,15 @@ void picture_start(struct parser_state *state) void picture_end(struct parser_state *state) { - add_picture(&state->cur_dive->pictures, state->cur_picture); + add_picture(state->cur_dive->pictures, std::move(state->cur_picture)); /* dive_add_picture took ownership, we can just clear out copy of the data */ - state->cur_picture = empty_picture; + state->cur_picture = picture(); } cylinder_t *cylinder_start(struct parser_state *state) { - return add_empty_cylinder(&state->cur_dive->cylinders); + state->cur_dive->cylinders.emplace_back(); + return &state->cur_dive->cylinders.back(); } void cylinder_end(struct parser_state *state) @@ -324,7 +309,7 @@ void cylinder_end(struct parser_state *state) void ws_start(struct parser_state *state) { - add_cloned_weightsystem(&state->cur_dive->weightsystems, empty_weightsystem); + state->cur_dive->weightsystems.emplace_back(); } void ws_end(struct parser_state *state) @@ -336,7 +321,7 @@ void ws_end(struct parser_state *state) */ static int sanitize_sensor_id(const struct dive *d, int nr) { - return d && nr >= 0 && nr < d->cylinders.nr ? nr : NO_SENSOR; + return d && nr >= 0 && static_cast(nr) < d->cylinders.size() ? nr : NO_SENSOR; } /* @@ -362,13 +347,13 @@ void sample_start(struct parser_state *state) struct divecomputer *dc = get_dc(state); struct sample *sample = prepare_sample(dc); - if (sample != dc->sample) { - memcpy(sample, sample-1, sizeof(struct sample)); - sample->pressure[0].mbar = 0; - sample->pressure[1].mbar = 0; + if (dc->samples.size() > 1) { + *sample = dc->samples[dc->samples.size() - 2]; + sample->pressure[0] = 0_bar; + sample->pressure[1] = 0_bar; } else { - sample->sensor[0] = sanitize_sensor_id(state->cur_dive, !state->o2pressure_sensor); - sample->sensor[1] = sanitize_sensor_id(state->cur_dive, state->o2pressure_sensor); + sample->sensor[0] = sanitize_sensor_id(state->cur_dive.get(), !state->o2pressure_sensor); + sample->sensor[1] = sanitize_sensor_id(state->cur_dive.get(), state->o2pressure_sensor); } state->cur_sample = sample; state->next_o2_sensor = 0; @@ -379,26 +364,17 @@ void sample_end(struct parser_state *state) if (!state->cur_dive) return; - finish_sample(get_dc(state)); state->cur_sample = NULL; } void divecomputer_start(struct parser_state *state) { - struct divecomputer *dc; - - /* Start from the previous dive computer */ - dc = &state->cur_dive->dc; - while (dc->next) - dc = dc->next; + struct divecomputer *dc = &state->cur_dive->dcs.back(); /* Did we already fill that in? */ - if (dc->samples || dc->model || dc->when) { - struct divecomputer *newdc = (divecomputer *)calloc(1, sizeof(*newdc)); - if (newdc) { - dc->next = newdc; - dc = newdc; - } + if (!dc->samples.empty() || !dc->model.empty() || dc->when) { + state->cur_dive->dcs.emplace_back(); + dc = &state->cur_dive->dcs.back(); } /* .. this is the one we'll use */ @@ -424,28 +400,8 @@ void userid_stop(struct parser_state *state) } /* - * Copy whitespace-trimmed string. Warning: the passed in string will be freed, - * therefore make sure to only pass in to NULL-initialized pointers or pointers - * to owned strings + * Copy whitespace-trimmed string. */ -extern "C" void utf8_string(const char *buffer, char **res) -{ - free(*res); - while (isspace(*buffer)) - ++buffer; - if (!*buffer) { - *res = strdup(""); - return; - } - const char *end = buffer + strlen(buffer); - while (isspace(end[-1])) - --end; - size_t len = end - buffer; - *res = (char *)malloc(len + 1); - memcpy(*res, buffer, len); - (*res)[len] = '\0'; -} - void utf8_string_std(const char *buffer, std::string *res) { while (isspace(*buffer)) @@ -468,52 +424,41 @@ void add_dive_site(const char *ds_name, struct dive *dive, struct parser_state * struct dive_site *ds = dive->dive_site; if (!ds) { // if the dive doesn't have a dive site, check if there's already a dive site by this name - ds = get_dive_site_by_name(trimmed.c_str(), state->log->sites); + ds = state->log->sites.get_by_name(trimmed); } if (ds) { // we have a dive site, let's hope there isn't a different name - if (empty_string(ds->name)) { - ds->name = copy_string(trimmed.c_str()); + if (ds->name.empty()) { + ds->name = trimmed; } else if (trimmed != ds->name) { // if it's not the same name, it's not the same dive site // but wait, we could have gotten this one based on GPS coords and could // have had two different names for the same site... so let's search the other // way around - struct dive_site *exact_match = get_dive_site_by_gps_and_name(trimmed.c_str(), &ds->location, state->log->sites); + struct dive_site *exact_match = state->log->sites.get_by_gps_and_name(trimmed, ds->location); if (exact_match) { unregister_dive_from_dive_site(dive); - add_dive_to_dive_site(dive, exact_match); + exact_match->add_dive(dive); } else { - struct dive_site *newds = create_dive_site(trimmed.c_str(), state->log->sites); + struct dive_site *newds = state->log->sites.create(trimmed.c_str()); unregister_dive_from_dive_site(dive); - add_dive_to_dive_site(dive, newds); + newds->add_dive(dive); if (has_location(&state->cur_location)) { // we started this uuid with GPS data, so lets use those newds->location = state->cur_location; } else { newds->location = ds->location; } - newds->notes = add_to_string(newds->notes, translate("gettextFromC", "additional name for site: %s\n"), ds->name); + newds->notes += '\n'; + newds->notes += format_string_std(translate("gettextFromC", "additional name for site: %s\n"), ds->name.c_str()); } } else if (dive->dive_site != ds) { // add the existing dive site to the current dive unregister_dive_from_dive_site(dive); - add_dive_to_dive_site(dive, ds); + ds->add_dive(dive); } } else { - add_dive_to_dive_site(dive, create_dive_site(trimmed.c_str(), state->log->sites)); + state->log->sites.create(trimmed)->add_dive(dive); } } } - -extern "C" int atoi_n(char *ptr, unsigned int len) -{ - if (len < 10) { - char buf[10]; - - memcpy(buf, ptr, len); - buf[len] = 0; - return atoi(buf); - } - return 0; -} diff --git a/core/parse.h b/core/parse.h index ebd8af7a6..4420088b1 100644 --- a/core/parse.h +++ b/core/parse.h @@ -2,8 +2,6 @@ #ifndef PARSE_H #define PARSE_H -#define MAX_EVENT_NAME 128 - #include "event.h" #include "equipment.h" // for cylinder_t #include "extradata.h" @@ -12,21 +10,16 @@ #include #include +#include #include +#include struct xml_params; struct divelog; - -typedef union { - struct event event; - char allocation[sizeof(struct event) + MAX_EVENT_NAME]; -} event_allocation_t; - -#ifdef __cplusplus +struct fingerprint_record; /* * Dive info as it is being built up.. - * C++-only so we can use std::string */ struct parser_settings { @@ -63,12 +56,12 @@ struct parser_state { enum import_source import_source = UNKNOWN; struct divecomputer *cur_dc = nullptr; /* non-owning */ - struct dive *cur_dive = nullptr; /* owning */ - struct dive_site *cur_dive_site = nullptr; /* owning */ - location_t cur_location { 0 }; - struct dive_trip *cur_trip = nullptr; /* owning */ + std::unique_ptr cur_dive; /* owning */ + std::unique_ptr cur_dive_site; /* owning */ + location_t cur_location; + std::unique_ptr cur_trip; /* owning */ struct sample *cur_sample = nullptr; /* non-owning */ - struct picture cur_picture { 0 }; /* owning */ + struct picture cur_picture; /* owning */ std::unique_ptr cur_filter; /* owning */ std::string fulltext; /* owning */ std::string fulltext_string_mode; /* owning */ @@ -91,16 +84,16 @@ struct parser_state { struct { std::string key; std::string value; } cur_extra_data; struct units xml_parsing_units; struct divelog *log = nullptr; /* non-owning */ - struct fingerprint_table *fingerprints = nullptr; /* non-owning */ + std::vector *fingerprints = nullptr; + /* non-owning */ sqlite3 *sql_handle = nullptr; /* for SQL based parsers */ bool event_active = false; - event_allocation_t event_allocation; + event cur_event; + parser_state(); ~parser_state(); }; -#define cur_event event_allocation.event - void event_start(struct parser_state *state); void event_end(struct parser_state *state); struct divecomputer *get_dc(struct parser_state *state); @@ -143,18 +136,13 @@ void utf8_string_std(const char *buffer, std::string *res); void add_dive_site(const char *ds_name, struct dive *dive, struct parser_state *state); -extern "C" { -#endif - int trimspace(char *buffer); void start_match(const char *type, const char *name, char *buffer); void nonmatch(const char *type, const char *name, char *buffer); -int atoi_n(char *ptr, unsigned int len); -void utf8_string(const char *buffer, char **res); -void parse_xml_init(void); +void parse_xml_init(); int parse_xml_buffer(const char *url, const char *buf, int size, struct divelog *log, const struct xml_params *params); -void parse_xml_exit(void); +void parse_xml_exit(); int parse_dm4_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log); int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log); int parse_seac_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log); @@ -163,10 +151,6 @@ int parse_shearwater_cloud_buffer(sqlite3 *handle, const char *url, const char * int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log); int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log); int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divelog *log); -#ifdef __cplusplus -} -#include std::string trimspace(const char *buffer); -#endif #endif diff --git a/core/picture.c b/core/picture.c deleted file mode 100644 index 632c3a2f5..000000000 --- a/core/picture.c +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "picture.h" -#include "dive.h" -#if !defined(SUBSURFACE_MOBILE) -#include "metadata.h" -#endif -#include "subsurface-string.h" -#include "table.h" -#include -#include - -static void free_picture(struct picture picture) -{ - free(picture.filename); - picture.filename = NULL; -} - -static int comp_pictures(struct picture a, struct picture b) -{ - if (a.offset.seconds < b.offset.seconds) - return -1; - if (a.offset.seconds > b.offset.seconds) - return 1; - return strcmp(a.filename ?: "", b.filename ?: ""); -} - -static bool picture_less_than(struct picture a, struct picture b) -{ - return comp_pictures(a, b) < 0; -} - -/* picture table functions */ -//static MAKE_GET_IDX(picture_table, struct picture, pictures) -static MAKE_GROW_TABLE(picture_table, struct picture, pictures) -static MAKE_GET_INSERTION_INDEX(picture_table, struct picture, pictures, picture_less_than) -MAKE_ADD_TO(picture_table, struct picture, pictures) -MAKE_REMOVE_FROM(picture_table, pictures) -MAKE_SORT(picture_table, struct picture, pictures, comp_pictures) -//MAKE_REMOVE(picture_table, struct picture, picture) -MAKE_CLEAR_TABLE(picture_table, pictures, picture) - -/* Add a clone of a picture to the end of a picture table. - * Cloned means that the filename-string is copied. */ -static void add_cloned_picture(struct picture_table *t, struct picture pic) -{ - pic.filename = copy_string(pic.filename); - int idx = picture_table_get_insertion_index(t, pic); - add_to_picture_table(t, idx, pic); -} - -void copy_pictures(const struct picture_table *s, struct picture_table *d) -{ - int i; - clear_picture_table(d); - for (i = 0; i < s->nr; i++) - add_cloned_picture(d, s->pictures[i]); -} - -void add_picture(struct picture_table *t, struct picture newpic) -{ - int idx = picture_table_get_insertion_index(t, newpic); - add_to_picture_table(t, idx, newpic); -} - -int get_picture_idx(const struct picture_table *t, const char *filename) -{ - for (int i = 0; i < t->nr; ++i) { - if (same_string(t->pictures[i].filename, filename)) - return i; - } - return -1; -} - -#if !defined(SUBSURFACE_MOBILE) -/* Return distance of timestamp to time of dive. Result is always positive, 0 means during dive. */ -static timestamp_t time_from_dive(const struct dive *d, timestamp_t timestamp) -{ - timestamp_t end_time = dive_endtime(d); - if (timestamp < d->when) - return d->when - timestamp; - else if (timestamp > end_time) - return timestamp - end_time; - else - return 0; -} - -/* Return dive closest selected dive to given timestamp or NULL if no dives are selected. */ -static struct dive *nearest_selected_dive(timestamp_t timestamp) -{ - struct dive *d, *res = NULL; - int i; - timestamp_t offset, min = 0; - - for_each_dive(i, d) { - if (!d->selected) - continue; - offset = time_from_dive(d, timestamp); - if (!res || offset < min) { - res = d; - min = offset; - } - - /* We suppose that dives are sorted chronologically. Thus - * if the offset starts to increase, we can end. This ignores - * pathological cases such as overlapping dives. In such a - * case the user will have to add pictures manually. - */ - if (offset == 0 || offset > min) - break; - } - return res; -} - -// only add pictures that have timestamps between 30 minutes before the dive and -// 30 minutes after the dive ends -#define D30MIN (30 * 60) -static bool dive_check_picture_time(const struct dive *d, timestamp_t timestamp) -{ - return time_from_dive(d, timestamp) < D30MIN; -} - -/* Creates a picture and indicates the dive to which this picture should be added. - * The caller is responsible for actually adding the picture to the dive. - * If no appropriate dive was found, no picture is created and NULL is returned. - */ -struct picture *create_picture(const char *filename, timestamp_t shift_time, bool match_all, struct dive **dive) -{ - struct metadata metadata; - timestamp_t timestamp; - - get_metadata(filename, &metadata); - timestamp = metadata.timestamp + shift_time; - *dive = nearest_selected_dive(timestamp); - - if (!*dive) - return NULL; - if (get_picture_idx(&(*dive)->pictures, filename) >= 0) - return NULL; - if (!match_all && !dive_check_picture_time(*dive, timestamp)) - return NULL; - - struct picture *picture = malloc(sizeof(struct picture)); - picture->filename = strdup(filename); - picture->offset.seconds = metadata.timestamp - (*dive)->when + shift_time; - picture->location = metadata.location; - return picture; -} - -bool picture_check_valid_time(timestamp_t timestamp, timestamp_t shift_time) -{ - int i; - struct dive *dive; - - for_each_dive (i, dive) - if (dive->selected && dive_check_picture_time(dive, timestamp + shift_time)) - return true; - return false; -} -#endif diff --git a/core/picture.cpp b/core/picture.cpp new file mode 100644 index 000000000..a932d78ee --- /dev/null +++ b/core/picture.cpp @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "picture.h" +#include "dive.h" +#include "divelist.h" +#include "divelog.h" +#if !defined(SUBSURFACE_MOBILE) +#include "metadata.h" +#endif +#include "range.h" +#include "subsurface-string.h" +#include +#include + +bool picture::operator<(const struct picture &p) const +{ + return std::tie(offset.seconds, filename) < + std::tie(p.offset.seconds, p.filename); +} + +void add_picture(picture_table &t, struct picture newpic) +{ + auto it = std::lower_bound(t.begin(), t.end(), newpic); + t.insert(it, std::move(newpic)); +} + +int get_picture_idx(const picture_table &t, const std::string &filename) +{ + + return index_of_if(t, [&filename] (const picture &p) + { return p.filename == filename; }); +} + +#if !defined(SUBSURFACE_MOBILE) +/* Return distance of timestamp to time of dive. Result is always positive, 0 means during dive. */ +static timestamp_t time_from_dive(const struct dive &d, timestamp_t timestamp) +{ + timestamp_t end_time = d.endtime(); + if (timestamp < d.when) + return d.when - timestamp; + else if (timestamp > end_time) + return timestamp - end_time; + else + return 0; +} + +/* Return dive closest selected dive to given timestamp or NULL if no dives are selected. */ +static struct dive *nearest_selected_dive(timestamp_t timestamp) +{ + struct dive *res = NULL; + timestamp_t min = 0; + + for (auto &d: divelog.dives) { + if (!d->selected) + continue; + timestamp_t offset = time_from_dive(*d, timestamp); + if (!res || offset < min) { + res = d.get(); + min = offset; + } + + /* We suppose that dives are sorted chronologically. Thus + * if the offset starts to increase, we can end. This ignores + * pathological cases such as overlapping dives. In such a + * case the user will have to add pictures manually. + */ + if (offset == 0 || offset > min) + break; + } + return res; +} + +// only add pictures that have timestamps between 30 minutes before the dive and +// 30 minutes after the dive ends +static constexpr timestamp_t d30min = 30 * 60; +static bool dive_check_picture_time(const struct dive &d, timestamp_t timestamp) +{ + return time_from_dive(d, timestamp) < d30min; +} + +/* Creates a picture and indicates the dive to which this picture should be added. + * The caller is responsible for actually adding the picture to the dive. + * If no appropriate dive was found, no picture is created and null is returned. + */ +std::pair, dive *> create_picture(const std::string &filename, timestamp_t shift_time, bool match_all) +{ + struct metadata metadata; + timestamp_t timestamp; + + get_metadata(filename.c_str(), &metadata); + timestamp = metadata.timestamp + shift_time; + struct dive *dive = nearest_selected_dive(timestamp); + + if (!dive) + return { {}, nullptr }; + if (get_picture_idx(dive->pictures, filename) >= 0) + return { {}, nullptr }; + if (!match_all && !dive_check_picture_time(*dive, timestamp)) + return { {}, nullptr }; + + struct picture picture; + picture.filename = filename; + picture.offset.seconds = metadata.timestamp - dive->when + shift_time; + picture.location = metadata.location; + return { picture, dive }; +} + +bool picture_check_valid_time(timestamp_t timestamp, timestamp_t shift_time) +{ + return std::any_of(divelog.dives.begin(), divelog.dives.end(), + [t = timestamp + shift_time] (auto &d) + { return d->selected && dive_check_picture_time(*d, t); }); +} +#endif diff --git a/core/picture.h b/core/picture.h index 042fb9bae..de584af7f 100644 --- a/core/picture.h +++ b/core/picture.h @@ -1,56 +1,35 @@ // SPDX-License-Identifier: GPL-2.0 +// picture (more precisely media) related strutures and functions #ifndef PICTURE_H #define PICTURE_H -// picture (more precisely media) related strutures and functions #include "units.h" -#include // For NULL - -#ifdef __cplusplus -extern "C" { -#endif +#include +#include +#include struct dive; struct picture { - char *filename; + std::string filename; offset_t offset; location_t location; + bool operator<(const picture &) const; }; -static const struct picture empty_picture = { NULL, { 0 }, { { 0 }, { 0 } } }; - -/* loop through all pictures of a dive */ -#define FOR_EACH_PICTURE(_dive) \ - if ((_dive) && (_dive)->pictures.nr) \ - for (struct picture *picture = (_dive)->pictures.pictures; \ - picture < (_dive)->pictures.pictures + (_dive)->pictures.nr; \ - picture++) /* Table of pictures. Attention: this stores pictures, - * *not* pointers to pictures. This has two crucial consequences: - * 1) Pointers to pictures are not stable. They may be - * invalidated if the table is reallocated. - * 2) add_to_picture_table(), etc. take ownership of the - * picture. Notably of the filename. */ -struct picture_table { - int nr, allocated; - struct picture *pictures; -}; + * *not* pointers to pictures. This means that + * pointers to pictures are not stable. They are + * invalidated if the table is reallocated. + */ +using picture_table = std::vector; /* picture table functions */ -extern void clear_picture_table(struct picture_table *); -extern void add_to_picture_table(struct picture_table *, int idx, struct picture pic); -extern void copy_pictures(const struct picture_table *s, struct picture_table *d); -extern void add_picture(struct picture_table *, struct picture newpic); -extern void remove_from_picture_table(struct picture_table *, int idx); -extern int get_picture_idx(const struct picture_table *, const char *filename); /* Return -1 if not found */ -extern void sort_picture_table(struct picture_table *); +extern void add_to_picture_table(picture_table &, int idx, struct picture pic); +extern void add_picture(picture_table &, struct picture newpic); +extern int get_picture_idx(const picture_table &, const std::string &filename); /* Return -1 if not found */ -extern struct picture *create_picture(const char *filename, timestamp_t shift_time, bool match_all, struct dive **dive); +extern std::pair, dive *> create_picture(const std::string &filename, timestamp_t shift_time, bool match_all); extern bool picture_check_valid_time(timestamp_t timestamp, timestamp_t shift_time); -#ifdef __cplusplus -} -#endif - #endif // PICTURE_H diff --git a/core/pictureobj.cpp b/core/pictureobj.cpp deleted file mode 100644 index cb6b32675..000000000 --- a/core/pictureobj.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pictureobj.h" -#include "qthelper.h" - -PictureObj::PictureObj() : offset({ 0 }), location({ 0 }) -{ -} - -PictureObj::PictureObj(const picture &pic) : filename(pic.filename), offset(pic.offset), location(pic.location) -{ -} - -picture PictureObj::toCore() const -{ - return picture { - strdup(filename.c_str()), - offset, - location - }; -} - -bool PictureObj::operator<(const PictureObj &p2) const -{ - if (offset.seconds != p2.offset.seconds) - return offset.seconds < p2.offset.seconds; - return strcmp(filename.c_str(), p2.filename.c_str()) < 0; -} diff --git a/core/pictureobj.h b/core/pictureobj.h deleted file mode 100644 index 6ea20259b..000000000 --- a/core/pictureobj.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#ifndef PICTUREOBJ_H -#define PICTUREOBJ_H - -// A tiny helper class that represents a struct picture of the core -// It does, however, keep the filename as a std::string so that C++ code -// doesn't have do its own memory-management. - -#include "core/units.h" -#include "core/picture.h" -#include - -struct PictureObj { - std::string filename; - offset_t offset; - location_t location; - - PictureObj(); // Initialize to empty picture. - PictureObj(const picture &pic); // Create from core struct picture. - picture toCore() const; // Turn into core structure. Caller responsible for freeing. - bool operator<(const PictureObj &p2) const; -}; - -#endif // PICTUREOBJ_H diff --git a/core/planner.cpp b/core/planner.cpp index 3cd0a5d6b..f04eff3e6 100644 --- a/core/planner.cpp +++ b/core/planner.cpp @@ -10,9 +10,9 @@ #include #include #include -#include "ssrf.h" #include "dive.h" #include "divelist.h" // for init_decompression() +#include "divelog.h" #include "sample.h" #include "subsurface-string.h" #include "deco.h" @@ -20,6 +20,7 @@ #include "event.h" #include "interpolate.h" #include "planner.h" +#include "range.h" #include "subsurface-time.h" #include "gettext.h" #include "libdivecomputer/parser.h" @@ -42,7 +43,7 @@ static int decostoplevels_imperial[] = { 0, 3048, 6096, 9144, 12192, 15240, 1828 325120, 345440, 365760, 386080 }; #if DEBUG_PLAN -extern "C" void dump_plan(struct diveplan *diveplan) +void dump_plan(struct diveplan *diveplan) { struct divedatapoint *dp; struct tm tm; @@ -56,7 +57,7 @@ extern "C" void dump_plan(struct diveplan *diveplan) printf("\nDiveplan @ %04d-%02d-%02d %02d:%02d:%02d (surfpres %dmbar):\n", tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, - diveplan->surface_pressure); + diveplan->surface_pressure.mbar); dp = diveplan->dp; while (dp) { printf("\t%3u:%02u: %6dmm cylid: %2d setpoint: %d\n", FRACTION_TUPLE(dp->time, 60), dp->depth, dp->cylinderid, dp->setpoint); @@ -65,37 +66,36 @@ extern "C" void dump_plan(struct diveplan *diveplan) } #endif -extern "C" bool diveplan_empty(struct diveplan *diveplan) +diveplan::diveplan() { - struct divedatapoint *dp; - if (!diveplan || !diveplan->dp) - return true; - dp = diveplan->dp; - while (dp) { - if (dp->time) - return false; - dp = dp->next; - } - return true; +} + +diveplan::~diveplan() +{ +} + +bool diveplan::is_empty() const +{ + return std::none_of(dp.begin(), dp.end(), [](const divedatapoint &dp) { return dp.time != 0; }); } /* get the cylinder index at a certain time during the dive */ -extern "C" int get_cylinderid_at_time(struct dive *dive, struct divecomputer *dc, duration_t time) +int get_cylinderid_at_time(struct dive *dive, struct divecomputer *dc, duration_t time) { // we start with the first cylinder unless an event tells us otherwise int cylinder_idx = 0; - struct event *event = dc->events; - while (event && event->time.seconds <= time.seconds) { - if (!strcmp(event->name, "gaschange")) - cylinder_idx = get_cylinder_index(dive, event); - event = event->next; + for (const auto &event: dc->events) { + if (event.time.seconds > time.seconds) + break; + if (event.name == "gaschange") + cylinder_idx = dive->get_cylinder_index(event); } return cylinder_idx; } static int get_gasidx(struct dive *dive, struct gasmix mix) { - return find_best_gasmix_match(mix, &dive->cylinders); + return find_best_gasmix_match(mix, dive->cylinders); } static void interpolate_transition(struct deco_state *ds, struct dive *dive, duration_t t0, duration_t t1, depth_t d0, depth_t d1, struct gasmix gasmix, o2pressure_t po2, enum divemode_t divemode) @@ -104,20 +104,17 @@ static void interpolate_transition(struct deco_state *ds, struct dive *dive, dur for (j = t0.seconds; j < t1.seconds; j++) { int depth = interpolate(d0.mm, d1.mm, j - t0.seconds, t1.seconds - t0.seconds); - add_segment(ds, depth_to_bar(depth, dive), gasmix, 1, po2.mbar, divemode, prefs.bottomsac, true); + add_segment(ds, dive->depth_to_bar(depth), gasmix, 1, po2.mbar, divemode, prefs.bottomsac, true); } if (d1.mm > d0.mm) - calc_crushing_pressure(ds, depth_to_bar(d1.mm, dive)); + calc_crushing_pressure(ds, dive->depth_to_bar(d1.mm)); } /* returns the tissue tolerance at the end of this (partial) dive */ static int tissue_at_end(struct deco_state *ds, struct dive *dive, const struct divecomputer *dc, deco_state_cache &cache) { - struct sample *sample, *psample; - int i; - depth_t lastdepth = {}; - duration_t t0 = {}, t1 = {}; - struct gasmix gas; + depth_t lastdepth; + duration_t t0; int surface_interval = 0; if (!dive) @@ -125,27 +122,21 @@ static int tissue_at_end(struct deco_state *ds, struct dive *dive, const struct if (cache) { cache.restore(ds, true); } else { - surface_interval = init_decompression(ds, dive, true); + surface_interval = divelog.dives.init_decompression(ds, dive, true); cache.cache(ds); } - if (!dc->samples) + if (dc->samples.empty()) return 0; - psample = sample = dc->sample; - const struct event *evdm = NULL; - enum divemode_t divemode = UNDEF_COMP_TYPE; + const struct sample *psample = nullptr; + divemode_loop loop(*dc); + for (auto &sample: dc->samples) { + o2pressure_t setpoint = psample ? psample->setpoint + : sample.setpoint; - for (i = 0; i < dc->samples; i++, sample++) { - o2pressure_t setpoint; - - if (i) - setpoint = sample[-1].setpoint; - else - setpoint = sample[0].setpoint; - - t1 = sample->time; - gas = get_gasmix_at_time(dive, dc, t0); - if (i > 0) + duration_t t1 = sample.time; + struct gasmix gas = dive->get_gasmix_at_time(*dc, t0); + if (psample) lastdepth = psample->depth; /* The ceiling in the deeper portion of a multilevel dive is sometimes critical for the VPM-B @@ -157,23 +148,22 @@ static int tissue_at_end(struct deco_state *ds, struct dive *dive, const struct * portion of the dive. * Remember the value for later. */ - if ((decoMode(true) == VPMB) && (lastdepth.mm > sample->depth.mm)) { + if ((decoMode(true) == VPMB) && (lastdepth.mm > sample.depth.mm)) { pressure_t ceiling_pressure; nuclear_regeneration(ds, t0.seconds); vpmb_start_gradient(ds); - ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(ds, dive, - depth_to_bar(lastdepth.mm, dive), true), + ceiling_pressure.mbar = dive->depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(ds, dive, + dive->depth_to_bar(lastdepth.mm), true), dive->surface_pressure.mbar / 1000.0, dive, - 1), - dive); + 1)); if (ceiling_pressure.mbar > ds->max_bottom_ceiling_pressure.mbar) ds->max_bottom_ceiling_pressure.mbar = ceiling_pressure.mbar; } - divemode = get_current_divemode(&dive->dc, t0.seconds + 1, &evdm, &divemode); - interpolate_transition(ds, dive, t0, t1, lastdepth, sample->depth, gas, setpoint, divemode); - psample = sample; + divemode_t divemode = loop.at(t0.seconds + 1); + interpolate_transition(ds, dive, t0, t1, lastdepth, sample.depth, gas, setpoint, divemode); + psample = &sample; t0 = t1; } return surface_interval; @@ -194,69 +184,60 @@ static void update_cylinder_pressure(struct dive *d, int old_depth, int new_dept if (!cyl) return; mean_depth.mm = (old_depth + new_depth) / 2; - gas_used.mliter = lrint(depth_to_atm(mean_depth.mm, d) * sac / 60 * duration * factor / 1000); - cyl->gas_used.mliter += gas_used.mliter; + gas_used.mliter = lrint(d->depth_to_atm(mean_depth.mm) * sac / 60 * duration * factor / 1000); + cyl->gas_used += gas_used; if (in_deco) - cyl->deco_gas_used.mliter += gas_used.mliter; + cyl->deco_gas_used += gas_used; if (cyl->type.size.mliter) { delta_p.mbar = lrint(gas_used.mliter * 1000.0 / cyl->type.size.mliter * gas_compressibility_factor(cyl->gasmix, cyl->end.mbar / 1000.0)); - cyl->end.mbar -= delta_p.mbar; + cyl->end -= delta_p; } } /* overwrite the data in dive * return false if something goes wrong */ -static void create_dive_from_plan(struct diveplan *diveplan, struct dive *dive, struct divecomputer *dc, bool track_gas) +static void create_dive_from_plan(struct diveplan &diveplan, struct dive *dive, struct divecomputer *dc, bool track_gas) { - struct divedatapoint *dp; - struct sample *sample; - struct event *ev; cylinder_t *cyl; int oldpo2 = 0; int lasttime = 0, last_manual_point = 0; - depth_t lastdepth = {.mm = 0}; + depth_t lastdepth; int lastcylid; enum divemode_t type = dc->divemode; - if (!diveplan || !diveplan->dp) + if (diveplan.dp.empty()) return; #if DEBUG_PLAN & 4 printf("in create_dive_from_plan\n"); dump_plan(diveplan); #endif - dive->salinity = diveplan->salinity; + dive->salinity = diveplan.salinity; // reset the cylinders and clear out the samples and events of the // dive-to-be-planned so we can restart reset_cylinders(dive, track_gas); - dc->when = dive->when = diveplan->when; - dc->surface_pressure.mbar = diveplan->surface_pressure; - dc->salinity = diveplan->salinity; - free_samples(dc); - while ((ev = dc->events)) { - dc->events = dc->events->next; - free(ev); - } - dp = diveplan->dp; + dc->when = dive->when = diveplan.when; + dc->surface_pressure = diveplan.surface_pressure; + dc->salinity = diveplan.salinity; + dc->samples.clear(); + dc->events.clear(); /* Create first sample at time = 0, not based on dp because * there is no real dp for time = 0, set first cylinder to 0 * O2 setpoint for this sample will be filled later from next dp */ - cyl = get_or_create_cylinder(dive, 0); - sample = prepare_sample(dc); + cyl = dive->get_or_create_cylinder(0); + struct sample *sample = prepare_sample(dc); sample->sac.mliter = prefs.bottomsac; if (track_gas && cyl->type.workingpressure.mbar) sample->pressure[0].mbar = cyl->end.mbar; sample->manually_entered = true; - finish_sample(dc); lastcylid = 0; - while (dp) { - int po2 = dp->setpoint; - int time = dp->time; - depth_t depth = dp->depth; + for (auto &dp: diveplan.dp) { + int po2 = dp.setpoint; + int time = dp.time; + depth_t depth = dp.depth; if (time == 0) { /* special entries that just inform the algorithm about * additional gases that are available */ - dp = dp->next; continue; } @@ -265,18 +246,18 @@ static void create_dive_from_plan(struct diveplan *diveplan, struct dive *dive, /* this is a bad idea - we should get a different SAMPLE_EVENT type * reserved for this in libdivecomputer... overloading SMAPLE_EVENT_PO2 * with a different meaning will only cause confusion elsewhere in the code */ - if (dp->divemode == type) + if (dp.divemode == type) add_event(dc, lasttime, SAMPLE_EVENT_PO2, 0, po2, QT_TRANSLATE_NOOP("gettextFromC", "SP change")); oldpo2 = po2; } /* Make sure we have the new gas, and create a gas change event */ - if (dp->cylinderid != lastcylid) { + if (dp.cylinderid != lastcylid) { /* need to insert a first sample for the new gas */ - add_gas_switch_event(dive, dc, lasttime + 1, dp->cylinderid); - cyl = get_cylinder(dive, dp->cylinderid); // FIXME: This actually may get one past the last cylinder for "surface air". + add_gas_switch_event(dive, dc, lasttime + 1, dp.cylinderid); + cyl = dive->get_cylinder(dp.cylinderid); // FIXME: This actually may get one past the last cylinder for "surface air". if (!cyl) { - report_error("Invalid cylinder in create_dive_from_plan(): %d", dp->cylinderid); + report_error("Invalid cylinder in create_dive_from_plan(): %d", dp.cylinderid); continue; } sample = prepare_sample(dc); @@ -285,13 +266,12 @@ static void create_dive_from_plan(struct diveplan *diveplan, struct dive *dive, sample[-1].o2sensor[0].mbar = po2; sample->time.seconds = lasttime + 1; sample->depth = lastdepth; - sample->manually_entered = dp->entered; - sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac; - finish_sample(dc); - lastcylid = dp->cylinderid; + sample->manually_entered = dp.entered; + sample->sac.mliter = dp.entered ? prefs.bottomsac : prefs.decosac; + lastcylid = dp.cylinderid; } - if (dp->divemode != type) { - type = dp->divemode; + if (dp.divemode != type) { + type = dp.divemode; add_event(dc, lasttime, SAMPLE_EVENT_BOOKMARK, 0, type, "modechange"); } @@ -302,78 +282,51 @@ static void create_dive_from_plan(struct diveplan *diveplan, struct dive *dive, sample[-1].setpoint.mbar = po2; sample->setpoint.mbar = po2; sample->time.seconds = lasttime = time; - if (dp->entered) last_manual_point = dp->time; + if (dp.entered) last_manual_point = dp.time; sample->depth = lastdepth = depth; - sample->manually_entered = dp->entered; - sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac; + sample->manually_entered = dp.entered; + sample->sac.mliter = dp.entered ? prefs.bottomsac : prefs.decosac; if (track_gas && !sample[-1].setpoint.mbar) { /* Don't track gas usage for CCR legs of dive */ update_cylinder_pressure(dive, sample[-1].depth.mm, depth.mm, time - sample[-1].time.seconds, - dp->entered ? diveplan->bottomsac : diveplan->decosac, cyl, !dp->entered, type); + dp.entered ? diveplan.bottomsac : diveplan.decosac, cyl, !dp.entered, type); if (cyl->type.workingpressure.mbar) sample->pressure[0].mbar = cyl->end.mbar; } - finish_sample(dc); - dp = dp->next; } dc->last_manual_time.seconds = last_manual_point; #if DEBUG_PLAN & 32 - save_dive(stdout, dive); + save_dive(stdout, *dive); #endif return; } -extern "C" void free_dps(struct diveplan *diveplan) +divedatapoint::divedatapoint(int time_incr, int depth, int cylinderid, int po2, bool entered) : + time(time_incr), + depth{ .mm = depth }, + cylinderid(cylinderid), + minimum_gas(0_bar), + setpoint(po2), + entered(entered), + divemode(OC) { - if (!diveplan) - return; - struct divedatapoint *dp = diveplan->dp; - while (dp) { - struct divedatapoint *ndp = dp->next; - free(dp); - dp = ndp; - } - diveplan->dp = NULL; } -static struct divedatapoint *create_dp(int time_incr, int depth, int cylinderid, int po2) +static void add_to_end_of_diveplan(struct diveplan &diveplan, const struct divedatapoint &dp) { - struct divedatapoint *dp; - - dp = (divedatapoint *)malloc(sizeof(struct divedatapoint)); - dp->time = time_incr; - dp->depth.mm = depth; - dp->cylinderid = cylinderid; - dp->minimum_gas.mbar = 0; - dp->setpoint = po2; - dp->entered = false; - dp->next = NULL; - return dp; + auto maxtime = std::max_element(diveplan.dp.begin(), diveplan.dp.end(), + [] (const divedatapoint &p1, const divedatapoint &p2) + { return p1.time < p2.time; }); + int lasttime = maxtime != diveplan.dp.end() ? maxtime->time : 0; + diveplan.dp.push_back(dp); + diveplan.dp.back().time += lasttime; } -static void add_to_end_of_diveplan(struct diveplan *diveplan, struct divedatapoint *dp) +void plan_add_segment(struct diveplan &diveplan, int duration, int depth, int cylinderid, int po2, bool entered, enum divemode_t divemode) { - struct divedatapoint **lastdp = &diveplan->dp; - struct divedatapoint *ldp = *lastdp; - int lasttime = 0; - while (*lastdp) { - ldp = *lastdp; - if (ldp->time > lasttime) - lasttime = ldp->time; - lastdp = &(*lastdp)->next; - } - *lastdp = dp; - if (ldp) - dp->time += lasttime; -} - -extern "C" struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, int cylinderid, int po2, bool entered, enum divemode_t divemode) -{ - struct divedatapoint *dp = create_dp(duration, depth, cylinderid, divemode == CCR ? po2 : 0); - dp->entered = entered; - dp->divemode = divemode; + struct divedatapoint dp(duration, depth, cylinderid, divemode == CCR ? po2 : 0, entered); + dp.divemode = divemode; add_to_end_of_diveplan(diveplan, dp); - return dp; } struct gaschanges { @@ -385,61 +338,62 @@ struct gaschanges { static int setpoint_change(struct dive *dive, int cylinderid) { - cylinder_t *cylinder = get_cylinder(dive, cylinderid); - if (!cylinder->type.description) + cylinder_t *cylinder = dive->get_cylinder(cylinderid); + if (cylinder->type.description.empty()) return 0; - if (!strncmp(cylinder->type.description, "SP ", 3)) { + if (starts_with(cylinder->type.description, "SP ")) { float sp; - sscanf(cylinder->type.description + 3, "%f", &sp); - return (int) (sp * 1000); + sscanf(cylinder->type.description.c_str() + 3, "%f", &sp); + return (int) (sp * 1000.0); } else { return 0; } } -static std::vector analyze_gaslist(struct diveplan *diveplan, struct dive *dive, int depth, int *asc_cylinder, bool ccr) +static std::vector analyze_gaslist(const struct diveplan &diveplan, struct dive *dive, int dcNr, + int depth, int *asc_cylinder, bool ccr, bool &inappropriate_cylinder_use) { size_t nr = 0; std::vector gaschanges; - struct divedatapoint *dp = diveplan->dp; - struct divedatapoint *best_ascent_dp = NULL; + const struct divedatapoint *best_ascent_dp = nullptr; bool total_time_zero = true; - while (dp) { - if (dp->time == 0 && total_time_zero && (ccr == (bool) setpoint_change(dive, dp->cylinderid))) { - if (dp->depth.mm <= depth) { + const divecomputer *dc = dive->get_dc(dcNr); + for (auto &dp: diveplan.dp) { + inappropriate_cylinder_use = inappropriate_cylinder_use || !is_cylinder_use_appropriate(*dc, *dive->get_cylinder(dp.cylinderid), false); + + if (dp.time == 0 && total_time_zero && (ccr == (bool) setpoint_change(dive, dp.cylinderid))) { + if (dp.depth.mm <= depth) { int i = 0; nr++; gaschanges.resize(nr); while (i < static_cast(nr) - 1) { - if (dp->depth.mm < gaschanges[i].depth) { + if (dp.depth.mm < gaschanges[i].depth) { for (int j = static_cast(nr) - 2; j >= i; j--) gaschanges[j + 1] = gaschanges[j]; break; } i++; } - gaschanges[i].depth = dp->depth.mm; - gaschanges[i].gasidx = dp->cylinderid; + gaschanges[i].depth = dp.depth.mm; + gaschanges[i].gasidx = dp.cylinderid; assert(gaschanges[i].gasidx != -1); } else { /* is there a better mix to start deco? */ - if (!best_ascent_dp || dp->depth.mm < best_ascent_dp->depth.mm) { - best_ascent_dp = dp; + if (!best_ascent_dp || dp.depth.mm < best_ascent_dp->depth.mm) { + best_ascent_dp = &dp; } } } else { total_time_zero = false; } - dp = dp->next; } - if (best_ascent_dp) { + if (best_ascent_dp) *asc_cylinder = best_ascent_dp->cylinderid; - } #if DEBUG_PLAN & 16 for (size_t nr = 0; nr < gaschanges.size(); nr++) { int idx = gaschanges[nr].gasidx; printf("gaschange nr %d: @ %5.2lfm gasidx %d (%s)\n", nr, gaschanges[nr].depth / 1000.0, - idx, gasname(&get_cylinder(&dive, idx)->gasmix)); + idx, dive.get_cylinder(idx)->gasmix.name().c_str()); } #endif return gaschanges; @@ -501,7 +455,7 @@ static std::vector sort_stops(int dstops[], size_t dnr, std::vectorget_cylinder(cylinder_id); while (depth > 0) { int deltad = ascent_velocity(depth, avg_depth, bottom_time) * base_timestep; if (deltad > depth) @@ -550,11 +504,11 @@ static bool trial_ascent(struct deco_state *ds, int wait_time, int trial_depth, // However, we still need to make sure we don't break the ceiling due to on-gassing during ascent. trial_cache.cache(ds); if (wait_time) - add_segment(ds, depth_to_bar(trial_depth, dive), + add_segment(ds, dive->depth_to_bar(trial_depth), gasmix, wait_time, po2, divemode, prefs.decosac, true); if (decoMode(true) == VPMB) { - double tolerance_limit = tissue_tolerance_calc(ds, dive, depth_to_bar(stoplevel, dive), true); + double tolerance_limit = tissue_tolerance_calc(ds, dive, dive->depth_to_bar(stoplevel), true); update_regression(ds, dive); if (deco_allowed_depth(tolerance_limit, surface_pressure, dive, 1) > stoplevel) { trial_cache.restore(ds, false); @@ -567,10 +521,10 @@ static bool trial_ascent(struct deco_state *ds, int wait_time, int trial_depth, int deltad = ascent_velocity(trial_depth, avg_depth, bottom_time) * base_timestep; if (deltad > trial_depth) /* don't test against depth above surface */ deltad = trial_depth; - add_segment(ds, depth_to_bar(trial_depth, dive), + add_segment(ds, dive->depth_to_bar(trial_depth), gasmix, base_timestep, po2, divemode, prefs.decosac, true); - tolerance_limit = tissue_tolerance_calc(ds, dive, depth_to_bar(trial_depth, dive), true); + tolerance_limit = tissue_tolerance_calc(ds, dive, dive->depth_to_bar(trial_depth), true); if (decoMode(true) == VPMB) update_regression(ds, dive); if (deco_allowed_depth(tolerance_limit, surface_pressure, dive, 1) > trial_depth - deltad) { @@ -590,10 +544,9 @@ static bool trial_ascent(struct deco_state *ds, int wait_time, int trial_depth, */ static bool enough_gas(const struct dive *dive, int current_cylinder) { - cylinder_t *cyl; - if (current_cylinder < 0 || current_cylinder >= dive->cylinders.nr) + if (current_cylinder < 0 || static_cast(current_cylinder) >= dive->cylinders.size()) return false; - cyl = get_cylinder(dive, current_cylinder); + const cylinder_t *cyl = dive->get_cylinder(current_cylinder); if (!cyl->start.mbar) return true; @@ -625,25 +578,23 @@ static int wait_until(struct deco_state *ds, struct dive *dive, int clock, int m return wait_until(ds, dive, clock, min, leap / 2, stepsize, depth, target_depth, avg_depth, bottom_time, gasmix, po2, surface_pressure, divemode); } -static void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_depth) +static void average_max_depth(const struct diveplan &dive, int *avg_depth, int *max_depth) { int integral = 0; int last_time = 0; int last_depth = 0; - struct divedatapoint *dp = dive->dp; *max_depth = 0; - while (dp) { - if (dp->time) { + for (auto &dp: dive.dp) { + if (dp.time) { /* Ignore gas indication samples */ - integral += (dp->depth.mm + last_depth) * (dp->time - last_time) / 2; - last_time = dp->time; - last_depth = dp->depth.mm; - if (dp->depth.mm > *max_depth) - *max_depth = dp->depth.mm; + integral += (dp.depth.mm + last_depth) * (dp.time - last_time) / 2; + last_time = dp.time; + last_depth = dp.depth.mm; + if (dp.depth.mm > *max_depth) + *max_depth = dp.depth.mm; } - dp = dp->next; } if (last_time) *avg_depth = integral / last_time; @@ -651,7 +602,7 @@ static void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_de *avg_depth = *max_depth = 0; } -bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, int dcNr, int timestep, struct decostop *decostoptable, deco_state_cache &cache, bool is_planner, bool show_disclaimer) +std::vector plan(struct deco_state *ds, struct diveplan &diveplan, struct dive *dive, int dcNr, int timestep, deco_state_cache &cache, bool is_planner, bool show_disclaimer) { int bottom_depth; @@ -661,7 +612,6 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i int bottom_time; int previous_deco_time; deco_state_cache bottom_cache; - struct sample *sample; int po2; int transitiontime, gi; int current_cylinder, stop_cylinder; @@ -680,33 +630,29 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i bool o2break_next = false; int break_cylinder = -1, breakfrom_cylinder = 0; bool last_segment_min_switch = false; - bool error = false; - bool decodive = false; + planner_error_t error = PLAN_OK; int first_stop_depth = 0; int laststoptime = timestep; bool o2breaking = false; - int decostopcounter = 0; - struct divecomputer *dc = get_dive_dc(dive, dcNr); + struct divecomputer *dc = dive->get_dc(dcNr); enum divemode_t divemode = dc->divemode; - set_gf(diveplan->gflow, diveplan->gfhigh); - set_vpmb_conservatism(diveplan->vpmb_conservatism); + set_gf(diveplan.gflow, diveplan.gfhigh); + set_vpmb_conservatism(diveplan.vpmb_conservatism); - if (!diveplan->surface_pressure) { + if (diveplan.surface_pressure.mbar == 0) { // Lets use dive's surface pressure in planner, if have one... - if (dc->surface_pressure.mbar) { // First from DC... - diveplan->surface_pressure = dc->surface_pressure.mbar; - } - else if (dive->surface_pressure.mbar) { // After from user... - diveplan->surface_pressure = dive->surface_pressure.mbar; - } - else { - diveplan->surface_pressure = SURFACE_PRESSURE; + if (dc->surface_pressure.mbar != 0) { // First from DC... + diveplan.surface_pressure = dc->surface_pressure; + } else if (dive->surface_pressure.mbar != 0) { // After from user... + diveplan.surface_pressure = dive->surface_pressure; + } else { + diveplan.surface_pressure = 1_atm; } } clear_deco(ds, dive->surface_pressure.mbar / 1000.0, true); - ds->max_bottom_ceiling_pressure.mbar = ds->first_ceiling_pressure.mbar = 0; + ds->max_bottom_ceiling_pressure = ds->first_ceiling_pressure = 0_bar; create_dive_from_plan(diveplan, dive, dc, is_planner); // Do we want deco stop array in metres or feet? @@ -728,20 +674,19 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i *(decostoplevels + 1) = M_OR_FT(3,10); /* Let's start at the last 'sample', i.e. the last manually entered waypoint. */ - sample = &dc->sample[dc->samples - 1]; + const struct sample &sample = dc->samples.back(); /* Keep time during the ascend */ - bottom_time = clock = previous_point_time = dc->sample[dc->samples - 1].time.seconds; + bottom_time = clock = previous_point_time = sample.time.seconds; - current_cylinder = get_cylinderid_at_time(dive, dc, sample->time); + current_cylinder = get_cylinderid_at_time(dive, dc, sample.time); // Find the divemode at the end of the dive - const struct event *ev = NULL; - divemode = UNDEF_COMP_TYPE; - divemode = get_current_divemode(dc, bottom_time, &ev, &divemode); - gas = get_cylinder(dive, current_cylinder)->gasmix; + divemode_loop loop(*dc); + divemode = loop.at(bottom_time); + gas = dive->get_cylinder(current_cylinder)->gasmix; - po2 = sample->setpoint.mbar; - depth = dc->sample[dc->samples - 1].depth.mm; + po2 = sample.setpoint.mbar; + depth = sample.depth.mm; average_max_depth(diveplan, &avg_depth, &max_depth); last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); @@ -753,18 +698,22 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i transitiontime = lrint(depth / (double)prefs.ascratelast6m); plan_add_segment(diveplan, transitiontime, 0, current_cylinder, po2, false, divemode); create_dive_from_plan(diveplan, dive, dc, is_planner); - return false; + return {}; } #if DEBUG_PLAN & 4 - printf("gas %s\n", gasname(&gas)); + printf("gas %s\n", gas.name().c_str()); printf("depth %5.2lfm \n", depth / 1000.0); printf("current_cylinder %i\n", current_cylinder); #endif /* Find the gases available for deco */ - std::vector gaschanges = analyze_gaslist(diveplan, dive, depth, &best_first_ascend_cylinder, divemode == CCR && !prefs.dobailout); + bool inappropriate_cylinder_use = false; + std::vector gaschanges = analyze_gaslist(diveplan, dive, dcNr, depth, &best_first_ascend_cylinder, divemode == CCR && !prefs.dobailout, inappropriate_cylinder_use); + if (inappropriate_cylinder_use) { + error = PLAN_ERROR_INAPPROPRIATE_GAS; + } /* Find the first potential decostopdepth above current depth */ for (stopidx = 0; stopidx < decostoplevelcount; stopidx++) @@ -779,7 +728,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i gi = static_cast(gaschanges.size()) - 1; /* Set tissue tolerance and initial vpmb gradient at start of ascent phase */ - diveplan->surface_interval = tissue_at_end(ds, dive, dc, cache); + diveplan.surface_interval = tissue_at_end(ds, dive, dc, cache); nuclear_regeneration(ds, clock); vpmb_start_gradient(ds); if (decoMode(true) == RECREATIONAL) { @@ -787,20 +736,20 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i track_ascent_gas(depth, dive, current_cylinder, avg_depth, bottom_time, safety_stop, divemode); // How long can we stay at the current depth and still directly ascent to the surface? do { - add_segment(ds, depth_to_bar(depth, dive), - get_cylinder(dive, current_cylinder)->gasmix, + add_segment(ds, dive->depth_to_bar(depth), + dive->get_cylinder(current_cylinder)->gasmix, timestep, po2, divemode, prefs.bottomsac, true); - update_cylinder_pressure(dive, depth, depth, timestep, prefs.bottomsac, get_cylinder(dive, current_cylinder), false, divemode); + update_cylinder_pressure(dive, depth, depth, timestep, prefs.bottomsac, dive->get_cylinder(current_cylinder), false, divemode); clock += timestep; - } while (trial_ascent(ds, 0, depth, 0, avg_depth, bottom_time, get_cylinder(dive, current_cylinder)->gasmix, - po2, diveplan->surface_pressure / 1000.0, dive, divemode) && + } while (trial_ascent(ds, 0, depth, 0, avg_depth, bottom_time, dive->get_cylinder(current_cylinder)->gasmix, + po2, diveplan.surface_pressure.mbar / 1000.0, dive, divemode) && enough_gas(dive, current_cylinder) && clock < 6 * 3600); // We did stay one timestep too many. // In the best of all worlds, we would roll back also the last add_segment in terms of caching deco state, but // let's ignore that since for the eventual ascent in recreational mode, nobody looks at the ceiling anymore, // so we don't really have to compute the deco state. - update_cylinder_pressure(dive, depth, depth, -timestep, prefs.bottomsac, get_cylinder(dive, current_cylinder), false, divemode); + update_cylinder_pressure(dive, depth, depth, -timestep, prefs.bottomsac, dive->get_cylinder(current_cylinder), false, divemode); clock -= timestep; plan_add_segment(diveplan, clock - previous_point_time, depth, current_cylinder, po2, true, divemode); previous_point_time = clock; @@ -828,15 +777,15 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i } while (depth > 0); plan_add_segment(diveplan, clock - previous_point_time, 0, current_cylinder, po2, false, divemode); create_dive_from_plan(diveplan, dive, dc, is_planner); - add_plan_to_notes(diveplan, dive, show_disclaimer, error); - fixup_dc_duration(dc); + diveplan.add_plan_to_notes(*dive, show_disclaimer, error); + fixup_dc_duration(*dc); - return false; + return {}; } if (best_first_ascend_cylinder != -1 && best_first_ascend_cylinder != current_cylinder) { current_cylinder = best_first_ascend_cylinder; - gas = get_cylinder(dive, current_cylinder)->gasmix; + gas = dive->get_cylinder(current_cylinder)->gasmix; #if DEBUG_PLAN & 16 printf("switch to gas %d (%d/%d) @ %5.2lfm\n", best_first_ascend_cylinder, @@ -850,8 +799,8 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i divemode = OC; po2 = 0; int bailoutsegment = std::max(prefs.min_switch_duration, 60 * prefs.problemsolvingtime); - add_segment(ds, depth_to_bar(depth, dive), - get_cylinder(dive, current_cylinder)->gasmix, + add_segment(ds, dive->depth_to_bar(depth), + dive->get_cylinder(current_cylinder)->gasmix, bailoutsegment, po2, divemode, prefs.bottomsac, true); plan_add_segment(diveplan, bailoutsegment, depth, current_cylinder, po2, false, divemode); bottom_time += bailoutsegment; @@ -865,11 +814,12 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i bottom_stopidx = stopidx; //CVA + std::vector decostoptable; do { - decostopcounter = 0; + decostoptable.clear(); is_final_plan = (decoMode(true) == BUEHLMANN) || (previous_deco_time - ds->deco_time < 10); // CVA time converges if (ds->deco_time != 10000000) - vpmb_next_gradient(ds, ds->deco_time, diveplan->surface_pressure / 1000.0, true); + vpmb_next_gradient(ds, ds->deco_time, diveplan.surface_pressure.mbar / 1000.0, true); previous_deco_time = ds->deco_time; bottom_cache.restore(ds, true); @@ -879,29 +829,28 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i clock = previous_point_time = bottom_time; gas = bottom_gas; stopping = false; - decodive = false; + bool decodive = false; first_stop_depth = 0; stopidx = bottom_stopidx; - ds->first_ceiling_pressure.mbar = depth_to_mbar( - deco_allowed_depth(tissue_tolerance_calc(ds, dive, depth_to_bar(depth, dive), true), - diveplan->surface_pressure / 1000.0, dive, 1), - dive); + ds->first_ceiling_pressure.mbar = dive->depth_to_mbar( + deco_allowed_depth(tissue_tolerance_calc(ds, dive, dive->depth_to_bar(depth), true), + diveplan.surface_pressure.mbar / 1000.0, dive, 1)); if (ds->max_bottom_ceiling_pressure.mbar > ds->first_ceiling_pressure.mbar) ds->first_ceiling_pressure.mbar = ds->max_bottom_ceiling_pressure.mbar; last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); /* Always prefer the best_first_ascend_cylinder if it has the right gasmix. * Otherwise take first cylinder from list with rightgasmix */ - if (best_first_ascend_cylinder != -1 && same_gasmix(gas, get_cylinder(dive, best_first_ascend_cylinder)->gasmix)) + if (best_first_ascend_cylinder != -1 && same_gasmix(gas, dive->get_cylinder(best_first_ascend_cylinder)->gasmix)) current_cylinder = best_first_ascend_cylinder; else current_cylinder = get_gasidx(dive, gas); if (current_cylinder == -1) { - report_error(translate("gettextFromC", "Can't find gas %s"), gasname(gas)); + report_error(translate("gettextFromC", "Can't find gas %s"), gas.name().c_str()); current_cylinder = 0; } reset_regression(ds); - while (1) { + while (error == PLAN_OK) { /* We will break out when we hit the surface */ do { /* Ascend to next stop depth */ @@ -916,8 +865,8 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i if (depth - deltad < stoplevels[stopidx]) deltad = depth - stoplevels[stopidx]; - add_segment(ds, depth_to_bar(depth, dive), - get_cylinder(dive, current_cylinder)->gasmix, + add_segment(ds, dive->depth_to_bar(depth), + dive->get_cylinder(current_cylinder)->gasmix, base_timestep, po2, divemode, prefs.decosac, true); last_segment_min_switch = false; clock += base_timestep; @@ -942,7 +891,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i if (current_cylinder != gaschanges[gi].gasidx) { if (!prefs.switch_at_req_stop || !trial_ascent(ds, 0, depth, stoplevels[stopidx - 1], avg_depth, bottom_time, - get_cylinder(dive, current_cylinder)->gasmix, po2, diveplan->surface_pressure / 1000.0, dive, divemode) || get_o2(get_cylinder(dive, current_cylinder)->gasmix) < 160) { + dive->get_cylinder(current_cylinder)->gasmix, po2, diveplan.surface_pressure.mbar / 1000.0, dive, divemode) || get_o2(dive->get_cylinder(current_cylinder)->gasmix) < 160) { if (is_final_plan) plan_add_segment(diveplan, clock - previous_point_time, depth, current_cylinder, po2, false, divemode); stopping = true; @@ -951,14 +900,14 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i if (divemode == CCR) po2 = setpoint_change(dive, current_cylinder); #if DEBUG_PLAN & 16 - gas = get_cylinder(dive, current_cylinder)->gasmix; + gas = dive->get_cylinder(current_cylinder)->gasmix; printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi].gasidx, (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi].depth / 1000.0); #endif /* Stop for the minimum duration to switch gas unless we switch to o2 */ - if (!last_segment_min_switch && get_o2(get_cylinder(dive, current_cylinder)->gasmix) != 1000) { - add_segment(ds, depth_to_bar(depth, dive), - get_cylinder(dive, current_cylinder)->gasmix, + if (!last_segment_min_switch && get_o2(dive->get_cylinder(current_cylinder)->gasmix) != 1000) { + add_segment(ds, dive->depth_to_bar(depth), + dive->get_cylinder(current_cylinder)->gasmix, prefs.min_switch_duration, po2, divemode, prefs.decosac, true); clock += prefs.min_switch_duration; last_segment_min_switch = true; @@ -978,10 +927,8 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i while (1) { /* Check if ascending to next stop is clear, go back and wait if we hit the ceiling on the way */ if (trial_ascent(ds, 0, depth, stoplevels[stopidx], avg_depth, bottom_time, - get_cylinder(dive, current_cylinder)->gasmix, po2, diveplan->surface_pressure / 1000.0, dive, divemode)) { - decostoptable[decostopcounter].depth = depth; - decostoptable[decostopcounter].time = 0; - decostopcounter++; + dive->get_cylinder(current_cylinder)->gasmix, po2, diveplan.surface_pressure.mbar / 1000.0, dive, divemode)) { + decostoptable.push_back( decostop { depth, 0 }); break; /* We did not hit the ceiling */ } @@ -1007,14 +954,14 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i if (divemode == CCR) po2 = setpoint_change(dive, current_cylinder); #if DEBUG_PLAN & 16 - gas = get_cylinder(dive, current_cylinder)->gasmix; + gas = dive->get_cylinder(current_cylinder)->gasmix; printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi + 1].gasidx, (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi + 1].depth / 1000.0); #endif /* Stop for the minimum duration to switch gas unless we switch to o2 */ - if (!last_segment_min_switch && get_o2(get_cylinder(dive, current_cylinder)->gasmix) != 1000) { - add_segment(ds, depth_to_bar(depth, dive), - get_cylinder(dive, current_cylinder)->gasmix, + if (!last_segment_min_switch && get_o2(dive->get_cylinder(current_cylinder)->gasmix) != 1000) { + add_segment(ds, dive->depth_to_bar(depth), + dive->get_cylinder(current_cylinder)->gasmix, prefs.min_switch_duration, po2, divemode, prefs.decosac, true); clock += prefs.min_switch_duration; } @@ -1022,11 +969,12 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i } int new_clock = wait_until(ds, dive, clock, clock, laststoptime * 2 + 1, timestep, depth, stoplevels[stopidx], avg_depth, - bottom_time, get_cylinder(dive, current_cylinder)->gasmix, po2, diveplan->surface_pressure / 1000.0, divemode); + bottom_time, dive->get_cylinder(current_cylinder)->gasmix, po2, diveplan.surface_pressure.mbar / 1000.0, divemode); laststoptime = new_clock - clock; /* Finish infinite deco */ if (laststoptime >= 48 * 3600 && depth >= 6000) { - error = true; + error = PLAN_ERROR_TIMEOUT; + break; } @@ -1037,12 +985,12 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i * backgas. This could be customized if there were demand. */ if (break_cylinder == -1) { - if (best_first_ascend_cylinder != -1 && get_o2(get_cylinder(dive, best_first_ascend_cylinder)->gasmix) <= 320) + if (best_first_ascend_cylinder != -1 && get_o2(dive->get_cylinder(best_first_ascend_cylinder)->gasmix) <= 320) break_cylinder = best_first_ascend_cylinder; else break_cylinder = 0; } - if (get_o2(get_cylinder(dive, current_cylinder)->gasmix) == 1000) { + if (get_o2(dive->get_cylinder(current_cylinder)->gasmix) == 1000) { if (laststoptime >= 12 * 60) { laststoptime = 12 * 60; new_clock = clock + laststoptime; @@ -1067,12 +1015,10 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i } } } - add_segment(ds, depth_to_bar(depth, dive), get_cylinder(dive, stop_cylinder)->gasmix, + add_segment(ds, dive->depth_to_bar(depth), dive->get_cylinder(stop_cylinder)->gasmix, laststoptime, po2, divemode, prefs.decosac, true); last_segment_min_switch = false; - decostoptable[decostopcounter].depth = depth; - decostoptable[decostopcounter].time = laststoptime; - ++decostopcounter; + decostoptable.push_back(decostop { depth, laststoptime } ); clock += laststoptime; if (!o2breaking) @@ -1091,13 +1037,12 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i * if the ascent rate is slower, which is completely nonsensical. * Assume final ascent takes 20s, which is the time taken to ascend at 9m/min from 3m */ ds->deco_time = clock - bottom_time - (M_OR_FT(3,10) * ( prefs.last_stop ? 2 : 1)) / last_ascend_rate + 20; - } while (!is_final_plan); - decostoptable[decostopcounter].depth = 0; + } while (!is_final_plan && error == PLAN_OK); plan_add_segment(diveplan, clock - previous_point_time, 0, current_cylinder, po2, false, divemode); if (decoMode(true) == VPMB) { - diveplan->eff_gfhigh = lrint(100.0 * regressionb(ds)); - diveplan->eff_gflow = lrint(100.0 * (regressiona(ds) * first_stop_depth + regressionb(ds))); + diveplan.eff_gfhigh = lrint(100.0 * regressionb(ds)); + diveplan.eff_gflow = lrint(100.0 * (regressiona(ds) * first_stop_depth + regressionb(ds))); } if (prefs.surface_segment != 0) { @@ -1106,129 +1051,11 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i // we had a fixed cylinder table: It uses an extra fake cylinder // past the regular cylinder table, which is not visible to the UI. // Fix this as soon as possible! - current_cylinder = dive->cylinders.nr; + current_cylinder = static_cast(dive->cylinders.size()); plan_add_segment(diveplan, prefs.surface_segment, 0, current_cylinder, 0, false, OC); } create_dive_from_plan(diveplan, dive, dc, is_planner); - add_plan_to_notes(diveplan, dive, show_disclaimer, error); - fixup_dc_duration(dc); - - return decodive; -} - -/* - * Get a value with a given number of decimals: - * - get_decimals("10.2", &"10.2", 1) == 102 - * - get_decimals("9", &"9", 1) = 90 - * - get_decimals("1.35", &"1.35", 2) == 135)) - * - * Return negative for errors. - */ -static int get_decimals(const char *begin, const char **endp, const unsigned decimals) -{ - char *end; - int value = strtol(begin, &end, 10); - - if (begin == end) - return -1; - - /* Fraction? We only look at the first digit */ - if (*end == '.') { - unsigned fraction = 0; - for (unsigned i = 0; i < decimals; i++) { - value *= 10; - - end++; - - if (!isdigit(*end)) - return -1; - - fraction = 10 * fraction + (*end - '0'); - } - value += fraction; - - do { - end++; - } while (isdigit(*end)); - } - *endp = end; - return value; -} - -static int get_permille(const char *begin, const char **end) -{ - int value = get_decimals(begin, end, 1); - if (value >= 0) { - /* Allow a percentage sign */ - if (**end == '%') - ++*end; - } - return value; -} - -extern "C" int validate_gas(const char *text, struct gasmix *gas) -{ - int o2, he; - - if (!text) - return 0; - - while (isspace(*text)) - text++; - - if (!*text) - return 0; - - if (!strcasecmp(text, translate("gettextFromC", "air"))) { - o2 = O2_IN_AIR; - he = 0; - text += strlen(translate("gettextFromC", "air")); - } else if (!strcasecmp(text, translate("gettextFromC", "oxygen"))) { - o2 = 1000; - he = 0; - text += strlen(translate("gettextFromC", "oxygen")); - } else if (!strncasecmp(text, translate("gettextFromC", "ean"), 3)) { - o2 = get_permille(text + 3, &text); - he = 0; - } else { - o2 = get_permille(text, &text); - he = 0; - if (*text == '/') - he = get_permille(text + 1, &text); - } - - /* We don't want any extra crud */ - while (isspace(*text)) - text++; - if (*text) - return 0; - - /* Validate the gas mix */ - if (*text || o2 < 1 || o2 > 1000 || he < 0 || o2 + he > 1000) - return 0; - - /* Let it rip */ - gas->o2.permille = o2; - gas->he.permille = he; - return 1; -} - -extern "C" int validate_po2(const char *text, int *mbar_po2) -{ - int po2; - - if (!text) - return 0; - - po2 = get_decimals(text, &text, 2); - if (po2 < 0) - return 0; - - while (isspace(*text)) - text++; - if (*text) - return 0; - - *mbar_po2 = po2 * 10; - return 1; + diveplan.add_plan_to_notes(*dive, show_disclaimer, error); + fixup_dc_duration(*dc); + return decostoptable; } diff --git a/core/planner.h b/core/planner.h index dc145604d..f770b6814 100644 --- a/core/planner.h +++ b/core/planner.h @@ -4,49 +4,63 @@ #include "units.h" #include "divemode.h" +#include +#include /* this should be converted to use our types */ struct divedatapoint { - int time; + int time = 0; depth_t depth; - int cylinderid; + int cylinderid = 0; pressure_t minimum_gas; - int setpoint; - bool entered; - struct divedatapoint *next; - enum divemode_t divemode; + int setpoint = 0; + bool entered = false; + enum divemode_t divemode = OC; + + divedatapoint() = default; + divedatapoint(const divedatapoint &) = default; + divedatapoint(divedatapoint &&) = default; + divedatapoint &operator=(const divedatapoint &) = default; + divedatapoint(int time_incr, int depth, int cylinderid, int po2, bool entered); }; +typedef enum { + PLAN_OK, + PLAN_ERROR_TIMEOUT, + PLAN_ERROR_INAPPROPRIATE_GAS, +} planner_error_t; + struct diveplan { - timestamp_t when; - int surface_pressure; /* mbar */ - int bottomsac; /* ml/min */ - int decosac; /* ml/min */ - int salinity; - short gflow; - short gfhigh; - short vpmb_conservatism; - struct divedatapoint *dp; - int eff_gflow, eff_gfhigh; - int surface_interval; + diveplan(); + ~diveplan(); + diveplan(const diveplan &) = default; + diveplan(diveplan &&) = default; + diveplan &operator=(const diveplan &) = default; + diveplan &operator=(diveplan &&) = default; + + timestamp_t when = 0; + pressure_t surface_pressure; + int bottomsac = 0; /* ml/min */ + int decosac = 0; /* ml/min */ + int salinity = 0; + short gflow = 0; + short gfhigh = 0; + short vpmb_conservatism = 0; + std::vector dp; + int eff_gflow = 0, eff_gfhigh = 0; + int surface_interval = 0; + + bool is_empty() const; + void add_plan_to_notes(struct dive &dive, bool show_disclaimer, planner_error_t error); + int duration() const; }; struct deco_state_cache; -#ifdef __cplusplus -extern "C" { -#endif - -extern int validate_gas(const char *text, struct gasmix *gas); -extern int validate_po2(const char *text, int *mbar_po2); extern int get_cylinderid_at_time(struct dive *dive, struct divecomputer *dc, duration_t time); -extern bool diveplan_empty(struct diveplan *diveplan); -extern void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, bool error); extern const char *get_planner_disclaimer(); -extern void free_dps(struct diveplan *diveplan); - -struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, int cylinderid, int po2, bool entered, enum divemode_t divemode); +void plan_add_segment(struct diveplan &diveplan, int duration, int depth, int cylinderid, int po2, bool entered, enum divemode_t divemode); #if DEBUG_PLAN void dump_plan(struct diveplan *diveplan); #endif @@ -55,11 +69,6 @@ struct decostop { int time; }; -#ifdef __cplusplus -} - -#include extern std::string get_planner_disclaimer_formatted(); -extern bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, int dcNr, int timestep, struct decostop *decostoptable, deco_state_cache &cache, bool is_planner, bool show_disclaimer); -#endif +extern std::vector plan(struct deco_state *ds, struct diveplan &diveplan, struct dive *dive, int dcNr, int timestep, deco_state_cache &cache, bool is_planner, bool show_disclaimer); #endif // PLANNER_H diff --git a/core/plannernotes.cpp b/core/plannernotes.cpp index 72ee910f0..4bbb1d207 100644 --- a/core/plannernotes.cpp +++ b/core/plannernotes.cpp @@ -10,11 +10,14 @@ #include #include #include -#include "dive.h" #include "deco.h" -#include "units.h" +#include "dive.h" #include "divelist.h" +#include "divelog.h" +#include "event.h" +#include "units.h" #include "planner.h" +#include "range.h" #include "gettext.h" #include "libdivecomputer/parser.h" #include "qthelper.h" @@ -22,17 +25,15 @@ #include "subsurface-string.h" #include "version.h" -static int diveplan_duration(struct diveplan *diveplan) +int diveplan::duration() const { - struct divedatapoint *dp = diveplan->dp; int duration = 0; int lastdepth = 0; - while(dp) { - if (dp->time > duration && (dp->depth.mm > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD)) { - duration = dp->time; - lastdepth = dp->depth.mm; + for (auto &dp: this->dp) { + if (dp.time > duration && (dp.depth.mm > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD)) { + duration = dp.time; + lastdepth = dp.depth.mm; } - dp = dp->next; } return (duration + 30) / 60; } @@ -61,7 +62,7 @@ static std::string icd_entry(struct icd_data *icdvalues, bool printheader, int t b += casprintf_loc( "%3d%s" "%s➙", - (time_seconds + 30) / 60, translate("gettextFromC", "min"), gasname(gas_from)); + (time_seconds + 30) / 60, translate("gettextFromC", "min"), gas_from.name().c_str()); b += casprintf_loc( "%s%+5.1f%%" "%+5.1f%%" @@ -69,7 +70,7 @@ static std::string icd_entry(struct icd_data *icdvalues, bool printheader, int t "%+5.2f%s" "%+5.2f%s" "%+5.2f%s", - gasname(gas_to), icdvalues->dHe / 10.0, + gas_to.name().c_str(), icdvalues->dHe / 10.0, ((5 * icdvalues->dN2) > -icdvalues->dHe) ? "red" : "#383838", icdvalues->dN2 / 10.0 , 0.2 * (-icdvalues->dHe / 10.0), ambientpressure_mbar * icdvalues->dHe / 1e6f, translate("gettextFromC", "bar"), ((5 * icdvalues->dN2) > -icdvalues->dHe) ? "red" : "#383838", ambientpressure_mbar * icdvalues->dN2 / 1e6f, translate("gettextFromC", "bar"), @@ -77,7 +78,7 @@ static std::string icd_entry(struct icd_data *icdvalues, bool printheader, int t return b; } -extern "C" const char *get_planner_disclaimer() +const char *get_planner_disclaimer() { return translate("gettextFromC", "DISCLAIMER / WARNING: THIS IMPLEMENTATION OF THE %s " "ALGORITHM AND A DIVE PLANNER IMPLEMENTATION BASED ON THAT HAS " @@ -93,39 +94,51 @@ extern std::string get_planner_disclaimer_formatted() return format_string_std(get_planner_disclaimer(), deco); } -extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, bool error) +void diveplan::add_plan_to_notes(struct dive &dive, bool show_disclaimer, planner_error_t error) { std::string buf; std::string icdbuf; const char *segmentsymbol; int lastdepth = 0, lasttime = 0, lastsetpoint = -1, newdepth = 0, lastprintdepth = 0, lastprintsetpoint = -1; struct gasmix lastprintgasmix = gasmix_invalid; - struct divedatapoint *dp = diveplan->dp; bool plan_verbatim = prefs.verbatim_plan; bool plan_display_runtime = prefs.display_runtime; bool plan_display_duration = prefs.display_duration; bool plan_display_transitions = prefs.display_transitions; - bool gaschange_after = !plan_verbatim; - bool gaschange_before; bool rebreatherchange_after = !plan_verbatim; bool rebreatherchange_before; enum divemode_t lastdivemode = UNDEF_COMP_TYPE; bool lastentered = true; bool icdwarning = false, icdtableheader = true; - struct divedatapoint *nextdp = NULL; - struct divedatapoint *lastbottomdp = NULL; + struct divedatapoint *lastbottomdp = nullptr; struct icd_data icdvalues; - if (!dp) + if (dp.empty()) return; - if (error) { + if (error != PLAN_OK) { + const char *message; + switch (error) { + case PLAN_ERROR_TIMEOUT: + message = translate("gettextFromC", "Decompression calculation aborted due to excessive time"); + + break; + case PLAN_ERROR_INAPPROPRIATE_GAS: + message = translate("gettextFromC", "One or more tanks with a tank use type inappropriate for the selected dive mode are included in the dive plan. " + "Please change them to appropriate tanks to enable the generation of a dive plan."); + + break; + default: + message = translate("gettextFromC", "An error occurred during dive plan generation"); + + break; + } + buf += format_string_std("%s %s
", - translate("gettextFromC", "Warning:"), - translate("gettextFromC", "Decompression calculation aborted due to excessive time")); - // TODO: avoid copy - free(dive->notes); - dive->notes = strdup(buf.c_str()); + translate("gettextFromC", "Warning:"), message); + + dive.notes = std::move(buf); + return; } @@ -136,16 +149,14 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, } buf += "
\n"; - if (diveplan->surface_interval < 0) { + if (surface_interval < 0) { buf += format_string_std("%s (%s) %s", translate("gettextFromC", "Subsurface"), subsurface_canonical_version(), translate("gettextFromC", "dive plan (overlapping dives detected)")); - // TODO: avoid copy - free(dive->notes); - dive->notes = strdup(buf.c_str()); + dive.notes = std::move(buf); return; - } else if (diveplan->surface_interval >= 48 * 60 *60) { + } else if (surface_interval >= 48 * 60 *60) { buf += format_string_std("%s (%s) %s %s", translate("gettextFromC", "Subsurface"), subsurface_canonical_version(), @@ -156,7 +167,7 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, translate("gettextFromC", "Subsurface"), subsurface_canonical_version(), translate("gettextFromC", "dive plan (surface interval "), - FRACTION_TUPLE(diveplan->surface_interval / 60, 60), + FRACTION_TUPLE(surface_interval / 60, 60), translate("gettextFromC", "created on"), get_current_date().c_str()); } @@ -164,10 +175,10 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, if (prefs.display_variations && decoMode(true) != RECREATIONAL) buf += casprintf_loc(translate("gettextFromC", "Runtime: %dmin%s"), - diveplan_duration(diveplan), "VARIATIONS"); + duration(), "VARIATIONS"); else buf += casprintf_loc(translate("gettextFromC", "Runtime: %dmin%s"), - diveplan_duration(diveplan), ""); + duration(), ""); buf += "
\n
\n"; if (!plan_verbatim) { @@ -180,30 +191,31 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, translate("gettextFromC", "gas")); } - do { + for (auto dp = this->dp.begin(); dp != this->dp.end(); ++dp) { + auto nextdp = std::next(dp); struct gasmix gasmix, newgasmix = {}; const char *depth_unit; double depthvalue; int decimals; bool isascent = (dp->depth.mm < lastdepth); - nextdp = dp->next; if (dp->time == 0) continue; - gasmix = get_cylinder(dive, dp->cylinderid)->gasmix; + gasmix = dive.get_cylinder(dp->cylinderid)->gasmix; depthvalue = get_depth_units(dp->depth.mm, &decimals, &depth_unit); /* analyze the dive points ahead */ - while (nextdp && nextdp->time == 0) - nextdp = nextdp->next; - if (nextdp) - newgasmix = get_cylinder(dive, nextdp->cylinderid)->gasmix; - gaschange_after = (nextdp && (gasmix_distance(gasmix, newgasmix))); - gaschange_before = (gasmix_distance(lastprintgasmix, gasmix)); - rebreatherchange_after = (nextdp && (dp->setpoint != nextdp->setpoint || dp->divemode != nextdp->divemode)); + while (nextdp != this->dp.end() && nextdp->time == 0) + ++nextdp; + bool atend = nextdp == this->dp.end(); + if (!atend) + newgasmix = dive.get_cylinder(nextdp->cylinderid)->gasmix; + bool gaschange_after = (!atend && (gasmix_distance(gasmix, newgasmix))); + bool gaschange_before = (gasmix_distance(lastprintgasmix, gasmix)); + rebreatherchange_after = (!atend && (dp->setpoint != nextdp->setpoint || dp->divemode != nextdp->divemode)); rebreatherchange_before = lastprintsetpoint != dp->setpoint || lastdivemode != dp->divemode; /* do we want to skip this leg as it is devoid of anything useful? */ if (!dp->entered && - nextdp && + !atend && dp->depth.mm != lastdepth && nextdp->depth.mm != dp->depth.mm && !gaschange_before && @@ -212,14 +224,14 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, !rebreatherchange_after) continue; // Ignore final surface segment for notes - if (lastdepth == 0 && dp->depth.mm == 0 && !dp->next) + if (lastdepth == 0 && dp->depth.mm == 0 && atend) continue; - if ((dp->time - lasttime < 10 && lastdepth == dp->depth.mm) && !(gaschange_after && dp->next && dp->depth.mm != dp->next->depth.mm)) + if ((dp->time - lasttime < 10 && lastdepth == dp->depth.mm) && !(gaschange_after && !atend && dp->depth.mm != nextdp->depth.mm)) continue; /* Store pointer to last entered datapoint for minimum gas calculation */ - if (dp->entered && !nextdp->entered) - lastbottomdp = dp; + if (dp->entered && !atend && !nextdp->entered) + lastbottomdp = &*dp; if (plan_verbatim) { /* When displaying a verbatim plan, we output a waypoint for every gas change. @@ -229,14 +241,14 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, * non-verbatim plan. */ if (dp->depth.mm != lastprintdepth) { - if (plan_display_transitions || dp->entered || !dp->next || (gaschange_after && dp->next && dp->depth.mm != nextdp->depth.mm)) { + if (plan_display_transitions || dp->entered || atend || (gaschange_after && !atend && dp->depth.mm != nextdp->depth.mm)) { if (dp->setpoint) { buf += casprintf_loc(translate("gettextFromC", "%s to %.*f %s in %d:%02d min - runtime %d:%02u on %s (SP = %.1fbar)"), dp->depth.mm < lastprintdepth ? translate("gettextFromC", "Ascend") : translate("gettextFromC", "Descend"), decimals, depthvalue, depth_unit, FRACTION_TUPLE(dp->time - lasttime, 60), FRACTION_TUPLE(dp->time, 60), - gasname(gasmix), + gasmix.name().c_str(), (double) dp->setpoint / 1000.0); } else { buf += casprintf_loc(translate("gettextFromC", "%s to %.*f %s in %d:%02d min - runtime %d:%02u on %s"), @@ -244,7 +256,7 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, decimals, depthvalue, depth_unit, FRACTION_TUPLE(dp->time - lasttime, 60), FRACTION_TUPLE(dp->time, 60), - gasname(gasmix)); + gasmix.name().c_str()); } buf += "
\n"; @@ -252,20 +264,20 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, newdepth = dp->depth.mm; lasttime = dp->time; } else { - if ((nextdp && dp->depth.mm != nextdp->depth.mm) || gaschange_after) { + if ((!atend && dp->depth.mm != nextdp->depth.mm) || gaschange_after) { if (dp->setpoint) { buf += casprintf_loc(translate("gettextFromC", "Stay at %.*f %s for %d:%02d min - runtime %d:%02u on %s (SP = %.1fbar CCR)"), decimals, depthvalue, depth_unit, FRACTION_TUPLE(dp->time - lasttime, 60), FRACTION_TUPLE(dp->time, 60), - gasname(gasmix), + gasmix.name().c_str(), (double) dp->setpoint / 1000.0); } else { buf += casprintf_loc(translate("gettextFromC", "Stay at %.*f %s for %d:%02d min - runtime %d:%02u on %s %s"), decimals, depthvalue, depth_unit, FRACTION_TUPLE(dp->time - lasttime, 60), FRACTION_TUPLE(dp->time, 60), - gasname(gasmix), + gasmix.name().c_str(), translate("gettextFromC", divemode_text_ui[dp->divemode])); } buf += "
\n"; @@ -290,12 +302,12 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, * used for a calculated ascent, there is a subsequent gas change before the first deco stop, and zero * time has been allowed for a gas switch. */ - if (plan_display_transitions || dp->entered || !dp->next || - (nextdp && dp->depth.mm != nextdp->depth.mm) || - (!isascent && (gaschange_before || rebreatherchange_before) && nextdp && dp->depth.mm != nextdp->depth.mm) || + if (plan_display_transitions || dp->entered || atend || + (!atend && dp->depth.mm != nextdp->depth.mm) || + (!isascent && (gaschange_before || rebreatherchange_before) && !atend && dp->depth.mm != nextdp->depth.mm) || ((gaschange_after || rebreatherchange_after) && lastentered) || ((gaschange_after || rebreatherchange_after)&& !isascent) || - (isascent && (gaschange_after || rebreatherchange_after) && nextdp && dp->depth.mm != nextdp->depth.mm ) || - (lastentered && !dp->entered && dp->next->depth.mm == dp->depth.mm)) { + (isascent && (gaschange_after || rebreatherchange_after) && !atend && dp->depth.mm != nextdp->depth.mm ) || + (lastentered && !dp->entered && nextdp->depth.mm == dp->depth.mm)) { // Print a symbol to indicate whether segment is an ascent, descent, constant depth (user entered) or deco stop if (isascent) segmentsymbol = "➚"; // up-right arrow for ascent @@ -322,19 +334,19 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, /* Normally a gas change is displayed on the stopping segment, so only display a gas change at the end of * an ascent segment if it is not followed by a stop */ - if (isascent && gaschange_after && dp->next && nextdp && nextdp->entered) { + if (isascent && gaschange_after && !atend && nextdp->entered) { if (nextdp->setpoint) { temp = casprintf_loc(translate("gettextFromC", "(SP = %.1fbar CCR)"), nextdp->setpoint / 1000.0); buf += format_string_std("%s %s", - gasname(newgasmix), temp.c_str()); + newgasmix.name().c_str(), temp.c_str()); } else { buf += format_string_std("%s %s", - gasname(newgasmix), dp->divemode == UNDEF_COMP_TYPE || dp->divemode == nextdp->divemode ? "" : translate("gettextFromC", divemode_text_ui[nextdp->divemode])); + newgasmix.name().c_str(), dp->divemode == UNDEF_COMP_TYPE || dp->divemode == nextdp->divemode ? "" : translate("gettextFromC", divemode_text_ui[nextdp->divemode])); if (isascent && (get_he(lastprintgasmix) > 0)) { // For a trimix gas change on ascent, save ICD info if previous cylinder had helium if (isobaric_counterdiffusion(lastprintgasmix, newgasmix, &icdvalues)) // Do icd calulations icdwarning = true; if (icdvalues.dN2 > 0) { // If the gas change involved helium as well as an increase in nitrogen.. - icdbuf += icd_entry(&icdvalues, icdtableheader, dp->time, depth_to_mbar(dp->depth.mm, dive), lastprintgasmix, newgasmix); // .. then print calculations to buffer. + icdbuf += icd_entry(&icdvalues, icdtableheader, dp->time, dive.depth_to_mbar(dp->depth.mm), lastprintgasmix, newgasmix); // .. then print calculations to buffer. icdtableheader = false; } } @@ -347,15 +359,15 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, // If a new gas has been used for this segment, now is the time to show it if (dp->setpoint) { temp = casprintf_loc(translate("gettextFromC", "(SP = %.1fbar CCR)"), (double) dp->setpoint / 1000.0); - buf += format_string_std("%s %s", gasname(gasmix), temp.c_str()); + buf += format_string_std("%s %s", gasmix.name().c_str(), temp.c_str()); } else { - buf += format_string_std("%s %s", gasname(gasmix), + buf += format_string_std("%s %s", gasmix.name().c_str(), lastdivemode == UNDEF_COMP_TYPE || lastdivemode == dp->divemode ? "" : translate("gettextFromC", divemode_text_ui[dp->divemode])); if (get_he(lastprintgasmix) > 0) { // For a trimix gas change, save ICD info if previous cylinder had helium if (isobaric_counterdiffusion(lastprintgasmix, gasmix, &icdvalues)) // Do icd calculations icdwarning = true; if (icdvalues.dN2 > 0) { // If the gas change involved helium as well as an increase in nitrogen.. - icdbuf += icd_entry(&icdvalues, icdtableheader, lasttime, depth_to_mbar(dp->depth.mm, dive), lastprintgasmix, gasmix); // .. then print data to buffer. + icdbuf += icd_entry(&icdvalues, icdtableheader, lasttime, dive.depth_to_mbar(dp->depth.mm), lastprintgasmix, gasmix); // .. then print data to buffer. icdtableheader = false; } } @@ -378,15 +390,15 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, // gas switch at this waypoint for verbatim if (plan_verbatim) { if (lastsetpoint >= 0) { - if (nextdp && nextdp->setpoint) { - buf += casprintf_loc(translate("gettextFromC", "Switch gas to %s (SP = %.1fbar)"), gasname(newgasmix), (double) nextdp->setpoint / 1000.0); + if (!atend && nextdp->setpoint) { + buf += casprintf_loc(translate("gettextFromC", "Switch gas to %s (SP = %.1fbar)"), newgasmix.name().c_str(), (double) nextdp->setpoint / 1000.0); } else { - buf += format_string_std(translate("gettextFromC", "Switch gas to %s"), gasname(newgasmix)); + buf += format_string_std(translate("gettextFromC", "Switch gas to %s"), newgasmix.name().c_str()); if ((isascent) && (get_he(lastprintgasmix) > 0)) { // For a trimix gas change on ascent: if (isobaric_counterdiffusion(lastprintgasmix, newgasmix, &icdvalues)) // Do icd calculations icdwarning = true; if (icdvalues.dN2 > 0) { // If the gas change involved helium as well as an increase in nitrogen.. - icdbuf += icd_entry(&icdvalues, icdtableheader, dp->time, depth_to_mbar(dp->depth.mm, dive), lastprintgasmix, newgasmix); // ... then print data to buffer. + icdbuf += icd_entry(&icdvalues, icdtableheader, dp->time, dive.depth_to_mbar(dp->depth.mm), lastprintgasmix, newgasmix); // ... then print data to buffer. icdtableheader = false; } } @@ -394,7 +406,6 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, buf += "
\n"; } lastprintgasmix = newgasmix; - gaschange_after = false; gasmix = newgasmix; } } @@ -402,39 +413,39 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, lastdepth = dp->depth.mm; lastsetpoint = dp->setpoint; lastentered = dp->entered; - } while ((dp = nextdp) != NULL); + } if (!plan_verbatim) buf += "\n\n
\n"; /* Print the CNS and OTU next.*/ - dive->cns = 0; - dive->maxcns = 0; - update_cylinder_related_info(dive); - buf += casprintf_loc("
\n%s: %i%%", translate("gettextFromC", "CNS"), dive->cns); - buf += casprintf_loc("
\n%s: %i
\n
\n", translate("gettextFromC", "OTU"), dive->otu); + dive.cns = 0; + dive.maxcns = 0; + divelog.dives.update_cylinder_related_info(dive); + buf += casprintf_loc("
\n%s: %i%%", translate("gettextFromC", "CNS"), dive.cns); + buf += casprintf_loc("
\n%s: %i
\n
\n", translate("gettextFromC", "OTU"), dive.otu); /* Print the settings for the diveplan next. */ buf += "
\n"; if (decoMode(true) == BUEHLMANN) { - buf += casprintf_loc(translate("gettextFromC", "Deco model: Bühlmann ZHL-16C with GFLow = %d%% and GFHigh = %d%%"), diveplan->gflow, diveplan->gfhigh); + buf += casprintf_loc(translate("gettextFromC", "Deco model: Bühlmann ZHL-16C with GFLow = %d%% and GFHigh = %d%%"), gflow, gfhigh); } else if (decoMode(true) == VPMB) { - if (diveplan->vpmb_conservatism == 0) + if (vpmb_conservatism == 0) buf += translate("gettextFromC", "Deco model: VPM-B at nominal conservatism"); else - buf += casprintf_loc(translate("gettextFromC", "Deco model: VPM-B at +%d conservatism"), diveplan->vpmb_conservatism); - if (diveplan->eff_gflow) - buf += casprintf_loc( translate("gettextFromC", ", effective GF=%d/%d"), diveplan->eff_gflow, diveplan->eff_gfhigh); + buf += casprintf_loc(translate("gettextFromC", "Deco model: VPM-B at +%d conservatism"), vpmb_conservatism); + if (eff_gflow) + buf += casprintf_loc( translate("gettextFromC", ", effective GF=%d/%d"), eff_gflow, eff_gfhigh); } else if (decoMode(true) == RECREATIONAL) { buf += casprintf_loc(translate("gettextFromC", "Deco model: Recreational mode based on Bühlmann ZHL-16B with GFLow = %d%% and GFHigh = %d%%"), - diveplan->gflow, diveplan->gfhigh); + gflow, gfhigh); } buf += "
\n"; { const char *depth_unit; - int altitude = (int) get_depth_units((int) (pressure_to_altitude(diveplan->surface_pressure)), NULL, &depth_unit); + int altitude = (int) get_depth_units((int) (pressure_to_altitude(surface_pressure)), NULL, &depth_unit); - buf += casprintf_loc(translate("gettextFromC", "ATM pressure: %dmbar (%d%s)
\n
\n"), diveplan->surface_pressure, altitude, depth_unit); + buf += casprintf_loc(translate("gettextFromC", "ATM pressure: %dmbar (%d%s)
\n\n"), surface_pressure, altitude, depth_unit); } /* Get SAC values and units for printing it in gas consumption */ @@ -451,7 +462,7 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, /* Print the gas consumption next.*/ std::string temp; - if (dive->dc.divemode == CCR) + if (dive.dcs[0].divemode == CCR) temp = translate("gettextFromC", "Gas consumption (CCR legs excluded):"); else temp = casprintf_loc("%s %.*f|%.*f%s/min):", translate("gettextFromC", "Gas consumption (based on SAC"), @@ -460,34 +471,33 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, } /* Print gas consumption: This loop covers all cylinders */ - for (int gasidx = 0; gasidx < dive->cylinders.nr; gasidx++) { + for (auto [gasidx, cyl]: enumerated_range(dive.cylinders)) { double volume, pressure, deco_volume, deco_pressure, mingas_volume, mingas_pressure, mingas_d_pressure, mingas_depth; const char *unit, *pressure_unit, *depth_unit; std::string temp; std::string warning; std::string mingas; - cylinder_t *cyl = get_cylinder(dive, gasidx); - if (cyl->cylinder_use == NOT_USED) + if (cyl.cylinder_use == NOT_USED) continue; - volume = get_volume_units(cyl->gas_used.mliter, NULL, &unit); - deco_volume = get_volume_units(cyl->deco_gas_used.mliter, NULL, &unit); - if (cyl->type.size.mliter) { - int remaining_gas = lrint((double)cyl->end.mbar * cyl->type.size.mliter / 1000.0 / gas_compressibility_factor(cyl->gasmix, cyl->end.mbar / 1000.0)); - double deco_pressure_mbar = isothermal_pressure(cyl->gasmix, 1.0, remaining_gas + cyl->deco_gas_used.mliter, - cyl->type.size.mliter) * 1000 - cyl->end.mbar; + volume = get_volume_units(cyl.gas_used.mliter, NULL, &unit); + deco_volume = get_volume_units(cyl.deco_gas_used.mliter, NULL, &unit); + if (cyl.type.size.mliter) { + int remaining_gas = lrint((double)cyl.end.mbar * cyl.type.size.mliter / 1000.0 / gas_compressibility_factor(cyl.gasmix, cyl.end.mbar / 1000.0)); + double deco_pressure_mbar = isothermal_pressure(cyl.gasmix, 1.0, remaining_gas + cyl.deco_gas_used.mliter, + cyl.type.size.mliter) * 1000 - cyl.end.mbar; deco_pressure = get_pressure_units(lrint(deco_pressure_mbar), &pressure_unit); - pressure = get_pressure_units(cyl->start.mbar - cyl->end.mbar, &pressure_unit); + pressure = get_pressure_units(cyl.start.mbar - cyl.end.mbar, &pressure_unit); /* Warn if the plan uses more gas than is available in a cylinder * This only works if we have working pressure for the cylinder * 10bar is a made up number - but it seemed silly to pretend you could breathe cylinder down to 0 */ - if (cyl->end.mbar < 10000) + if (cyl.end.mbar < 10000) warning = format_string_std("
\n — %s %s", translate("gettextFromC", "Warning:"), translate("gettextFromC", "this is more gas than available in the specified cylinder!")); else - if (cyl->end.mbar / 1000.0 * cyl->type.size.mliter / gas_compressibility_factor(cyl->gasmix, cyl->end.mbar / 1000.0) - < cyl->deco_gas_used.mliter) + if (cyl.end.mbar / 1000.0 * cyl.type.size.mliter / gas_compressibility_factor(cyl.gasmix, cyl.end.mbar / 1000.0) + < cyl.deco_gas_used.mliter) warning = format_string_std("
\n — %s %s", translate("gettextFromC", "Warning:"), translate("gettextFromC", "not enough reserve for gas sharing on ascent!")); @@ -496,22 +506,22 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, /* not for recreational mode and if no other warning was set before. */ else if (lastbottomdp && gasidx == lastbottomdp->cylinderid - && dive->dc.divemode == OC && decoMode(true) != RECREATIONAL) { + && dive.dcs[0].divemode == OC && decoMode(true) != RECREATIONAL) { /* Calculate minimum gas volume. */ volume_t mingasv; mingasv.mliter = lrint(prefs.sacfactor / 100.0 * prefs.problemsolvingtime * prefs.bottomsac - * depth_to_bar(lastbottomdp->depth.mm, dive) - + prefs.sacfactor / 100.0 * cyl->deco_gas_used.mliter); + * dive.depth_to_bar(lastbottomdp->depth.mm) + + prefs.sacfactor / 100.0 * cyl.deco_gas_used.mliter); /* Calculate minimum gas pressure for cyclinder. */ - lastbottomdp->minimum_gas.mbar = lrint(isothermal_pressure(cyl->gasmix, 1.0, - mingasv.mliter, cyl->type.size.mliter) * 1000); + lastbottomdp->minimum_gas.mbar = lrint(isothermal_pressure(cyl.gasmix, 1.0, + mingasv.mliter, cyl.type.size.mliter) * 1000); /* Translate all results into correct units */ mingas_volume = get_volume_units(mingasv.mliter, NULL, &unit); mingas_pressure = get_pressure_units(lastbottomdp->minimum_gas.mbar, &pressure_unit); - mingas_d_pressure = get_pressure_units(lrint((double)cyl->end.mbar + deco_pressure_mbar - lastbottomdp->minimum_gas.mbar), &pressure_unit); + mingas_d_pressure = get_pressure_units(lrint((double)cyl.end.mbar + deco_pressure_mbar - lastbottomdp->minimum_gas.mbar), &pressure_unit); mingas_depth = get_depth_units(lastbottomdp->depth.mm, NULL, &depth_unit); /* Print it to results */ - if (cyl->start.mbar > lastbottomdp->minimum_gas.mbar) { + if (cyl.start.mbar > lastbottomdp->minimum_gas.mbar) { mingas = casprintf_loc("
\n — %s (%s %.1fx%s/+%d%s@%.0f%s): " "%.0f%s/%.0f%s/Δ:%+.0f%s", mingas_d_pressure > 0 ? "green" :"red", @@ -535,18 +545,18 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, /* Print the gas consumption for every cylinder here to temp buffer. */ if (lrint(volume) > 0) { temp = casprintf_loc(translate("gettextFromC", "%.0f%s/%.0f%s of %s (%.0f%s/%.0f%s in planned ascent)"), - volume, unit, pressure, pressure_unit, gasname(cyl->gasmix), deco_volume, unit, deco_pressure, pressure_unit); + volume, unit, pressure, pressure_unit, cyl.gasmix.name().c_str(), deco_volume, unit, deco_pressure, pressure_unit); } else { temp = casprintf_loc(translate("gettextFromC", "%.0f%s/%.0f%s of %s"), - volume, unit, pressure, pressure_unit, gasname(cyl->gasmix)); + volume, unit, pressure, pressure_unit, cyl.gasmix.name().c_str()); } } else { if (lrint(volume) > 0) { temp = casprintf_loc(translate("gettextFromC", "%.0f%s of %s (%.0f%s during planned ascent)"), - volume, unit, gasname(cyl->gasmix), deco_volume, unit); + volume, unit, cyl.gasmix.name().c_str(), deco_volume, unit); } else { temp = casprintf_loc(translate("gettextFromC", "%.0f%s of %s"), - volume, unit, gasname(cyl->gasmix)); + volume, unit, cyl.gasmix.name().c_str()); } } /* Gas consumption: Now finally print all strings to output */ @@ -568,56 +578,49 @@ extern "C" void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, /* Print warnings for pO2 (move into separate function?) */ { - dp = diveplan->dp; bool o2warning_exist = false; - enum divemode_t current_divemode; double amb; - const struct event *evd = NULL; - current_divemode = UNDEF_COMP_TYPE; - if (dive->dc.divemode != CCR) { - while (dp) { - if (dp->time != 0) { + divemode_loop loop(dive.dcs[0]); + if (dive.dcs[0].divemode != CCR) { + for (auto &dp: this->dp) { + if (dp.time != 0) { std::string temp; - struct gas_pressures pressures; - struct gasmix gasmix = get_cylinder(dive, dp->cylinderid)->gasmix; + struct gasmix gasmix = dive.get_cylinder(dp.cylinderid)->gasmix; - current_divemode = get_current_divemode(&dive->dc, dp->time, &evd, ¤t_divemode); - amb = depth_to_atm(dp->depth.mm, dive); - fill_pressures(&pressures, amb, gasmix, (current_divemode == OC) ? 0.0 : amb * gasmix.o2.permille / 1000.0, current_divemode); + divemode_t current_divemode = loop.at(dp.time); + amb = dive.depth_to_atm(dp.depth.mm); + gas_pressures pressures = fill_pressures(amb, gasmix, (current_divemode == OC) ? 0.0 : amb * gasmix.o2.permille / 1000.0, current_divemode); - if (pressures.o2 > (dp->entered ? prefs.bottompo2 : prefs.decopo2) / 1000.0) { + if (pressures.o2 > (dp.entered ? prefs.bottompo2 : prefs.decopo2) / 1000.0) { const char *depth_unit; int decimals; - double depth_value = get_depth_units(dp->depth.mm, &decimals, &depth_unit); + double depth_value = get_depth_units(dp.depth.mm, &decimals, &depth_unit); if (!o2warning_exist) buf += "
\n"; o2warning_exist = true; temp = casprintf_loc(translate("gettextFromC", "high pO₂ value %.2f at %d:%02u with gas %s at depth %.*f %s"), - pressures.o2, FRACTION_TUPLE(dp->time, 60), gasname(gasmix), decimals, depth_value, depth_unit); + pressures.o2, FRACTION_TUPLE(dp.time, 60), gasmix.name().c_str(), decimals, depth_value, depth_unit); buf += format_string_std("%s %s
\n", translate("gettextFromC", "Warning:"), temp.c_str()); } else if (pressures.o2 < 0.16) { const char *depth_unit; int decimals; - double depth_value = get_depth_units(dp->depth.mm, &decimals, &depth_unit); + double depth_value = get_depth_units(dp.depth.mm, &decimals, &depth_unit); if (!o2warning_exist) buf += "
"; o2warning_exist = true; temp = casprintf_loc(translate("gettextFromC", "low pO₂ value %.2f at %d:%02u with gas %s at depth %.*f %s"), - pressures.o2, FRACTION_TUPLE(dp->time, 60), gasname(gasmix), decimals, depth_value, depth_unit); + pressures.o2, FRACTION_TUPLE(dp.time, 60), gasmix.name().c_str(), decimals, depth_value, depth_unit); buf += format_string_std("%s %s
\n", translate("gettextFromC", "Warning:"), temp.c_str()); } } - dp = dp->next; } } if (o2warning_exist) buf += "
\n"; } - // TODO: avoid copy - free(dive->notes); - dive->notes = strdup(buf.c_str()); + dive.notes = std::move(buf); #ifdef DEBUG_PLANNER_NOTES - printf("\n\n\tplannernotes\n\t\n%s\t\n\n", dive->notes); + printf("\n\n\tplannernotes\n\t\n%s\t\n\n", dive.notes); #endif } diff --git a/core/pref.c b/core/pref.c deleted file mode 100644 index 17ca1c652..000000000 --- a/core/pref.c +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pref.h" -#include "subsurface-string.h" -#include "git-access.h" // for CLOUD_HOST - -struct preferences prefs, git_prefs; -struct preferences default_prefs = { - .cloud_base_url = "https://" CLOUD_HOST_EU "/", // if we don't know any better, use the European host - .units = SI_UNITS, - .unit_system = METRIC, - .coordinates_traditional = true, - .pp_graphs = { - .po2 = false, - .pn2 = false, - .phe = false, - .po2_threshold_min = 0.16, - .po2_threshold_max = 1.6, - .pn2_threshold = 4.0, - .phe_threshold = 13.0, - }, - .mod = false, - .modpO2 = 1.6, - .ead = false, - .hrgraph = false, - .percentagegraph = false, - .dcceiling = true, - .redceiling = false, - .calcceiling = false, - .calcceiling3m = false, - .calcndltts = false, - .decoinfo = true, - .gflow = 30, - .gfhigh = 75, - .animation_speed = 500, - .gf_low_at_maxdepth = false, - .show_ccr_setpoint = false, - .show_ccr_sensors = false, - .show_scr_ocpo2 = false, - .font_size = -1, - .mobile_scale = 1.0, - .display_invalid_dives = false, - .show_sac = false, - .include_unused_tanks = false, - .display_default_tank_infos = true, - .show_average_depth = true, - .show_icd = false, - .ascrate75 = 9000 / 60, - .ascrate50 = 9000 / 60, - .ascratestops = 9000 / 60, - .ascratelast6m = 9000 / 60, - .descrate = 18000 / 60, - .sacfactor = 400, - .problemsolvingtime = 4, - .bottompo2 = 1400, - .decopo2 = 1600, - .bestmixend.mm = 30000, - .doo2breaks = false, - .dobailout = false, - .drop_stone_mode = false, - .switch_at_req_stop = false, - .min_switch_duration = 60, - .surface_segment = 0, - .last_stop = false, - .verbatim_plan = false, - .display_runtime = true, - .display_duration = true, - .display_transitions = true, - .display_variations = false, - .o2narcotic = true, - .safetystop = true, - .bottomsac = 20000, - .decosac = 17000, - .reserve_gas=40000, - .o2consumption = 720, - .pscr_ratio = 100, - .show_pictures_in_profile = true, - .tankbar = false, - .defaultsetpoint = 1100, - .geocoding = { - .category = { 0 } - }, - .locale = { - .use_system_language = true, - }, - .planner_deco_mode = BUEHLMANN, - .vpmb_conservatism = 3, -#if defined(SUBSURFACE_MOBILE) - .cloud_timeout = 10, -#else - .cloud_timeout = 5, -#endif - .auto_recalculate_thumbnails = true, - .extract_video_thumbnails = true, - .extract_video_thumbnails_position = 20, // The first fifth seems like a reasonable place - .three_m_based_grid = false, - .sync_dc_time = false, -}; - -/* copy a preferences block, including making copies of all included strings */ -void copy_prefs(struct preferences *src, struct preferences *dest) -{ - *dest = *src; - dest->divelist_font = copy_string(src->divelist_font); - dest->default_filename = copy_string(src->default_filename); - dest->default_cylinder = copy_string(src->default_cylinder); - dest->cloud_base_url = copy_string(src->cloud_base_url); - dest->proxy_host = copy_string(src->proxy_host); - dest->proxy_user = copy_string(src->proxy_user); - dest->proxy_pass = copy_string(src->proxy_pass); - dest->time_format = copy_string(src->time_format); - dest->date_format = copy_string(src->date_format); - dest->date_format_short = copy_string(src->date_format_short); - dest->cloud_storage_password = copy_string(src->cloud_storage_password); - dest->cloud_storage_email = copy_string(src->cloud_storage_email); - dest->cloud_storage_email_encoded = copy_string(src->cloud_storage_email_encoded); - dest->ffmpeg_executable = copy_string(src->ffmpeg_executable); -} - -/* - * Free strduped prefs before exit. - * - * These are not real leaks but they plug the holes found by eg. - * valgrind so you can find the real leaks. - */ -void free_prefs(void) -{ - // nop -} diff --git a/core/pref.cpp b/core/pref.cpp new file mode 100644 index 000000000..fa1cb2496 --- /dev/null +++ b/core/pref.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pref.h" +#include "subsurface-string.h" +#include "git-access.h" // for CLOUD_HOST + +struct preferences prefs, git_prefs, default_prefs; + +preferences::preferences() : + animation_speed(500), + cloud_base_url("https://" CLOUD_HOST_EU "/"), // if we don't know any better, use the European host +#if defined(SUBSURFACE_MOBILE) + cloud_timeout(10), +#else + cloud_timeout(5), +#endif + sync_dc_time(false), + display_invalid_dives(false), + font_size(-1), + mobile_scale(1.0), + show_developer(true), + three_m_based_grid(false), + map_short_names(false), + include_unused_tanks(false), + display_default_tank_infos(true), + auto_recalculate_thumbnails(true), + extract_video_thumbnails(true), + extract_video_thumbnails_position(20), // The first fifth seems like a reasonable place + defaultsetpoint(1100), + default_file_behavior(LOCAL_DEFAULT_FILE), + o2consumption(720), + pscr_ratio(100), + use_default_file(true), + extraEnvironmentalDefault(false), + salinityEditDefault(false), + date_format_override(false), + time_format_override(false), + proxy_auth(false), + proxy_port(0), + proxy_type(0), + ascratelast6m(9000 / 60), + ascratestops(9000 / 60), + ascrate50(9000 / 60), + ascrate75(9000 / 60), + bestmixend(30_m), + bottompo2(1400), + bottomsac(20000), + decopo2(1600), + decosac(17000), + descrate(18000 / 60), + display_duration(true), + display_runtime(true), + display_transitions(true), + display_variations(false), + doo2breaks(false), + dobailout(false), + o2narcotic(true), + drop_stone_mode(false), + last_stop(false), + min_switch_duration(60), + surface_segment(0), + planner_deco_mode(BUEHLMANN), + problemsolvingtime(4), + reserve_gas(40000), + sacfactor(400), + safetystop(true), + switch_at_req_stop(false), + verbatim_plan(false), + calcalltissues(false), + calcceiling(false), + calcceiling3m(false), + calcndltts(false), + decoinfo(true), + dcceiling(true), + display_deco_mode(BUEHLMANN), + ead(false), + gfhigh(75), + gflow(30), + gf_low_at_maxdepth(false), + hrgraph(false), + mod(false), + modpO2(1.6), + percentagegraph(false), + redceiling(false), + rulergraph(false), + show_average_depth(true), + show_ccr_sensors(false), + show_ccr_setpoint(false), + show_icd(false), + show_pictures_in_profile(true), + show_sac(false), + show_scr_ocpo2(false), + tankbar(false), + vpmb_conservatism(3), + zoomed_plot(false), + infobox(true), + allowOcGasAsDiluent(false), + coordinates_traditional(true), + unit_system(METRIC), + units(SI_UNITS) +{ +} + +preferences::~preferences() = default; + +void set_git_prefs(std::string_view prefs) +{ + if (contains(prefs, "TANKBAR")) + git_prefs.tankbar = 1; + if (contains(prefs, "SHOW_SETPOINT")) + git_prefs.show_ccr_setpoint = 1; + if (contains(prefs, "SHOW_SENSORS")) + git_prefs.show_ccr_sensors = 1; + if (contains(prefs, "PO2_GRAPH")) + git_prefs.pp_graphs.po2 = 1; +} diff --git a/core/pref.h b/core/pref.h index b649ffc74..a0d450de3 100644 --- a/core/pref.h +++ b/core/pref.h @@ -2,35 +2,31 @@ #ifndef PREF_H #define PREF_H -#ifdef __cplusplus -extern "C" { -#else -#include -#endif - #include "units.h" #include "taxonomy.h" -typedef struct -{ - bool po2; - bool pn2; - bool phe; - double po2_threshold_min; - double po2_threshold_max; - double pn2_threshold; - double phe_threshold; -} partial_pressure_graphs_t; +#include +#include -typedef struct { - enum taxonomy_category category[3]; -} geocoding_prefs_t; +struct partial_pressure_graphs_t { + bool po2 = false; + bool pn2 = false; + bool phe = false; + double po2_threshold_min = 0.16; + double po2_threshold_max = 1.6; + double pn2_threshold = 4.0; + double phe_threshold = 13.0; +}; -typedef struct { - const char *language; - const char *lang_locale; - bool use_system_language; -} locale_prefs_t; +struct geocoding_prefs_t { + enum taxonomy_category category[3] = { TC_NONE, TC_NONE, TC_NONE }; +}; + +struct locale_prefs_t { + std::string language; + std::string lang_locale; + bool use_system_language = true; +}; enum deco_mode { BUEHLMANN, @@ -45,18 +41,18 @@ enum def_file_behavior { CLOUD_DEFAULT_FILE }; -typedef struct { - bool dont_check_for_updates; - const char *last_version_used; - int next_check; -} update_manager_prefs_t; +struct update_manager_prefs_t { + bool dont_check_for_updates = false; + std::string last_version_used; + int next_check = 0; +}; -typedef struct { - const char *vendor; - const char *product; - const char *device; - const char *device_name; -} dive_computer_prefs_t; +struct dive_computer_prefs_t { + std::string vendor; + std::string product; + std::string device; + std::string device_name; +}; // NOTE: these enums are duplicated in mobile-widgets/qmlinterface.h enum unit_system_values { @@ -81,11 +77,11 @@ struct preferences { // ********** CloudStorage ********** bool cloud_auto_sync; - const char *cloud_base_url; - const char *cloud_storage_email; - const char *cloud_storage_email_encoded; - const char *cloud_storage_password; - const char *cloud_storage_pin; + std::string cloud_base_url; + std::string cloud_storage_email; + std::string cloud_storage_email_encoded; + std::string cloud_storage_password; + std::string cloud_storage_pin; int cloud_timeout; int cloud_verification_status; bool save_password_local; @@ -100,7 +96,7 @@ struct preferences { // ********** Display ************* bool display_invalid_dives; - const char *divelist_font; + std::string divelist_font; double font_size; double mobile_scale; bool show_developer; @@ -108,7 +104,7 @@ struct preferences { bool map_short_names; // ********** Equipment tab ******* - const char *default_cylinder; + std::string default_cylinder; bool include_unused_tanks; bool display_default_tank_infos; @@ -116,9 +112,9 @@ struct preferences { bool auto_recalculate_thumbnails; bool extract_video_thumbnails; int extract_video_thumbnails_position; // position in stream: 0=first 100=last second - const char *ffmpeg_executable; // path of ffmpeg binary + std::string ffmpeg_executable; // path of ffmpeg binary int defaultsetpoint; // default setpoint in mbar - const char *default_filename; + std::string default_filename; enum def_file_behavior default_file_behavior; int o2consumption; // ml per min int pscr_ratio; // dump ratio times 1000 @@ -130,20 +126,20 @@ struct preferences { geocoding_prefs_t geocoding; // ********** Language ********** - const char *date_format; + std::string date_format; bool date_format_override; - const char *date_format_short; + std::string date_format_short; locale_prefs_t locale; //: TODO: move the rest of locale based info here. - const char *time_format; + std::string time_format; bool time_format_override; // ********** Network ********** bool proxy_auth; - const char *proxy_host; + std::string proxy_host; int proxy_port; int proxy_type; - const char *proxy_user; - const char *proxy_pass; + std::string proxy_user; + std::string proxy_pass; // ********** Planner ********** int ascratelast6m; @@ -205,6 +201,7 @@ struct preferences { int vpmb_conservatism; bool zoomed_plot; bool infobox; + bool allowOcGasAsDiluent; // ********** Units ********** bool coordinates_traditional; @@ -213,23 +210,22 @@ struct preferences { // ********** UpdateManager ********** update_manager_prefs_t update_manager; + + preferences(); // Initialize to default + ~preferences(); }; extern struct preferences prefs, default_prefs, git_prefs; -extern const char *system_divelist_default_font; +extern std::string system_divelist_default_font; extern double system_divelist_default_font_size; -extern const char *system_default_directory(void); -extern const char *system_default_filename(); -extern bool subsurface_ignore_font(const char *font); +extern std::string system_default_directory(); +extern std::string system_default_filename(); +extern bool subsurface_ignore_font(const std::string &font); extern void subsurface_OS_pref_setup(); -extern void copy_prefs(struct preferences *src, struct preferences *dest); extern void set_informational_units(const char *units); - -#ifdef __cplusplus -} -#endif +extern void set_git_prefs(std::string_view prefs); #endif // PREF_H diff --git a/core/profile.cpp b/core/profile.cpp index 992434937..759fc787b 100644 --- a/core/profile.cpp +++ b/core/profile.cpp @@ -2,7 +2,6 @@ /* profile.c */ /* creates all the necessary data for drawing the dive profile */ -#include "ssrf.h" #include "gettext.h" #include #include @@ -11,6 +10,7 @@ #include "dive.h" #include "divelist.h" +#include "divelog.h" #include "errorhelper.h" #include "event.h" #include "interpolate.h" @@ -25,34 +25,35 @@ #include "libdivecomputer/version.h" #include "membuffer.h" #include "qthelper.h" +#include "range.h" #include "format.h" //#define DEBUG_GAS 1 #define MAX_PROFILE_DECO 7200 -extern "C" int ascent_velocity(int depth, int avg_depth, int bottom_time); +int ascent_velocity(int depth, int avg_depth, int bottom_time); #ifdef DEBUG_PI /* debugging tool - not normally used */ -static void dump_pi(struct plot_info *pi) +static void dump_pi(const struct plot_info &pi) { int i; printf("pi:{nr:%d maxtime:%d meandepth:%d maxdepth:%d \n" " maxpressure:%d mintemp:%d maxtemp:%d\n", - pi->nr, pi->maxtime, pi->meandepth, pi->maxdepth, - pi->maxpressure, pi->mintemp, pi->maxtemp); - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = &pi->entry[i]; + pi.nr, pi.maxtime, pi.meandepth, pi.maxdepth, + pi.maxpressure, pi.mintemp, pi.maxtemp); + for (i = 0; i < pi.nr; i++) { + struct plot_data &entry = pi.entry[i]; printf(" entry[%d]:{cylinderindex:%d sec:%d pressure:{%d,%d}\n" " time:%d:%02d temperature:%d depth:%d stopdepth:%d stoptime:%d ndl:%d smoothed:%d po2:%lf phe:%lf pn2:%lf sum-pp %lf}\n", - i, entry->sensor[0], entry->sec, - entry->pressure[0], entry->pressure[1], - entry->sec / 60, entry->sec % 60, - entry->temperature, entry->depth, entry->stopdepth, entry->stoptime, entry->ndl, entry->smoothed, - entry->pressures.o2, entry->pressures.he, entry->pressures.n2, - entry->pressures.o2 + entry->pressures.he + entry->pressures.n2); + i, entry.sensor[0], entry.sec, + entry.pressure[0], entry.pressure[1], + entry.sec / 60, entry.sec % 60, + entry.temperature, entry.depth, entry.stopdepth, entry.stoptime, entry.ndl, entry.smoothed, + entry.pressures.o2, entry.pressures.he, entry.pressures.n2, + entry.pressures.o2 + entry.pressures.he + entry.pressures.n2); } printf(" }\n"); } @@ -70,39 +71,47 @@ static T div_up(T x, T y) return (x + y - 1) / y; } +plot_info::plot_info() +{ +} + +plot_info::~plot_info() +{ +} + /* * When showing dive profiles, we scale things to the * current dive. However, we don't scale past less than * 30 minutes or 90 ft, just so that small dives show * up as such unless zoom is enabled. */ -extern "C" int get_maxtime(const struct plot_info *pi) +int get_maxtime(const struct plot_info &pi) { - int seconds = pi->maxtime; + int seconds = pi.maxtime; int min = prefs.zoomed_plot ? 30 : 30 * 60; return std::max(min, seconds); } /* get the maximum depth to which we want to plot */ -extern "C" int get_maxdepth(const struct plot_info *pi) +int get_maxdepth(const struct plot_info &pi) { /* 3m to spare */ - int mm = pi->maxdepth + 3000; + int mm = pi.maxdepth + 3000; return prefs.zoomed_plot ? mm : std::max(30000, mm); } /* UNUSED! */ -static int get_local_sac(struct plot_info *pi, int idx1, int idx2, struct dive *dive) __attribute__((unused)); +static int get_local_sac(struct plot_info &pi, int idx1, int idx2, struct dive *dive) __attribute__((unused)); /* Get local sac-rate (in ml/min) between entry1 and entry2 */ -static int get_local_sac(struct plot_info *pi, int idx1, int idx2, struct dive *dive) +static int get_local_sac(struct plot_info &pi, int idx1, int idx2, struct dive *dive) { int index = 0; cylinder_t *cyl; - struct plot_data *entry1 = pi->entry + idx1; - struct plot_data *entry2 = pi->entry + idx2; - int duration = entry2->sec - entry1->sec; - int depth, airuse; + struct plot_data &entry1 = pi.entry[idx1]; + struct plot_data &entry2 = pi.entry[idx2]; + int duration = entry2.sec - entry1.sec; + int depth; pressure_t a, b; double atm; @@ -114,15 +123,15 @@ static int get_local_sac(struct plot_info *pi, int idx1, int idx2, struct dive * return 0; /* Mean pressure in ATM */ - depth = (entry1->depth + entry2->depth) / 2; - atm = depth_to_atm(depth, dive); + depth = (entry1.depth + entry2.depth) / 2; + atm = dive->depth_to_atm(depth); - cyl = get_cylinder(dive, index); + cyl = dive->get_cylinder(index); - airuse = gas_volume(cyl, a) - gas_volume(cyl, b); + volume_t airuse = cyl->gas_volume(a) - cyl->gas_volume(b); /* milliliters per minute */ - return lrint(airuse / atm * 60 / duration); + return lrint(airuse.mliter / atm * 60 / duration); } static velocity_t velocity(int speed) @@ -153,130 +162,67 @@ static velocity_t velocity(int speed) return v; } -static void analyze_plot_info(struct plot_info *pi) +static void analyze_plot_info(struct plot_info &pi) { - int i; - int nr = pi->nr; - /* Smoothing function: 5-point triangular smooth */ - for (i = 2; i < nr; i++) { - struct plot_data *entry = pi->entry + i; + for (size_t i = 2; i < pi.entry.size(); i++) { + struct plot_data &entry = pi.entry[i]; int depth; - if (i < nr - 2) { - depth = entry[-2].depth + 2 * entry[-1].depth + 3 * entry[0].depth + 2 * entry[1].depth + entry[2].depth; - entry->smoothed = (depth + 4) / 9; + if (i + 2 < pi.entry.size()) { + depth = pi.entry[i-2].depth + 2 * pi.entry[i-1].depth + 3 * pi.entry[i].depth + 2 * pi.entry[i+1].depth + pi.entry[i+2].depth; + entry.smoothed = (depth + 4) / 9; } /* vertical velocity in mm/sec */ /* Linus wants to smooth this - let's at least look at the samples that aren't FAST or CRAZY */ - if (entry[0].sec - entry[-1].sec) { - entry->speed = (entry[0].depth - entry[-1].depth) / (entry[0].sec - entry[-1].sec); - entry->velocity = velocity(entry->speed); + if (pi.entry[i].sec - pi.entry[i-1].sec) { + entry.speed = (pi.entry[i+0].depth - pi.entry[i-1].depth) / (pi.entry[i].sec - pi.entry[i-1].sec); + entry.velocity = velocity(entry.speed); /* if our samples are short and we aren't too FAST*/ - if (entry[0].sec - entry[-1].sec < 15 && entry->velocity < FAST) { + if (pi.entry[i].sec - pi.entry[i-1].sec < 15 && entry.velocity < FAST) { int past = -2; - while (i + past > 0 && entry[0].sec - entry[past].sec < 15) + while (i + past > 0 && pi.entry[i].sec - pi.entry[i+past].sec < 15) past--; - entry->velocity = velocity((entry[0].depth - entry[past].depth) / - (entry[0].sec - entry[past].sec)); + entry.velocity = velocity((pi.entry[i].depth - pi.entry[i+past].depth) / + (pi.entry[i].sec - pi.entry[i+past].sec)); } } else { - entry->velocity = STABLE; - entry->speed = 0; + entry.velocity = STABLE; + entry.speed = 0; } } } -/* - * If the event has an explicit cylinder index, - * we return that. If it doesn't, we return the best - * match based on the gasmix. - * - * Some dive computers give cylinder indices, some - * give just the gas mix. - */ -extern "C" int get_cylinder_index(const struct dive *dive, const struct event *ev) +static size_t set_setpoint(struct plot_info &pi, size_t i, int setpoint, int end) { - int best; - struct gasmix mix; - - if (ev->gas.index >= 0) - return ev->gas.index; - - /* - * This should no longer happen! - * - * We now match up gas change events with their cylinders at dive - * event fixup time. - */ - report_info("Still looking up cylinder based on gas mix in get_cylinder_index()!"); - - mix = get_gasmix_from_event(dive, ev); - best = find_best_gasmix_match(mix, &dive->cylinders); - return best < 0 ? 0 : best; -} - -extern "C" struct event *get_next_event_mutable(struct event *event, const char *name) -{ - if (!name || !*name) - return NULL; - while (event) { - if (same_string(event->name, name)) - return event; - event = event->next; - } - return event; -} - -extern "C" const struct event *get_next_event(const struct event *event, const char *name) -{ - return get_next_event_mutable((struct event *)event, name); -} - -static int count_events(const struct divecomputer *dc) -{ - int result = 0; - struct event *ev = dc->events; - while (ev != NULL) { - result++; - ev = ev->next; - } - return result; -} - -static int set_setpoint(struct plot_info *pi, int i, int setpoint, int end) -{ - while (i < pi->nr) { - struct plot_data *entry = pi->entry + i; - if (entry->sec > end) + while (i < pi.entry.size()) { + struct plot_data &entry = pi.entry[i]; + if (entry.sec > end) break; - entry->o2pressure.mbar = setpoint; + entry.o2pressure.mbar = setpoint; i++; } return i; } -static void check_setpoint_events(const struct dive *, const struct divecomputer *dc, struct plot_info *pi) +static void check_setpoint_events(const struct dive *, const struct divecomputer *dc, struct plot_info &pi) { - int i = 0; + size_t i = 0; pressure_t setpoint; - setpoint.mbar = 0; - const struct event *ev = get_next_event(dc->events, "SP change"); - if (!ev) - return; - - do { + event_loop loop("SP change", *dc); + bool found = false; + while (auto ev = loop.next()) { i = set_setpoint(pi, i, setpoint.mbar, ev->time.seconds); setpoint.mbar = ev->value; - ev = get_next_event(ev->next, "SP change"); - } while (ev); - set_setpoint(pi, i, setpoint.mbar, INT_MAX); + found = true; + } + if (found) // Fill the last setpoint until the end of the dive + set_setpoint(pi, i, setpoint.mbar, INT_MAX); } -static void calculate_max_limits_new(const struct dive *dive, const struct divecomputer *given_dc, struct plot_info *pi, bool in_planner) +static void calculate_max_limits_new(const struct dive *dive, const struct divecomputer *given_dc, struct plot_info &pi, bool in_planner) { - const struct divecomputer *dc = &(dive->dc); bool seen = false; bool found_sample_beyond_last_event = false; int maxdepth = dive->maxdepth.mm; @@ -285,42 +231,31 @@ static void calculate_max_limits_new(const struct dive *dive, const struct divec int maxhr = 0, minhr = INT_MAX; int mintemp = dive->mintemp.mkelvin; int maxtemp = dive->maxtemp.mkelvin; - int cyl; /* Get the per-cylinder maximum pressure if they are manual */ - for (cyl = 0; cyl < dive->cylinders.nr; cyl++) { - int mbar_start = get_cylinder(dive, cyl)->start.mbar; - int mbar_end = get_cylinder(dive, cyl)->end.mbar; + for (auto &cyl: dive->cylinders) { + int mbar_start = cyl.start.mbar; + int mbar_end = cyl.end.mbar; if (mbar_start > maxpressure) maxpressure = mbar_start; if (mbar_end && mbar_end < minpressure) minpressure = mbar_end; } - /* Then do all the samples from all the dive computers */ - do { - if (dc == given_dc) - seen = true; - int i = dc->samples; + auto process_dc = [&] (const divecomputer &dc) { int lastdepth = 0; - struct sample *s = dc->sample; - struct event *ev; /* Make sure we can fit all events */ - ev = dc->events; - while (ev) { - if (ev->time.seconds > maxtime) - maxtime = ev->time.seconds; - ev = ev->next; - } + if (!dc.events.empty()) + maxtime = std::max(maxtime, dc.events.back().time.seconds); - while (--i >= 0) { - int depth = s->depth.mm; - int temperature = s->temperature.mkelvin; - int heartbeat = s->heartbeat; + for (auto &s: dc.samples) { + int depth = s.depth.mm; + int temperature = s.temperature.mkelvin; + int heartbeat = s.heartbeat; for (int sensor = 0; sensor < MAX_SENSORS; ++sensor) { - int pressure = s->pressure[sensor].mbar; + int pressure = s.pressure[sensor].mbar; if (pressure && pressure < minpressure) minpressure = pressure; if (pressure > maxpressure) @@ -338,74 +273,72 @@ static void calculate_max_limits_new(const struct dive *dive, const struct divec minhr = heartbeat; if (depth > maxdepth) - maxdepth = s->depth.mm; + maxdepth = s.depth.mm; /* Make sure that we get the first sample beyond the last event. * If maxtime is somewhere in the middle of the last segment, * populate_plot_entries() gets confused leading to display artifacts. */ if ((depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD || in_planner || !found_sample_beyond_last_event) && - s->time.seconds > maxtime) { + s.time.seconds > maxtime) { found_sample_beyond_last_event = true; - maxtime = s->time.seconds; + maxtime = s.time.seconds; } lastdepth = depth; - s++; } + }; - dc = dc->next; - if (dc == NULL && !seen) { - dc = given_dc; + /* Then do all the samples from all the dive computers */ + for (auto &dc: dive->dcs) { + if (&dc == given_dc) seen = true; - } - } while (dc != NULL); + process_dc(dc); + } + if (!seen) + process_dc(*given_dc); if (minpressure > maxpressure) minpressure = 0; if (minhr > maxhr) minhr = maxhr; - memset(pi, 0, sizeof(*pi)); - pi->maxdepth = maxdepth; - pi->maxtime = maxtime; - pi->maxpressure = maxpressure; - pi->minpressure = minpressure; - pi->minhr = minhr; - pi->maxhr = maxhr; - pi->mintemp = mintemp; - pi->maxtemp = maxtemp; + pi.maxdepth = maxdepth; + pi.maxtime = maxtime; + pi.maxpressure = maxpressure; + pi.minpressure = minpressure; + pi.minhr = minhr; + pi.maxhr = maxhr; + pi.mintemp = mintemp; + pi.maxtemp = maxtemp; +} + +static plot_data &add_entry(struct plot_info &pi) +{ + pi.entry.emplace_back(); + pi.pressures.resize(pi.pressures.size() + pi.nr_cylinders); + return pi.entry.back(); } /* copy the previous entry (we know this exists), update time and depth * and zero out the sensor pressure (since this is a synthetic entry) * increment the entry pointer and the count of synthetic entries. */ -static void insert_entry(struct plot_info *pi, int idx, int time, int depth, int sac) +static void insert_entry(struct plot_info &pi, int time, int depth, int sac) { - struct plot_data *entry = pi->entry + idx; - struct plot_data *prev = pi->entry + idx - 1; - *entry = *prev; - entry->sec = time; - entry->depth = depth; - entry->running_sum = prev->running_sum + (time - prev->sec) * (depth + prev->depth) / 2; - entry->sac = sac; - entry->ndl = -1; - entry->bearing = -1; + struct plot_data &entry = add_entry(pi); + struct plot_data &prev = pi.entry[pi.entry.size() - 2]; + entry = prev; + entry.sec = time; + entry.depth = depth; + entry.running_sum = prev.running_sum + (time - prev.sec) * (depth + prev.depth) / 2; + entry.sac = sac; + entry.ndl = -1; + entry.bearing = -1; } -extern "C" void free_plot_info_data(struct plot_info *pi) +static void populate_plot_entries(const struct dive *dive, const struct divecomputer *dc, struct plot_info &pi) { - free(pi->entry); - free(pi->pressures); - memset(pi, 0, sizeof(*pi)); -} - -static void populate_plot_entries(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi) -{ - int idx, maxtime, nr, i; - int lastdepth, lasttime, lasttemp = 0; - struct plot_data *plot_data; - struct event *ev = dc->events; - maxtime = pi->maxtime; + pi.nr_cylinders = static_cast(dive->cylinders.size()); /* + * To avoid continuous reallocation, allocate the expected number of entries. * We want to have a plot_info event at least every 10s (so "maxtime/10+1"), * but samples could be more dense than that (so add in dc->samples). We also * need to have one for every event (so count events and add that) and @@ -414,28 +347,26 @@ static void populate_plot_entries(const struct dive *dive, const struct divecomp * that has time > maxtime (because there can be surface samples * past "maxtime" in the original sample data) */ - nr = dc->samples + 6 + maxtime / 10 + count_events(dc); - plot_data = (struct plot_data *)calloc(nr, sizeof(struct plot_data)); - pi->entry = plot_data; - pi->nr_cylinders = dive->cylinders.nr; - pi->pressures = (struct plot_pressure_data *)calloc(nr * (size_t)pi->nr_cylinders, sizeof(struct plot_pressure_data)); - if (!plot_data) - return; - pi->nr = nr; - idx = 2; /* the two extra events at the start */ + size_t nr = dc->samples.size() + 6 + pi.maxtime / 10 + dc->events.size(); + pi.entry.reserve(nr); + pi.pressures.reserve(nr * pi.nr_cylinders); - lastdepth = 0; - lasttime = 0; + // The two extra events at the start + pi.entry.resize(2); + pi.pressures.resize(pi.nr_cylinders * 2); + + int lastdepth = 0; + int lasttime = 0; + int lasttemp = 0; /* skip events at time = 0 */ - while (ev && ev->time.seconds == 0) - ev = ev->next; - for (i = 0; i < dc->samples; i++) { - struct plot_data *entry = plot_data + idx; - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; + auto evit = dc->events.begin(); + while (evit != dc->events.end() && evit->time.seconds == 0) + ++evit; + for (const auto &sample: dc->samples) { + int time = sample.time.seconds; int offset, delta; - int depth = sample->depth.mm; - int sac = sample->sac.mliter; + int depth = sample.depth.mm; + int sac = sample.sac.mliter; /* Add intermediate plot entries if required */ delta = time - lasttime; @@ -444,95 +375,87 @@ static void populate_plot_entries(const struct dive *dive, const struct divecomp delta = 1; // avoid divide by 0 } for (offset = 10; offset < delta; offset += 10) { - if (lasttime + offset > maxtime) + if (lasttime + offset > pi.maxtime) break; /* Add events if they are between plot entries */ - while (ev && (int)ev->time.seconds < lasttime + offset) { - insert_entry(pi, idx, ev->time.seconds, interpolate(lastdepth, depth, ev->time.seconds - lasttime, delta), sac); - entry++; - idx++; - ev = ev->next; + while (evit != dc->events.end() && static_cast(evit->time.seconds) < lasttime + offset) { + insert_entry(pi, evit->time.seconds, interpolate(lastdepth, depth, evit->time.seconds - lasttime, delta), sac); + ++evit; } /* now insert the time interpolated entry */ - insert_entry(pi, idx, lasttime + offset, interpolate(lastdepth, depth, offset, delta), sac); - entry++; - idx++; + insert_entry(pi, lasttime + offset, interpolate(lastdepth, depth, offset, delta), sac); /* skip events that happened at this time */ - while (ev && (int)ev->time.seconds == lasttime + offset) - ev = ev->next; + while (evit != dc->events.end() && static_cast(evit->time.seconds) == lasttime + offset) + ++evit; } /* Add events if they are between plot entries */ - while (ev && (int)ev->time.seconds < time) { - insert_entry(pi, idx, ev->time.seconds, interpolate(lastdepth, depth, ev->time.seconds - lasttime, delta), sac); - entry++; - idx++; - ev = ev->next; + while (evit != dc->events.end() && static_cast(evit->time.seconds) < time) { + insert_entry(pi, evit->time.seconds, interpolate(lastdepth, depth, evit->time.seconds - lasttime, delta), sac); + ++evit; } - entry->sec = time; - entry->depth = depth; + plot_data &entry = add_entry(pi); + plot_data &prev = pi.entry[pi.entry.size() - 2]; + entry.sec = time; + entry.depth = depth; - entry->running_sum = (entry - 1)->running_sum + (time - (entry - 1)->sec) * (depth + (entry - 1)->depth) / 2; - entry->stopdepth = sample->stopdepth.mm; - entry->stoptime = sample->stoptime.seconds; - entry->ndl = sample->ndl.seconds; - entry->tts = sample->tts.seconds; - entry->in_deco = sample->in_deco; - entry->cns = sample->cns; + entry.running_sum = prev.running_sum + (time - prev.sec) * (depth + prev.depth) / 2; + entry.stopdepth = sample.stopdepth.mm; + entry.stoptime = sample.stoptime.seconds; + entry.ndl = sample.ndl.seconds; + entry.tts = sample.tts.seconds; + entry.in_deco = sample.in_deco; + entry.cns = sample.cns; if (dc->divemode == CCR || (dc->divemode == PSCR && dc->no_o2sensors)) { - entry->o2pressure.mbar = entry->o2setpoint.mbar = sample->setpoint.mbar; // for rebreathers + entry.o2pressure.mbar = entry.o2setpoint.mbar = sample.setpoint.mbar; // for rebreathers int i; for (i = 0; i < MAX_O2_SENSORS; i++) - entry->o2sensor[i].mbar = sample->o2sensor[i].mbar; + entry.o2sensor[i].mbar = sample.o2sensor[i].mbar; } else { - entry->pressures.o2 = sample->setpoint.mbar / 1000.0; + entry.pressures.o2 = sample.setpoint.mbar / 1000.0; } - if (sample->pressure[0].mbar && sample->sensor[0] != NO_SENSOR) - set_plot_pressure_data(pi, idx, SENSOR_PR, sample->sensor[0], sample->pressure[0].mbar); - if (sample->pressure[1].mbar && sample->sensor[1] != NO_SENSOR) - set_plot_pressure_data(pi, idx, SENSOR_PR, sample->sensor[1], sample->pressure[1].mbar); - if (sample->temperature.mkelvin) - entry->temperature = lasttemp = sample->temperature.mkelvin; + if (sample.pressure[0].mbar && sample.sensor[0] != NO_SENSOR) + set_plot_pressure_data(pi, pi.entry.size() - 1, SENSOR_PR, sample.sensor[0], sample.pressure[0].mbar); + if (sample.pressure[1].mbar && sample.sensor[1] != NO_SENSOR) + set_plot_pressure_data(pi, pi.entry.size() - 1, SENSOR_PR, sample.sensor[1], sample.pressure[1].mbar); + if (sample.temperature.mkelvin) + entry.temperature = lasttemp = sample.temperature.mkelvin; else - entry->temperature = lasttemp; - entry->heartbeat = sample->heartbeat; - entry->bearing = sample->bearing.degrees; - entry->sac = sample->sac.mliter; - if (sample->rbt.seconds) - entry->rbt = sample->rbt.seconds; + entry.temperature = lasttemp; + entry.heartbeat = sample.heartbeat; + entry.bearing = sample.bearing.degrees; + entry.sac = sample.sac.mliter; + if (sample.rbt.seconds) + entry.rbt = sample.rbt.seconds; /* skip events that happened at this time */ - while (ev && (int)ev->time.seconds == time) - ev = ev->next; + while (evit != dc->events.end() && static_cast(evit->time.seconds) == time) + ++evit; lasttime = time; lastdepth = depth; - idx++; - if (time > maxtime) + if (time > pi.maxtime) break; } /* Add any remaining events */ - while (ev) { - struct plot_data *entry = plot_data + idx; - int time = ev->time.seconds; + while (evit != dc->events.end()) { + int time = evit->time.seconds; if (time > lasttime) { - insert_entry(pi, idx, ev->time.seconds, 0, 0); + insert_entry(pi, evit->time.seconds, 0, 0); lasttime = time; - idx++; - entry++; } - ev = ev->next; + ++evit; } /* Add two final surface events */ - plot_data[idx++].sec = lasttime + 1; - plot_data[idx++].sec = lasttime + 2; - pi->nr = idx; + add_entry(pi).sec = lasttime + 1; + add_entry(pi).sec = lasttime + 2; + pi.nr = (int)pi.entry.size(); } /* @@ -540,42 +463,38 @@ static void populate_plot_entries(const struct dive *dive, const struct divecomp * * Everything in between has a cylinder pressure for at least some of the cylinders. */ -static int sac_between(const struct dive *dive, struct plot_info *pi, int first, int last, const char gases[]) +static int sac_between(const struct dive *dive, const struct plot_info &pi, int first, int last, const char gases[]) { - int i, airuse; - double pressuretime; - if (first == last) return 0; /* Get airuse for the set of cylinders over the range */ - airuse = 0; - for (i = 0; i < pi->nr_cylinders; i++) { + volume_t airuse; + for (int i = 0; i < pi.nr_cylinders; i++) { pressure_t a, b; - cylinder_t *cyl; - int cyluse; if (!gases[i]) continue; a.mbar = get_plot_pressure(pi, first, i); b.mbar = get_plot_pressure(pi, last, i); - cyl = get_cylinder(dive, i); - cyluse = gas_volume(cyl, a) - gas_volume(cyl, b); - if (cyluse > 0) + const cylinder_t *cyl = dive->get_cylinder(i); + // TODO: Implement addition/subtraction on units.h types + volume_t cyluse = cyl->gas_volume(a) - cyl->gas_volume(b); + if (cyluse.mliter > 0) airuse += cyluse; } - if (!airuse) + if (!airuse.mliter) return 0; /* Calculate depthpressure integrated over time */ - pressuretime = 0.0; + double pressuretime = 0.0; do { - struct plot_data *entry = pi->entry + first; - struct plot_data *next = entry + 1; - int depth = (entry->depth + next->depth) / 2; - int time = next->sec - entry->sec; - double atm = depth_to_atm(depth, dive); + const struct plot_data &entry = pi.entry[first]; + const struct plot_data &next = pi.entry[first + 1]; + int depth = (entry.depth + next.depth) / 2; + int time = next.sec - entry.sec; + double atm = dive->depth_to_atm(depth); pressuretime += atm * time; } while (++first < last); @@ -584,15 +503,15 @@ static int sac_between(const struct dive *dive, struct plot_info *pi, int first, pressuretime /= 60; /* SAC = mliter per minute */ - return lrint(airuse / pressuretime); + return lrint(airuse.mliter / pressuretime); } /* Is there pressure data for all gases? */ -static bool all_pressures(struct plot_info *pi, int idx, const char gases[]) +static bool all_pressures(const struct plot_info &pi, int idx, const char gases[]) { int i; - for (i = 0; i < pi->nr_cylinders; i++) { + for (i = 0; i < pi.nr_cylinders; i++) { if (gases[i] && !get_plot_pressure(pi, idx, i)) return false; } @@ -601,12 +520,12 @@ static bool all_pressures(struct plot_info *pi, int idx, const char gases[]) } /* Which of the set of gases have pressure data? Returns false if none of them. */ -static bool filter_pressures(struct plot_info *pi, int idx, const char gases_in[], char gases_out[]) +static bool filter_pressures(const struct plot_info &pi, int idx, const char gases_in[], char gases_out[]) { int i; bool has_pressure = false; - for (i = 0; i < pi->nr_cylinders; i++) { + for (i = 0; i < pi.nr_cylinders; i++) { gases_out[i] = gases_in[i] && get_plot_pressure(pi, idx, i); has_pressure |= gases_out[i]; } @@ -620,13 +539,13 @@ static bool filter_pressures(struct plot_info *pi, int idx, const char gases_in[ * an array of gases, the caller passes in scratch memory in the last * argument. */ -static void fill_sac(const struct dive *dive, struct plot_info *pi, int idx, const char gases_in[], char gases[]) +static void fill_sac(const struct dive *dive, struct plot_info &pi, int idx, const char gases_in[], char gases[]) { - struct plot_data *entry = pi->entry + idx; + struct plot_data &entry = pi.entry[idx]; int first, last; int time; - if (entry->sac) + if (entry.sac) return; /* @@ -641,14 +560,14 @@ static void fill_sac(const struct dive *dive, struct plot_info *pi, int idx, con * Stop if the cylinder pressure data set changes. */ first = idx; - time = entry->sec - 30; + time = entry.sec - 30; while (idx > 0) { - struct plot_data *entry = pi->entry + idx; - struct plot_data *prev = pi->entry + idx - 1; + const struct plot_data &entry = pi.entry[idx]; + const struct plot_data &prev = pi.entry[idx - 1]; - if (prev->depth < SURFACE_THRESHOLD && entry->depth < SURFACE_THRESHOLD) + if (prev.depth < SURFACE_THRESHOLD && entry.depth < SURFACE_THRESHOLD) break; - if (prev->sec < time) + if (prev.sec < time) break; if (!all_pressures(pi, idx - 1, gases)) break; @@ -658,13 +577,13 @@ static void fill_sac(const struct dive *dive, struct plot_info *pi, int idx, con /* Now find an entry a minute after the first one */ last = first; - time = pi->entry[first].sec + 60; - while (++idx < pi->nr) { - struct plot_data *entry = pi->entry + last; - struct plot_data *next = pi->entry + last + 1; - if (next->depth < SURFACE_THRESHOLD && entry->depth < SURFACE_THRESHOLD) + time = pi.entry[first].sec + 60; + while (++idx < pi.nr) { + const struct plot_data &entry = pi.entry[last]; + const struct plot_data &next = pi.entry[last + 1]; + if (next.depth < SURFACE_THRESHOLD && entry.depth < SURFACE_THRESHOLD) break; - if (next->sec > time) + if (next.sec > time) break; if (!all_pressures(pi, idx + 1, gases)) break; @@ -672,7 +591,7 @@ static void fill_sac(const struct dive *dive, struct plot_info *pi, int idx, con } /* Ok, now calculate the SAC between 'first' and 'last' */ - entry->sac = sac_between(dive, pi, first, last, gases); + entry.sac = sac_between(dive, pi, first, last, gases); } /* @@ -680,26 +599,23 @@ static void fill_sac(const struct dive *dive, struct plot_info *pi, int idx, con */ static void matching_gases(const struct dive *dive, struct gasmix gasmix, char gases[]) { - int i; - - for (i = 0; i < dive->cylinders.nr; i++) - gases[i] = same_gasmix(gasmix, get_cylinder(dive, i)->gasmix); + for (auto [i, cyl]: enumerated_range(dive->cylinders)) + gases[i] = same_gasmix(gasmix, cyl.gasmix); } -static void calculate_sac(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi) +static void calculate_sac(const struct dive *dive, const struct divecomputer *dc, struct plot_info &pi) { - struct gasmix gasmix = gasmix_invalid; - const struct event *ev = NULL; - - std::vector gases(pi->nr_cylinders, false); + std::vector gases(pi.nr_cylinders, false); /* This might be premature optimization, but let's allocate the gas array for * the fill_sac function only once an not once per sample */ - std::vector gases_scratch(pi->nr_cylinders); + std::vector gases_scratch(pi.nr_cylinders); - for (int i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - struct gasmix newmix = get_gasmix(dive, dc, entry->sec, &ev, gasmix); + struct gasmix gasmix = gasmix_invalid; + gasmix_loop loop(*dive, *dc); + for (int i = 0; i < pi.nr; i++) { + const struct plot_data &entry = pi.entry[i]; + struct gasmix newmix = loop.at(entry.sec).first; if (!same_gasmix(newmix, gasmix)) { gasmix = newmix; matching_gases(dive, newmix, gases.data()); @@ -709,27 +625,28 @@ static void calculate_sac(const struct dive *dive, const struct divecomputer *dc } } -static void populate_secondary_sensor_data(const struct divecomputer *dc, struct plot_info *pi) +static void populate_secondary_sensor_data(const struct divecomputer &dc, struct plot_info &pi) { - std::vector seen(pi->nr_cylinders, 0); - for (int idx = 0; idx < pi->nr; ++idx) - for (int c = 0; c < pi->nr_cylinders; ++c) + std::vector seen(pi.nr_cylinders, 0); + for (int idx = 0; idx < pi.nr; ++idx) + for (int c = 0; c < pi.nr_cylinders; ++c) if (get_plot_pressure_data(pi, idx, SENSOR_PR, c)) ++seen[c]; // Count instances so we can differentiate a real sensor from just start and end pressure int idx = 0; /* We should try to see if it has interesting pressure data here */ - for (int i = 0; i < dc->samples && idx < pi->nr; i++) { - struct sample *sample = dc->sample + i; - for (; idx < pi->nr; ++idx) { - if (idx == pi->nr - 1 || pi->entry[idx].sec >= sample->time.seconds) + for (const auto &sample: dc.samples) { + if (idx >= pi.nr) + break; + for (; idx < pi.nr; ++idx) { + if (idx == pi.nr - 1 || pi.entry[idx].sec >= sample.time.seconds) // We've either found the entry at or just after the sample's time, // or this is the last entry so use for the last sensor readings if there are any. break; } for (int s = 0; s < MAX_SENSORS; ++s) // Copy sensor data if available, but don't add if this dc already has sensor data - if (sample->sensor[s] != NO_SENSOR && seen[sample->sensor[s]] < 3 && sample->pressure[s].mbar) - set_plot_pressure_data(pi, idx, SENSOR_PR, sample->sensor[s], sample->pressure[s].mbar); + if (sample.sensor[s] != NO_SENSOR && seen[sample.sensor[s]] < 3 && sample.pressure[s].mbar) + set_plot_pressure_data(pi, idx, SENSOR_PR, sample.sensor[s], sample.pressure[s].mbar); } } @@ -737,62 +654,56 @@ static void populate_secondary_sensor_data(const struct divecomputer *dc, struct * This adds a pressure entry to the plot_info based on the gas change * information and the manually filled in pressures. */ -static void add_plot_pressure(struct plot_info *pi, int time, int cyl, pressure_t p) +static void add_plot_pressure(struct plot_info &pi, int time, int cyl, pressure_t p) { - for (int i = 0; i < pi->nr; i++) { - if (i == pi->nr - 1 || pi->entry[i].sec >= time) { + for (int i = 0; i < pi.nr; i++) { + if (i == pi.nr - 1 || pi.entry[i].sec >= time) { set_plot_pressure_data(pi, i, SENSOR_PR, cyl, p.mbar); return; } } } -static void setup_gas_sensor_pressure(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi) +static void setup_gas_sensor_pressure(const struct dive *dive, const struct divecomputer *dc, struct plot_info &pi) { - int i; - const struct event *ev; - - if (pi->nr_cylinders == 0) + if (pi.nr_cylinders == 0) return; /* FIXME: The planner uses a dummy one-past-end cylinder for surface air! */ - int num_cyl = pi->nr_cylinders + 1; - std::vector seen(num_cyl, 0); + int num_cyl = pi.nr_cylinders + 1; + std::vector seen(num_cyl, false); std::vector first(num_cyl, 0); std::vector last(num_cyl, INT_MAX); - const struct divecomputer *secondary; - int prev = explicit_first_cylinder(dive, dc); - prev = prev >= 0 ? prev : 0; - seen[prev] = 1; + int prev = -1; + gasmix_loop loop(*dive, *dc); + while (loop.has_next()) { + auto [cylinder_index, time] = loop.next_cylinder_index(); - for (ev = get_next_event(dc->events, "gaschange"); ev != NULL; ev = get_next_event(ev->next, "gaschange")) { - int cyl = ev->gas.index; - int sec = ev->time.seconds; - - if (cyl < 0) + if (cylinder_index < 0) continue; // unknown cylinder - if (cyl >= num_cyl) { - report_info("setup_gas_sensor_pressure(): invalid cylinder idx %d", cyl); + if (cylinder_index >= num_cyl) { + report_info("setup_gas_sensor_pressure(): invalid cylinder idx %d", cylinder_index); continue; } - last[prev] = sec; - prev = cyl; + if (prev >= 0) { + last[prev] = time; - last[cyl] = sec; - if (!seen[cyl]) { - // The end time may be updated by a subsequent cylinder change - first[cyl] = sec; - seen[cyl] = 1; + if (!seen[cylinder_index]) + first[cylinder_index] = time; } + + seen[cylinder_index] = true; + + prev = cylinder_index; } last[prev] = INT_MAX; // Fill in "seen[]" array - mark cylinders we're not interested // in as negative. - for (i = 0; i < pi->nr_cylinders; i++) { - const cylinder_t *cyl = get_cylinder(dive, i); + for (int i = 0; i < pi.nr_cylinders; i++) { + const cylinder_t *cyl = dive->get_cylinder(i); int start = cyl->start.mbar; int end = cyl->end.mbar; @@ -803,7 +714,8 @@ static void setup_gas_sensor_pressure(const struct dive *dive, const struct dive * to plot pressures for even if we've seen it.. */ if (!start || !end || start == end) { - seen[i] = -1; + seen[i] = false; + continue; } @@ -811,18 +723,19 @@ static void setup_gas_sensor_pressure(const struct dive *dive, const struct dive if (seen[i]) continue; - /* If it's only mentioned by other dc's, ignore it */ - for_each_dc(dive, secondary) { - if (has_gaschange_event(dive, secondary, i)) { - seen[i] = -1; - break; - } - } + /* If it's mentioned by other dcs, ignore it */ + bool other_divecomputer = false; + for (auto &secondary: dive->dcs) + if (dive->has_gaschange_event(&secondary, i)) + other_divecomputer = true; + + if (!other_divecomputer) + seen[i] = true; } - for (i = 0; i < pi->nr_cylinders; i++) { - if (seen[i] >= 0) { - const cylinder_t *cyl = get_cylinder(dive, i); + for (int i = 0; i < pi.nr_cylinders; i++) { + if (seen[i]) { + const cylinder_t *cyl = dive->get_cylinder(i); add_plot_pressure(pi, first[i], i, cyl->start); add_plot_pressure(pi, last[i], i, cyl->end); @@ -834,16 +747,15 @@ static void setup_gas_sensor_pressure(const struct dive *dive, const struct dive * and try to see if they have sensor data different from the * current dive computer (dc). */ - secondary = &dive->dc; - do { - if (secondary == dc) + for (auto &secondary: dive->dcs) { + if (&secondary == dc) continue; populate_secondary_sensor_data(secondary, pi); - } while ((secondary = secondary->next) != NULL); + } } /* calculate DECO STOP / TTS / NDL */ -static void calculate_ndl_tts(struct deco_state *ds, const struct dive *dive, struct plot_data *entry, struct gasmix gasmix, +static void calculate_ndl_tts(struct deco_state *ds, const struct dive *dive, struct plot_data &entry, struct gasmix gasmix, double surface_pressure, enum divemode_t divemode, bool in_planner) { /* should this be configurable? */ @@ -855,67 +767,67 @@ static void calculate_ndl_tts(struct deco_state *ds, const struct dive *dive, st const int deco_stepsize = M_OR_FT(3, 10); /* at what depth is the current deco-step? */ int next_stop = round_up(deco_allowed_depth( - tissue_tolerance_calc(ds, dive, depth_to_bar(entry->depth, dive), in_planner), + tissue_tolerance_calc(ds, dive, dive->depth_to_bar(entry.depth), in_planner), surface_pressure, dive, 1), deco_stepsize); - int ascent_depth = entry->depth; + int ascent_depth = entry.depth; /* at what time should we give up and say that we got enuff NDL? */ - /* If iterating through a dive, entry->tts_calc needs to be reset */ - entry->tts_calc = 0; + /* If iterating through a dive, entry.tts_calc needs to be reset */ + entry.tts_calc = 0; /* If we don't have a ceiling yet, calculate ndl. Don't try to calculate * a ndl for lower values than 3m it would take forever */ if (next_stop == 0) { - if (entry->depth < 3000) { - entry->ndl = MAX_PROFILE_DECO; + if (entry.depth < 3000) { + entry.ndl = MAX_PROFILE_DECO; return; } /* stop if the ndl is above max_ndl seconds, and call it plenty of time */ - while (entry->ndl_calc < MAX_PROFILE_DECO && - deco_allowed_depth(tissue_tolerance_calc(ds, dive, depth_to_bar(entry->depth, dive), in_planner), + while (entry.ndl_calc < MAX_PROFILE_DECO && + deco_allowed_depth(tissue_tolerance_calc(ds, dive, dive->depth_to_bar(entry.depth), in_planner), surface_pressure, dive, 1) <= 0 ) { - entry->ndl_calc += time_stepsize; - add_segment(ds, depth_to_bar(entry->depth, dive), - gasmix, time_stepsize, entry->o2pressure.mbar, divemode, prefs.bottomsac, in_planner); + entry.ndl_calc += time_stepsize; + add_segment(ds, dive->depth_to_bar(entry.depth), + gasmix, time_stepsize, entry.o2pressure.mbar, divemode, prefs.bottomsac, in_planner); } /* we don't need to calculate anything else */ return; } /* We are in deco */ - entry->in_deco_calc = true; + entry.in_deco_calc = true; /* Add segments for movement to stopdepth */ - for (; ascent_depth > next_stop; ascent_depth -= ascent_s_per_step * ascent_velocity(ascent_depth, entry->running_sum / entry->sec, 0), entry->tts_calc += ascent_s_per_step) { - add_segment(ds, depth_to_bar(ascent_depth, dive), - gasmix, ascent_s_per_step, entry->o2pressure.mbar, divemode, prefs.decosac, in_planner); - next_stop = round_up(deco_allowed_depth(tissue_tolerance_calc(ds, dive, depth_to_bar(ascent_depth, dive), in_planner), + for (; ascent_depth > next_stop; ascent_depth -= ascent_s_per_step * ascent_velocity(ascent_depth, entry.running_sum / entry.sec, 0), entry.tts_calc += ascent_s_per_step) { + add_segment(ds, dive->depth_to_bar(ascent_depth), + gasmix, ascent_s_per_step, entry.o2pressure.mbar, divemode, prefs.decosac, in_planner); + next_stop = round_up(deco_allowed_depth(tissue_tolerance_calc(ds, dive, dive->depth_to_bar(ascent_depth), in_planner), surface_pressure, dive, 1), deco_stepsize); } ascent_depth = next_stop; /* And how long is the current deco-step? */ - entry->stoptime_calc = 0; - entry->stopdepth_calc = next_stop; + entry.stoptime_calc = 0; + entry.stopdepth_calc = next_stop; next_stop -= deco_stepsize; /* And how long is the total TTS */ while (next_stop >= 0) { /* save the time for the first stop to show in the graph */ - if (ascent_depth == entry->stopdepth_calc) - entry->stoptime_calc += time_stepsize; + if (ascent_depth == entry.stopdepth_calc) + entry.stoptime_calc += time_stepsize; - entry->tts_calc += time_stepsize; - if (entry->tts_calc > MAX_PROFILE_DECO) + entry.tts_calc += time_stepsize; + if (entry.tts_calc > MAX_PROFILE_DECO) break; - add_segment(ds, depth_to_bar(ascent_depth, dive), - gasmix, time_stepsize, entry->o2pressure.mbar, divemode, prefs.decosac, in_planner); + add_segment(ds, dive->depth_to_bar(ascent_depth), + gasmix, time_stepsize, entry.o2pressure.mbar, divemode, prefs.decosac, in_planner); - if (deco_allowed_depth(tissue_tolerance_calc(ds, dive, depth_to_bar(ascent_depth,dive), in_planner), surface_pressure, dive, 1) <= next_stop) { + if (deco_allowed_depth(tissue_tolerance_calc(ds, dive, dive->depth_to_bar(ascent_depth), in_planner), surface_pressure, dive, 1) <= next_stop) { /* move to the next stop and add the travel between stops */ - for (; ascent_depth > next_stop; ascent_depth -= ascent_s_per_deco_step * ascent_velocity(ascent_depth, entry->running_sum / entry->sec, 0), entry->tts_calc += ascent_s_per_deco_step) - add_segment(ds, depth_to_bar(ascent_depth, dive), - gasmix, ascent_s_per_deco_step, entry->o2pressure.mbar, divemode, prefs.decosac, in_planner); + for (; ascent_depth > next_stop; ascent_depth -= ascent_s_per_deco_step * ascent_velocity(ascent_depth, entry.running_sum / entry.sec, 0), entry.tts_calc += ascent_s_per_deco_step) + add_segment(ds, dive->depth_to_bar(ascent_depth), + gasmix, ascent_s_per_deco_step, entry.o2pressure.mbar, divemode, prefs.decosac, in_planner); ascent_depth = next_stop; next_stop -= deco_stepsize; } @@ -925,17 +837,17 @@ static void calculate_ndl_tts(struct deco_state *ds, const struct dive *dive, st /* Let's try to do some deco calculations. */ static void calculate_deco_information(struct deco_state *ds, const struct deco_state *planner_ds, const struct dive *dive, - const struct divecomputer *dc, struct plot_info *pi) + const struct divecomputer *dc, struct plot_info &pi) { int i, count_iteration = 0; - double surface_pressure = (dc->surface_pressure.mbar ? dc->surface_pressure.mbar : get_surface_pressure_in_mbar(dive, true)) / 1000.0; + double surface_pressure = (dc->surface_pressure.mbar ? dc->surface_pressure.mbar : dive->get_surface_pressure().mbar) / 1000.0; bool first_iteration = true; int prev_deco_time = 10000000, time_deep_ceiling = 0; bool in_planner = planner_ds != NULL; if (!in_planner) { ds->deco_time = 0; - ds->first_ceiling_pressure.mbar = 0; + ds->first_ceiling_pressure = 0_bar; } else { ds->deco_time = planner_ds->deco_time; ds->first_ceiling_pressure = planner_ds->first_ceiling_pressure; @@ -952,38 +864,36 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ while ((abs(prev_deco_time - ds->deco_time) >= 30) && (count_iteration < 10)) { int last_ndl_tts_calc_time = 0, first_ceiling = 0, current_ceiling, last_ceiling = 0, final_tts = 0 , time_clear_ceiling = 0; if (decoMode(in_planner) == VPMB) - ds->first_ceiling_pressure.mbar = depth_to_mbar(first_ceiling, dive); - struct gasmix gasmix = gasmix_invalid; - const struct event *ev = NULL, *evd = NULL; - enum divemode_t current_divemode = UNDEF_COMP_TYPE; + ds->first_ceiling_pressure.mbar = dive->depth_to_mbar(first_ceiling); - for (i = 1; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - int j, t0 = (entry - 1)->sec, t1 = entry->sec; + gasmix_loop loop(*dive, *dc); + divemode_loop loop_d(*dc); + for (i = 1; i < pi.nr; i++) { + struct plot_data &entry = pi.entry[i]; + struct plot_data &prev = pi.entry[i - 1]; + int j, t0 = prev.sec, t1 = entry.sec; int time_stepsize = 20, max_ceiling = -1; - current_divemode = get_current_divemode(dc, entry->sec, &evd, ¤t_divemode); - gasmix = get_gasmix(dive, dc, t1, &ev, gasmix); - entry->ambpressure = depth_to_bar(entry->depth, dive); - entry->gfline = get_gf(ds, entry->ambpressure, dive) * (100.0 - AMB_PERCENTAGE) + AMB_PERCENTAGE; + divemode_t current_divemode = loop_d.at(entry.sec); + struct gasmix gasmix = loop.at(t1).first; + entry.ambpressure = dive->depth_to_bar(entry.depth); + entry.gfline = get_gf(ds, entry.ambpressure, dive) * (100.0 - AMB_PERCENTAGE) + AMB_PERCENTAGE; if (t0 > t1) { report_info("non-monotonous dive stamps %d %d", t0, t1); - int xchg = t1; - t1 = t0; - t0 = xchg; + std::swap(t0, t1); } if (t0 != t1 && t1 - t0 < time_stepsize) time_stepsize = t1 - t0; for (j = t0 + time_stepsize; j <= t1; j += time_stepsize) { - int depth = interpolate(entry[-1].depth, entry[0].depth, j - t0, t1 - t0); - add_segment(ds, depth_to_bar(depth, dive), - gasmix, time_stepsize, entry->o2pressure.mbar, current_divemode, entry->sac, in_planner); - entry->icd_warning = ds->icd_warning; + int depth = interpolate(prev.depth, entry.depth, j - t0, t1 - t0); + add_segment(ds, dive->depth_to_bar(depth), + gasmix, time_stepsize, entry.o2pressure.mbar, current_divemode, entry.sac, in_planner); + entry.icd_warning = ds->icd_warning; if ((t1 - j < time_stepsize) && (j < t1)) time_stepsize = t1 - j; } if (t0 == t1) { - entry->ceiling = (entry - 1)->ceiling; + entry.ceiling = prev.ceiling; } else { /* Keep updating the VPM-B gradients until the start of the ascent phase of the dive. */ if (decoMode(in_planner) == VPMB && last_ceiling >= first_ceiling && first_iteration == true) { @@ -993,19 +903,19 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ if (!first_iteration || in_planner) vpmb_next_gradient(ds, ds->deco_time, surface_pressure / 1000.0, in_planner); } - entry->ceiling = deco_allowed_depth(tissue_tolerance_calc(ds, dive, depth_to_bar(entry->depth, dive), in_planner), surface_pressure, dive, !prefs.calcceiling3m); + entry.ceiling = deco_allowed_depth(tissue_tolerance_calc(ds, dive, dive->depth_to_bar(entry.depth), in_planner), surface_pressure, dive, !prefs.calcceiling3m); if (prefs.calcceiling3m) - current_ceiling = deco_allowed_depth(tissue_tolerance_calc(ds, dive, depth_to_bar(entry->depth, dive), in_planner), surface_pressure, dive, true); + current_ceiling = deco_allowed_depth(tissue_tolerance_calc(ds, dive, dive->depth_to_bar(entry.depth), in_planner), surface_pressure, dive, true); else - current_ceiling = entry->ceiling; + current_ceiling = entry.ceiling; last_ceiling = current_ceiling; /* If using VPM-B, take first_ceiling_pressure as the deepest ceiling */ if (decoMode(in_planner) == VPMB) { if (current_ceiling >= first_ceiling || - (time_deep_ceiling == t0 && entry->depth == (entry - 1)->depth)) { + (time_deep_ceiling == t0 && entry.depth == prev.depth)) { time_deep_ceiling = t1; first_ceiling = current_ceiling; - ds->first_ceiling_pressure.mbar = depth_to_mbar(first_ceiling, dive); + ds->first_ceiling_pressure.mbar = dive->depth_to_mbar(first_ceiling); if (first_iteration) { nuclear_regeneration(ds, t1); vpmb_start_gradient(ds); @@ -1013,7 +923,7 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ but we want to over-estimate deco_time for the first iteration so it converges correctly, so add 30min*/ if (!in_planner) - ds->deco_time = pi->maxtime - t1 + 1800; + ds->deco_time = pi.maxtime - t1 + 1800; vpmb_next_gradient(ds, ds->deco_time, surface_pressure / 1000.0, in_planner); } } @@ -1024,23 +934,23 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ time_clear_ceiling = t1; } } - entry->surface_gf = 0.0; - entry->current_gf = 0.0; + entry.surface_gf = 0.0; + entry.current_gf = 0.0; for (j = 0; j < 16; j++) { - double m_value = ds->buehlmann_inertgas_a[j] + entry->ambpressure / ds->buehlmann_inertgas_b[j]; + double m_value = ds->buehlmann_inertgas_a[j] + entry.ambpressure / ds->buehlmann_inertgas_b[j]; double surface_m_value = ds->buehlmann_inertgas_a[j] + surface_pressure / ds->buehlmann_inertgas_b[j]; - entry->ceilings[j] = deco_allowed_depth(ds->tolerated_by_tissue[j], surface_pressure, dive, 1); - if (entry->ceilings[j] > max_ceiling) - max_ceiling = entry->ceilings[j]; - double current_gf = (ds->tissue_inertgas_saturation[j] - entry->ambpressure) / (m_value - entry->ambpressure); - entry->percentages[j] = ds->tissue_inertgas_saturation[j] < entry->ambpressure ? - lrint(ds->tissue_inertgas_saturation[j] / entry->ambpressure * AMB_PERCENTAGE) : + entry.ceilings[j] = deco_allowed_depth(ds->tolerated_by_tissue[j], surface_pressure, dive, 1); + if (entry.ceilings[j] > max_ceiling) + max_ceiling = entry.ceilings[j]; + double current_gf = (ds->tissue_inertgas_saturation[j] - entry.ambpressure) / (m_value - entry.ambpressure); + entry.percentages[j] = ds->tissue_inertgas_saturation[j] < entry.ambpressure ? + lrint(ds->tissue_inertgas_saturation[j] / entry.ambpressure * AMB_PERCENTAGE) : lrint(AMB_PERCENTAGE + current_gf * (100.0 - AMB_PERCENTAGE)); - if (current_gf > entry->current_gf) - entry->current_gf = current_gf; + if (current_gf > entry.current_gf) + entry.current_gf = current_gf; double surface_gf = 100.0 * (ds->tissue_inertgas_saturation[j] - surface_pressure) / (surface_m_value - surface_pressure); - if (surface_gf > entry->surface_gf) - entry->surface_gf = surface_gf; + if (surface_gf > entry.surface_gf) + entry.surface_gf = surface_gf; } // In the planner, if the ceiling is violated, add an event. @@ -1049,12 +959,12 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ // that can be trampled upon. But ultimately, the ceiling-violation // marker should be handled differently! // Don't scream if we violate the ceiling by a few cm. - if (in_planner && !pi->waypoint_above_ceiling && - entry->depth < max_ceiling - 100 && entry->sec > 0) { + if (in_planner && !pi.waypoint_above_ceiling && + entry.depth < max_ceiling - 100 && entry.sec > 0) { struct dive *non_const_dive = (struct dive *)dive; // cast away const! - add_event(&non_const_dive->dc, entry->sec, SAMPLE_EVENT_CEILING, -1, max_ceiling / 1000, + add_event(&non_const_dive->dcs[0], entry.sec, SAMPLE_EVENT_CEILING, -1, max_ceiling / 1000, translate("gettextFromC", "planned waypoint above ceiling")); - pi->waypoint_above_ceiling = true; + pi.waypoint_above_ceiling = true; } /* should we do more calculations? @@ -1062,24 +972,24 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ * If the ceiling hasn't cleared by the last data point, we need tts for VPM-B CVA calculation * It is not necessary to do these calculation on the first VPMB iteration, except for the last data point */ if ((prefs.calcndltts && (decoMode(in_planner) != VPMB || in_planner || !first_iteration)) || - (decoMode(in_planner) == VPMB && !in_planner && i == pi->nr - 1)) { + (decoMode(in_planner) == VPMB && !in_planner && i == pi.nr - 1)) { /* only calculate ndl/tts on every 30 seconds */ - if ((entry->sec - last_ndl_tts_calc_time) < 30 && i != pi->nr - 1) { - struct plot_data *prev_entry = (entry - 1); - entry->stoptime_calc = prev_entry->stoptime_calc; - entry->stopdepth_calc = prev_entry->stopdepth_calc; - entry->tts_calc = prev_entry->tts_calc; - entry->ndl_calc = prev_entry->ndl_calc; + if ((entry.sec - last_ndl_tts_calc_time) < 30 && i != pi.nr - 1) { + struct plot_data &prev_entry = pi.entry[i - 1]; + entry.stoptime_calc = prev_entry.stoptime_calc; + entry.stopdepth_calc = prev_entry.stopdepth_calc; + entry.tts_calc = prev_entry.tts_calc; + entry.ndl_calc = prev_entry.ndl_calc; continue; } - last_ndl_tts_calc_time = entry->sec; + last_ndl_tts_calc_time = entry.sec; /* We are going to mess up deco state, so store it for later restore */ deco_state_cache cache_data; cache_data.cache(ds); calculate_ndl_tts(ds, dive, entry, gasmix, surface_pressure, current_divemode, in_planner); - if (decoMode(in_planner) == VPMB && !in_planner && i == pi->nr - 1) - final_tts = entry->tts_calc; + if (decoMode(in_planner) == VPMB && !in_planner && i == pi.nr - 1) + final_tts = entry.tts_calc; /* Restore "real" deco state for next real time step */ cache_data.restore(ds, decoMode(in_planner) == VPMB); } @@ -1120,17 +1030,17 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_ /* Sort the o2 pressure values. There are so few that a simple bubble sort * will do */ -extern "C" void sort_o2_pressures(int *sensorn, int np, struct plot_data *entry) +void sort_o2_pressures(int *sensorn, int np, const struct plot_data &entry) { int smallest, position, old; for (int i = 0; i < np - 1; i++) { position = i; - smallest = entry->o2sensor[sensorn[i]].mbar; + smallest = entry.o2sensor[sensorn[i]].mbar; for (int j = i+1; j < np; j++) - if (entry->o2sensor[sensorn[j]].mbar < smallest) { + if (entry.o2sensor[sensorn[j]].mbar < smallest) { position = j; - smallest = entry->o2sensor[sensorn[j]].mbar; + smallest = entry.o2sensor[sensorn[j]].mbar; } old = sensorn[i]; sensorn[i] = position; @@ -1143,21 +1053,21 @@ extern "C" void sort_o2_pressures(int *sensorn, int np, struct plot_data *entry) * calculates the po2 value from the sensor data. If there are at least 3 sensors, sensors are voted out until * their span is within diff_limit. */ -static int calculate_ccr_po2(struct plot_data *entry, const struct divecomputer *dc) +static int calculate_ccr_po2(struct plot_data &entry, const struct divecomputer *dc) { int sump = 0, minp = 0, maxp = 0; int sensorn[MAX_O2_SENSORS]; int i, np = 0; for (i = 0; i < dc->no_o2sensors && i < MAX_O2_SENSORS; i++) - if (entry->o2sensor[i].mbar) { // Valid reading + if (entry.o2sensor[i].mbar) { // Valid reading sensorn[np++] = i; - sump += entry->o2sensor[i].mbar; + sump += entry.o2sensor[i].mbar; } if (np == 0) - return entry->o2pressure.mbar; + return entry.o2pressure.mbar; else if (np == 1) - return entry->o2sensor[sensorn[0]].mbar; + return entry.o2sensor[sensorn[0]].mbar; maxp = np - 1; sort_o2_pressures(sensorn, np, entry); @@ -1165,15 +1075,15 @@ static int calculate_ccr_po2(struct plot_data *entry, const struct divecomputer // This is the Shearwater voting logic: If there are still at least three sensors and one // differs by more than 20% from the closest it is voted out. while (maxp - minp > 1) { - if (entry->o2sensor[sensorn[minp + 1]].mbar - entry->o2sensor[sensorn[minp]].mbar > + if (entry.o2sensor[sensorn[minp + 1]].mbar - entry.o2sensor[sensorn[minp]].mbar > sump / (maxp - minp + 1) / 5) { - sump -= entry->o2sensor[sensorn[minp]].mbar; + sump -= entry.o2sensor[sensorn[minp]].mbar; ++minp; continue; } - if (entry->o2sensor[sensorn[maxp]].mbar - entry->o2sensor[sensorn[maxp - 1]].mbar > + if (entry.o2sensor[sensorn[maxp]].mbar - entry.o2sensor[sensorn[maxp - 1]].mbar > sump / (maxp - minp +1) / 5) { - sump -= entry->o2sensor[sensorn[maxp]].mbar; + sump -= entry.o2sensor[sensorn[maxp]].mbar; --maxp; continue; } @@ -1184,32 +1094,31 @@ static int calculate_ccr_po2(struct plot_data *entry, const struct divecomputer } -static double gas_density(const struct gas_pressures *pressures) +static double gas_density(const struct gas_pressures &pressures) { - return (pressures->o2 * O2_DENSITY + pressures->he * HE_DENSITY + pressures->n2 * N2_DENSITY) / 1000.0; + return (pressures.o2 * O2_DENSITY + pressures.he * HE_DENSITY + pressures.n2 * N2_DENSITY) / 1000.0; } -static void calculate_gas_information_new(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi) +static void calculate_gas_information_new(const struct dive *dive, const struct divecomputer *dc, struct plot_info &pi) { int i; double amb_pressure; - struct gasmix gasmix = gasmix_invalid; - const struct event *evg = NULL, *evd = NULL; - enum divemode_t current_divemode = UNDEF_COMP_TYPE; - for (i = 1; i < pi->nr; i++) { + gasmix_loop loop(*dive, *dc); + divemode_loop loop_d(*dc); + for (i = 1; i < pi.nr; i++) { double fn2, fhe; - struct plot_data *entry = pi->entry + i; + struct plot_data &entry = pi.entry[i]; - gasmix = get_gasmix(dive, dc, entry->sec, &evg, gasmix); - amb_pressure = depth_to_bar(entry->depth, dive); - current_divemode = get_current_divemode(dc, entry->sec, &evd, ¤t_divemode); - fill_pressures(&entry->pressures, amb_pressure, gasmix, (current_divemode == OC) ? 0.0 : entry->o2pressure.mbar / 1000.0, current_divemode); - fn2 = 1000.0 * entry->pressures.n2 / amb_pressure; - fhe = 1000.0 * entry->pressures.he / amb_pressure; + auto gasmix = loop.at(entry.sec).first; + amb_pressure = dive->depth_to_bar(entry.depth); + divemode_t current_divemode = loop_d.at(entry.sec); + entry.pressures = fill_pressures(amb_pressure, gasmix, (current_divemode == OC) ? 0.0 : entry.o2pressure.mbar / 1000.0, current_divemode); + fn2 = 1000.0 * entry.pressures.n2 / amb_pressure; + fhe = 1000.0 * entry.pressures.he / amb_pressure; if (dc->divemode == PSCR) { // OC pO2 is calulated for PSCR with or without external PO2 monitoring. - struct gasmix gasmix2 = get_gasmix(dive, dc, entry->sec, &evg, gasmix); - entry->scr_OC_pO2.mbar = (int) depth_to_mbar(entry->depth, dive) * get_o2(gasmix2) / 1000; + struct gasmix gasmix2 = loop.at(entry.sec).first; + entry.scr_OC_pO2.mbar = (int) dive->depth_to_mbar(entry.depth) * get_o2(gasmix2) / 1000; } /* Calculate MOD, EAD, END and EADD based on partial pressures calculated before @@ -1217,27 +1126,27 @@ static void calculate_gas_information_new(const struct dive *dive, const struct * END takes O₂ + N₂ (air) into account ("Narcotic" for trimix dives) * EAD just uses N₂ ("Air" for nitrox dives) */ pressure_t modpO2 = { .mbar = (int)(prefs.modpO2 * 1000) }; - entry->mod = gas_mod(gasmix, modpO2, dive, 1).mm; - entry->end = mbar_to_depth(lrint(depth_to_mbarf(entry->depth, dive) * (1000 - fhe) / 1000.0), dive); - entry->ead = mbar_to_depth(lrint(depth_to_mbarf(entry->depth, dive) * fn2 / (double)N2_IN_AIR), dive); - entry->eadd = mbar_to_depth(lrint(depth_to_mbarf(entry->depth, dive) * - (entry->pressures.o2 / amb_pressure * O2_DENSITY + - entry->pressures.n2 / amb_pressure * N2_DENSITY + - entry->pressures.he / amb_pressure * HE_DENSITY) / - (O2_IN_AIR * O2_DENSITY + N2_IN_AIR * N2_DENSITY) * 1000), dive); - entry->density = gas_density(&entry->pressures); - if (entry->mod < 0) - entry->mod = 0; - if (entry->ead < 0) - entry->ead = 0; - if (entry->end < 0) - entry->end = 0; - if (entry->eadd < 0) - entry->eadd = 0; + entry.mod = dive->gas_mod(gasmix, modpO2, 1).mm; + entry.end = dive->mbar_to_depth(lrint(dive->depth_to_mbarf(entry.depth) * (1000 - fhe) / 1000.0)); + entry.ead = dive->mbar_to_depth(lrint(dive->depth_to_mbarf(entry.depth) * fn2 / (double)N2_IN_AIR)); + entry.eadd = dive->mbar_to_depth(lrint(dive->depth_to_mbarf(entry.depth) * + (entry.pressures.o2 / amb_pressure * O2_DENSITY + + entry.pressures.n2 / amb_pressure * N2_DENSITY + + entry.pressures.he / amb_pressure * HE_DENSITY) / + (O2_IN_AIR * O2_DENSITY + N2_IN_AIR * N2_DENSITY) * 1000)); + entry.density = gas_density(entry.pressures); + if (entry.mod < 0) + entry.mod = 0; + if (entry.ead < 0) + entry.ead = 0; + if (entry.end < 0) + entry.end = 0; + if (entry.eadd < 0) + entry.eadd = 0; } } -static void fill_o2_values(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi) +static void fill_o2_values(const struct dive *dive, const struct divecomputer *dc, struct plot_info &pi) /* In the samples from each dive computer, there may be uninitialised oxygen * sensor or setpoint values, e.g. when events were inserted into the dive log * or if the dive computer does not report o2 values with every sample. But @@ -1251,25 +1160,25 @@ static void fill_o2_values(const struct dive *dive, const struct divecomputer *d pressure_t last_sensor[3], o2pressure; pressure_t amb_pressure; - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; + for (i = 0; i < pi.nr; i++) { + struct plot_data &entry = pi.entry[i]; if (dc->divemode == CCR || (dc->divemode == PSCR && dc->no_o2sensors)) { if (i == 0) { // For 1st iteration, initialise the last_sensor values for (j = 0; j < dc->no_o2sensors; j++) - last_sensor[j].mbar = pi->entry->o2sensor[j].mbar; + last_sensor[j] = entry.o2sensor[j]; } else { // Now re-insert the missing oxygen pressure values for (j = 0; j < dc->no_o2sensors; j++) - if (entry->o2sensor[j].mbar) - last_sensor[j].mbar = entry->o2sensor[j].mbar; + if (entry.o2sensor[j].mbar) + last_sensor[j] = entry.o2sensor[j]; else - entry->o2sensor[j].mbar = last_sensor[j].mbar; + entry.o2sensor[j] = last_sensor[j]; } // having initialised the empty o2 sensor values for this point on the profile, - amb_pressure.mbar = depth_to_mbar(entry->depth, dive); + amb_pressure.mbar = dive->depth_to_mbar(entry.depth); o2pressure.mbar = calculate_ccr_po2(entry, dc); // ...calculate the po2 based on the sensor data - entry->o2pressure.mbar = std::min(o2pressure.mbar, amb_pressure.mbar); + entry.o2pressure.mbar = std::min(o2pressure.mbar, amb_pressure.mbar); } else { - entry->o2pressure.mbar = 0; // initialise po2 to zero for dctype = OC + entry.o2pressure = 0_bar; // initialise po2 to zero for dctype = OC } } } @@ -1278,7 +1187,7 @@ static void fill_o2_values(const struct dive *dive, const struct divecomputer *d /* A CCR debug function that writes the cylinder pressure and the oxygen values to the file debug_print_profiledata.dat: * Called in create_plot_info_new() */ -static void debug_print_profiledata(struct plot_info *pi) +static void debug_print_profiledata(struct plot_info &pi) { FILE *f1; struct plot_data *entry; @@ -1287,25 +1196,17 @@ static void debug_print_profiledata(struct plot_info *pi) printf("File open error for: debug_print_profiledata.dat\n"); } else { fprintf(f1, "id t1 gas gasint t2 t3 dil dilint t4 t5 setpoint sensor1 sensor2 sensor3 t6 po2 fo2\n"); - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; + for (i = 0; i < pi.nr; i++) { + struct plot_data &entry = pi.entry[i]; fprintf(f1, "%d gas=%8d %8d ; dil=%8d %8d ; o2_sp= %d %d %d %d PO2= %f\n", i, get_plot_sensor_pressure(pi, i), get_plot_interpolated_pressure(pi, i), O2CYLINDER_PRESSURE(entry), INTERPOLATED_O2CYLINDER_PRESSURE(entry), - entry->o2pressure.mbar, entry->o2sensor[0].mbar, entry->o2sensor[1].mbar, entry->o2sensor[2].mbar, entry->pressures.o2); + entry.o2pressure.mbar, entry.o2sensor[0].mbar, entry.o2sensor[1].mbar, entry.o2sensor[2].mbar, entry.pressures.o2); } fclose(f1); } } #endif -/* - * Initialize a plot_info structure to all-zeroes - */ -extern "C" void init_plot_info(struct plot_info *pi) -{ - memset(pi, 0, sizeof(*pi)); -} - /* * Create a plot-info with smoothing and ranged min/max * @@ -1313,34 +1214,32 @@ extern "C" void init_plot_info(struct plot_info *pi) * sides, so that you can do end-points without having to worry * about it. * - * The old data will be freed. Before the first call, the plot - * info must be initialized with init_plot_info(). + * The old data will be freed. */ -extern "C" void create_plot_info_new(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi, const struct deco_state *planner_ds) +struct plot_info create_plot_info_new(const struct dive *dive, const struct divecomputer *dc, const struct deco_state *planner_ds) { - int o2, he, o2max; struct deco_state plot_deco_state; bool in_planner = planner_ds != NULL; - init_decompression(&plot_deco_state, dive, in_planner); - free_plot_info_data(pi); + divelog.dives.init_decompression(&plot_deco_state, dive, in_planner); + plot_info pi; calculate_max_limits_new(dive, dc, pi, in_planner); - get_dive_gas(dive, &o2, &he, &o2max); + auto [o2, he, o2max ] = dive->get_maximal_gas(); if (dc->divemode == FREEDIVE) { - pi->dive_type = plot_info::FREEDIVING; + pi.dive_type = plot_info::FREEDIVING; } else if (he > 0) { - pi->dive_type = plot_info::TRIMIX; + pi.dive_type = plot_info::TRIMIX; } else { if (o2) - pi->dive_type = plot_info::NITROX; + pi.dive_type = plot_info::NITROX; else - pi->dive_type = plot_info::AIR; + pi.dive_type = plot_info::AIR; } populate_plot_entries(dive, dc, pi); check_setpoint_events(dive, dc, pi); /* Populate setpoints */ setup_gas_sensor_pressure(dive, dc, pi); /* Try to populate our gas pressure knowledge */ - for (int cyl = 0; cyl < pi->nr_cylinders; cyl++) + for (int cyl = 0; cyl < pi.nr_cylinders; cyl++) populate_pressure_information(dive, dc, pi, cyl); fill_o2_values(dive, dc, pi); /* .. and insert the O2 sensor data having 0 values. */ calculate_sac(dive, dc, pi); /* Calculate sac */ @@ -1353,187 +1252,188 @@ extern "C" void create_plot_info_new(const struct dive *dive, const struct divec debug_print_profiledata(pi); #endif - pi->meandepth = dive->dc.meandepth.mm; + pi.meandepth = dive->dcs[0].meandepth.mm; analyze_plot_info(pi); + return pi; } -static std::vector plot_string(const struct dive *d, const struct plot_info *pi, int idx) +static std::vector plot_string(const struct dive *d, const struct plot_info &pi, int idx) { int pressurevalue, mod, ead, end, eadd; const char *depth_unit, *pressure_unit, *temp_unit, *vertical_speed_unit; double depthvalue, tempvalue, speedvalue, sacvalue; int decimals, cyl; const char *unit; - const struct plot_data *entry = pi->entry + idx; + const struct plot_data &entry = pi.entry[idx]; std::vector res; - depthvalue = get_depth_units(entry->depth, NULL, &depth_unit); - res.push_back(casprintf_loc(translate("gettextFromC", "@: %d:%02d"), FRACTION_TUPLE(entry->sec, 60))); + depthvalue = get_depth_units(entry.depth, NULL, &depth_unit); + res.push_back(casprintf_loc(translate("gettextFromC", "@: %d:%02d"), FRACTION_TUPLE(entry.sec, 60))); res.push_back(casprintf_loc(translate("gettextFromC", "D: %.1f%s"), depthvalue, depth_unit)); - for (cyl = 0; cyl < pi->nr_cylinders; cyl++) { + for (cyl = 0; cyl < pi.nr_cylinders; cyl++) { int mbar = get_plot_pressure(pi, idx, cyl); if (!mbar) continue; - struct gasmix mix = get_cylinder(d, cyl)->gasmix; + struct gasmix mix = d->get_cylinder(cyl)->gasmix; pressurevalue = get_pressure_units(mbar, &pressure_unit); - res.push_back(casprintf_loc(translate("gettextFromC", "P: %d%s (%s)"), pressurevalue, pressure_unit, gasname(mix))); + res.push_back(casprintf_loc(translate("gettextFromC", "P: %d%s (%s)"), pressurevalue, pressure_unit, mix.name().c_str())); } - if (entry->temperature) { - tempvalue = get_temp_units(entry->temperature, &temp_unit); + if (entry.temperature) { + tempvalue = get_temp_units(entry.temperature, &temp_unit); res.push_back(casprintf_loc(translate("gettextFromC", "T: %.1f%s"), tempvalue, temp_unit)); } - speedvalue = get_vertical_speed_units(abs(entry->speed), NULL, &vertical_speed_unit); + speedvalue = get_vertical_speed_units(abs(entry.speed), NULL, &vertical_speed_unit); /* Ascending speeds are positive, descending are negative */ - if (entry->speed > 0) + if (entry.speed > 0) speedvalue *= -1; res.push_back(casprintf_loc(translate("gettextFromC", "V: %.1f%s"), speedvalue, vertical_speed_unit)); - sacvalue = get_volume_units(entry->sac, &decimals, &unit); - if (entry->sac && prefs.show_sac) + sacvalue = get_volume_units(entry.sac, &decimals, &unit); + if (entry.sac && prefs.show_sac) res.push_back(casprintf_loc(translate("gettextFromC", "SAC: %.*f%s/min"), decimals, sacvalue, unit)); - if (entry->cns) - res.push_back(casprintf_loc(translate("gettextFromC", "CNS: %u%%"), entry->cns)); - if (prefs.pp_graphs.po2 && entry->pressures.o2 > 0) { - res.push_back(casprintf_loc(translate("gettextFromC", "pO₂: %.2fbar"), entry->pressures.o2)); - if (entry->scr_OC_pO2.mbar) - res.push_back(casprintf_loc(translate("gettextFromC", "SCR ΔpO₂: %.2fbar"), entry->scr_OC_pO2.mbar/1000.0 - entry->pressures.o2)); + if (entry.cns) + res.push_back(casprintf_loc(translate("gettextFromC", "CNS: %u%%"), entry.cns)); + if (prefs.pp_graphs.po2 && entry.pressures.o2 > 0) { + res.push_back(casprintf_loc(translate("gettextFromC", "pO₂: %.2fbar"), entry.pressures.o2)); + if (entry.scr_OC_pO2.mbar) + res.push_back(casprintf_loc(translate("gettextFromC", "SCR ΔpO₂: %.2fbar"), entry.scr_OC_pO2.mbar/1000.0 - entry.pressures.o2)); } - if (prefs.pp_graphs.pn2 && entry->pressures.n2 > 0) - res.push_back(casprintf_loc(translate("gettextFromC", "pN₂: %.2fbar"), entry->pressures.n2)); - if (prefs.pp_graphs.phe && entry->pressures.he > 0) - res.push_back(casprintf_loc(translate("gettextFromC", "pHe: %.2fbar"), entry->pressures.he)); - if (prefs.mod && entry->mod > 0) { - mod = lrint(get_depth_units(entry->mod, NULL, &depth_unit)); + if (prefs.pp_graphs.pn2 && entry.pressures.n2 > 0) + res.push_back(casprintf_loc(translate("gettextFromC", "pN₂: %.2fbar"), entry.pressures.n2)); + if (prefs.pp_graphs.phe && entry.pressures.he > 0) + res.push_back(casprintf_loc(translate("gettextFromC", "pHe: %.2fbar"), entry.pressures.he)); + if (prefs.mod && entry.mod > 0) { + mod = lrint(get_depth_units(entry.mod, NULL, &depth_unit)); res.push_back(casprintf_loc(translate("gettextFromC", "MOD: %d%s"), mod, depth_unit)); } - eadd = lrint(get_depth_units(entry->eadd, NULL, &depth_unit)); + eadd = lrint(get_depth_units(entry.eadd, NULL, &depth_unit)); if (prefs.ead) { - switch (pi->dive_type) { + switch (pi.dive_type) { case plot_info::NITROX: - if (entry->ead > 0) { - ead = lrint(get_depth_units(entry->ead, NULL, &depth_unit)); + if (entry.ead > 0) { + ead = lrint(get_depth_units(entry.ead, NULL, &depth_unit)); res.push_back(casprintf_loc(translate("gettextFromC", "EAD: %d%s"), ead, depth_unit)); - res.push_back(casprintf_loc(translate("gettextFromC", "EADD: %d%s / %.1fg/ℓ"), eadd, depth_unit, entry->density)); + res.push_back(casprintf_loc(translate("gettextFromC", "EADD: %d%s / %.1fg/ℓ"), eadd, depth_unit, entry.density)); break; } case plot_info::TRIMIX: - if (entry->end > 0) { - end = lrint(get_depth_units(entry->end, NULL, &depth_unit)); + if (entry.end > 0) { + end = lrint(get_depth_units(entry.end, NULL, &depth_unit)); res.push_back(casprintf_loc(translate("gettextFromC", "END: %d%s"), end, depth_unit)); - res.push_back(casprintf_loc(translate("gettextFromC", "EADD: %d%s / %.1fg/ℓ"), eadd, depth_unit, entry->density)); + res.push_back(casprintf_loc(translate("gettextFromC", "EADD: %d%s / %.1fg/ℓ"), eadd, depth_unit, entry.density)); break; } case plot_info::AIR: - if (entry->density > 0) { - res.push_back(casprintf_loc(translate("gettextFromC", "Density: %.1fg/ℓ"), entry->density)); + if (entry.density > 0) { + res.push_back(casprintf_loc(translate("gettextFromC", "Density: %.1fg/ℓ"), entry.density)); } case plot_info::FREEDIVING: /* nothing */ break; } } - if (entry->stopdepth) { - depthvalue = get_depth_units(entry->stopdepth, NULL, &depth_unit); - if (entry->ndl > 0) { + if (entry.stopdepth) { + depthvalue = get_depth_units(entry.stopdepth, NULL, &depth_unit); + if (entry.ndl > 0) { /* this is a safety stop as we still have ndl */ - if (entry->stoptime) - res.push_back(casprintf_loc(translate("gettextFromC", "Safety stop: %umin @ %.0f%s"), div_up(entry->stoptime, 60), + if (entry.stoptime) + res.push_back(casprintf_loc(translate("gettextFromC", "Safety stop: %umin @ %.0f%s"), div_up(entry.stoptime, 60), depthvalue, depth_unit)); else res.push_back(casprintf_loc(translate("gettextFromC", "Safety stop: unknown time @ %.0f%s"), depthvalue, depth_unit)); } else { /* actual deco stop */ - if (entry->stoptime) - res.push_back(casprintf_loc(translate("gettextFromC", "Deco: %umin @ %.0f%s"), div_up(entry->stoptime, 60), + if (entry.stoptime) + res.push_back(casprintf_loc(translate("gettextFromC", "Deco: %umin @ %.0f%s"), div_up(entry.stoptime, 60), depthvalue, depth_unit)); else res.push_back(casprintf_loc(translate("gettextFromC", "Deco: unknown time @ %.0f%s"), depthvalue, depth_unit)); } - } else if (entry->in_deco) { + } else if (entry.in_deco) { res.push_back(translate("gettextFromC", "In deco")); - } else if (entry->ndl >= 0) { - res.push_back(casprintf_loc(translate("gettextFromC", "NDL: %umin"), div_up(entry->ndl, 60))); + } else if (entry.ndl >= 0) { + res.push_back(casprintf_loc(translate("gettextFromC", "NDL: %umin"), div_up(entry.ndl, 60))); } - if (entry->tts) - res.push_back(casprintf_loc(translate("gettextFromC", "TTS: %umin"), div_up(entry->tts, 60))); - if (entry->stopdepth_calc && entry->stoptime_calc) { - depthvalue = get_depth_units(entry->stopdepth_calc, NULL, &depth_unit); - res.push_back(casprintf_loc(translate("gettextFromC", "Deco: %umin @ %.0f%s (calc)"), div_up(entry->stoptime_calc, 60), + if (entry.tts) + res.push_back(casprintf_loc(translate("gettextFromC", "TTS: %umin"), div_up(entry.tts, 60))); + if (entry.stopdepth_calc && entry.stoptime_calc) { + depthvalue = get_depth_units(entry.stopdepth_calc, NULL, &depth_unit); + res.push_back(casprintf_loc(translate("gettextFromC", "Deco: %umin @ %.0f%s (calc)"), div_up(entry.stoptime_calc, 60), depthvalue, depth_unit)); - } else if (entry->in_deco_calc) { + } else if (entry.in_deco_calc) { /* This means that we have no NDL left, * and we have no deco stop, * so if we just accend to the surface slowly * (ascent_mm_per_step / ascent_s_per_step) * everything will be ok. */ res.push_back(translate("gettextFromC", "In deco (calc)")); - } else if (prefs.calcndltts && entry->ndl_calc != 0) { - if(entry->ndl_calc < MAX_PROFILE_DECO) - res.push_back(casprintf_loc(translate("gettextFromC", "NDL: %umin (calc)"), div_up(entry->ndl_calc, 60))); + } else if (prefs.calcndltts && entry.ndl_calc != 0) { + if(entry.ndl_calc < MAX_PROFILE_DECO) + res.push_back(casprintf_loc(translate("gettextFromC", "NDL: %umin (calc)"), div_up(entry.ndl_calc, 60))); else res.push_back(translate("gettextFromC", "NDL: >2h (calc)")); } - if (entry->tts_calc) { - if (entry->tts_calc < MAX_PROFILE_DECO) - res.push_back(casprintf_loc(translate("gettextFromC", "TTS: %umin (calc)"), div_up(entry->tts_calc, 60))); + if (entry.tts_calc) { + if (entry.tts_calc < MAX_PROFILE_DECO) + res.push_back(casprintf_loc(translate("gettextFromC", "TTS: %umin (calc)"), div_up(entry.tts_calc, 60))); else res.push_back(translate("gettextFromC", "TTS: >2h (calc)")); } - if (entry->rbt) - res.push_back(casprintf_loc(translate("gettextFromC", "RBT: %umin"), div_up(entry->rbt, 60))); + if (entry.rbt) + res.push_back(casprintf_loc(translate("gettextFromC", "RBT: %umin"), div_up(entry.rbt, 60))); if (prefs.decoinfo) { - if (entry->current_gf > 0.0) - res.push_back(casprintf_loc(translate("gettextFromC", "GF %d%%"), (int)(100.0 * entry->current_gf))); - if (entry->surface_gf > 0.0) - res.push_back(casprintf_loc(translate("gettextFromC", "Surface GF %.0f%%"), entry->surface_gf)); - if (entry->ceiling) { - depthvalue = get_depth_units(entry->ceiling, NULL, &depth_unit); + if (entry.current_gf > 0.0) + res.push_back(casprintf_loc(translate("gettextFromC", "GF %d%%"), (int)(100.0 * entry.current_gf))); + if (entry.surface_gf > 0.0) + res.push_back(casprintf_loc(translate("gettextFromC", "Surface GF %.0f%%"), entry.surface_gf)); + if (entry.ceiling) { + depthvalue = get_depth_units(entry.ceiling, NULL, &depth_unit); res.push_back(casprintf_loc(translate("gettextFromC", "Calculated ceiling %.1f%s"), depthvalue, depth_unit)); if (prefs.calcalltissues) { int k; for (k = 0; k < 16; k++) { - if (entry->ceilings[k]) { - depthvalue = get_depth_units(entry->ceilings[k], NULL, &depth_unit); + if (entry.ceilings[k]) { + depthvalue = get_depth_units(entry.ceilings[k], NULL, &depth_unit); res.push_back(casprintf_loc(translate("gettextFromC", "Tissue %.0fmin: %.1f%s"), buehlmann_N2_t_halflife[k], depthvalue, depth_unit)); } } } } } - if (entry->icd_warning) + if (entry.icd_warning) res.push_back(translate("gettextFromC", "ICD in leading tissue")); - if (entry->heartbeat && prefs.hrgraph) - res.push_back(casprintf_loc(translate("gettextFromC", "heart rate: %d"), entry->heartbeat)); - if (entry->bearing >= 0) - res.push_back(casprintf_loc(translate("gettextFromC", "bearing: %d"), entry->bearing)); - if (entry->running_sum) { - depthvalue = get_depth_units(entry->running_sum / entry->sec, NULL, &depth_unit); + if (entry.heartbeat && prefs.hrgraph) + res.push_back(casprintf_loc(translate("gettextFromC", "heart rate: %d"), entry.heartbeat)); + if (entry.bearing >= 0) + res.push_back(casprintf_loc(translate("gettextFromC", "bearing: %d"), entry.bearing)); + if (entry.running_sum) { + depthvalue = get_depth_units(entry.running_sum / entry.sec, NULL, &depth_unit); res.push_back(casprintf_loc(translate("gettextFromC", "mean depth to here %.1f%s"), depthvalue, depth_unit)); } return res; } -std::pair> get_plot_details_new(const struct dive *d, const struct plot_info *pi, int time) +std::pair> get_plot_details_new(const struct dive *d, const struct plot_info &pi, int time) { /* The two first and the two last plot entries do not have useful data */ - if (pi->nr <= 4) + if (pi.entry.size() <= 4) return { 0, {} }; // binary search for sample index - auto it = std::lower_bound(pi->entry + 2, pi->entry + pi->nr - 3, time, + auto it = std::lower_bound(pi.entry.begin() + 2, pi.entry.end() - 3, time, [] (const plot_data &d, int time) { return d.sec < time; }); - int idx = it - pi->entry; + int idx = it - pi.entry.begin(); auto strings = plot_string(d, pi, idx); return std::make_pair(idx, strings); } /* Compare two plot_data entries and writes the results into a set of strings */ -std::vector compare_samples(const struct dive *d, const struct plot_info *pi, int idx1, int idx2, bool sum) +std::vector compare_samples(const struct dive *d, const struct plot_info &pi, int idx1, int idx2, bool sum) { std::string space(" "); const char *depth_unit, *pressure_unit, *vertical_speed_unit; @@ -1543,55 +1443,54 @@ std::vector compare_samples(const struct dive *d, const struct plot if (idx1 < 0 || idx2 < 0) return res; - if (pi->entry[idx1].sec > pi->entry[idx2].sec) { + if (pi.entry[idx1].sec > pi.entry[idx2].sec) { int tmp = idx2; idx2 = idx1; idx1 = tmp; - } else if (pi->entry[idx1].sec == pi->entry[idx2].sec) { + } else if (pi.entry[idx1].sec == pi.entry[idx2].sec) { return res; } - struct plot_data *start = pi->entry + idx1; - struct plot_data *stop = pi->entry + idx2; + const struct plot_data &start = pi.entry[idx1]; + const struct plot_data &stop = pi.entry[idx2]; int avg_speed = 0; int max_asc_speed = 0; int max_desc_speed = 0; - int delta_depth = abs(start->depth - stop->depth); - int delta_time = abs(start->sec - stop->sec); + int delta_depth = abs(start.depth - stop.depth); + int delta_time = abs(start.sec - stop.sec); int avg_depth = 0; int max_depth = 0; int min_depth = INT_MAX; - int last_sec = start->sec; + int last_sec = start.sec; - volume_t cylinder_volume = { .mliter = 0, }; - std::vector start_pressures(pi->nr_cylinders, 0); - std::vector last_pressures(pi->nr_cylinders, 0); - std::vector bar_used(pi->nr_cylinders, 0); - std::vector volumes_used(pi->nr_cylinders, 0); - std::vector cylinder_is_used(pi->nr_cylinders, false); + volume_t cylinder_volume; + std::vector start_pressures(pi.nr_cylinders, 0); + std::vector last_pressures(pi.nr_cylinders, 0); + std::vector bar_used(pi.nr_cylinders, 0); + std::vector volumes_used(pi.nr_cylinders, 0); + std::vector cylinder_is_used(pi.nr_cylinders, false); - struct plot_data *data = start; for (int i = idx1; i < idx2; ++i) { - data = pi->entry + i; + const struct plot_data &data = pi.entry[i]; if (sum) - avg_speed += abs(data->speed) * (data->sec - last_sec); + avg_speed += abs(data.speed) * (data.sec - last_sec); else - avg_speed += data->speed * (data->sec - last_sec); - avg_depth += data->depth * (data->sec - last_sec); + avg_speed += data.speed * (data.sec - last_sec); + avg_depth += data.depth * (data.sec - last_sec); - if (data->speed > max_desc_speed) - max_desc_speed = data->speed; - if (data->speed < max_asc_speed) - max_asc_speed = data->speed; + if (data.speed > max_desc_speed) + max_desc_speed = data.speed; + if (data.speed < max_asc_speed) + max_asc_speed = data.speed; - if (data->depth < min_depth) - min_depth = data->depth; - if (data->depth > max_depth) - max_depth = data->depth; + if (data.depth < min_depth) + min_depth = data.depth; + if (data.depth > max_depth) + max_depth = data.depth; - for (int cylinder_index = 0; cylinder_index < pi->nr_cylinders; cylinder_index++) { + for (int cylinder_index = 0; cylinder_index < pi.nr_cylinders; cylinder_index++) { int next_pressure = get_plot_pressure(pi, i, cylinder_index); if (next_pressure && !start_pressures[cylinder_index]) start_pressures[cylinder_index] = next_pressure; @@ -1600,9 +1499,11 @@ std::vector compare_samples(const struct dive *d, const struct plot if (last_pressures[cylinder_index]) { bar_used[cylinder_index] += last_pressures[cylinder_index] - next_pressure; - cylinder_t *cyl = get_cylinder(d, cylinder_index); + const cylinder_t *cyl = d->get_cylinder(cylinder_index); - volumes_used[cylinder_index] += gas_volume(cyl, (pressure_t){ last_pressures[cylinder_index] }) - gas_volume(cyl, (pressure_t){ next_pressure }); + // TODO: Implement addition/subtraction on units.h types + volumes_used[cylinder_index] += (cyl->gas_volume((pressure_t){ .mbar = last_pressures[cylinder_index] }) - + cyl->gas_volume((pressure_t){ .mbar = next_pressure })).mliter; } // check if the gas in this cylinder is being used @@ -1614,11 +1515,11 @@ std::vector compare_samples(const struct dive *d, const struct plot last_pressures[cylinder_index] = next_pressure; } - last_sec = data->sec; + last_sec = data.sec; } - avg_depth /= stop->sec - start->sec; - avg_speed /= stop->sec - start->sec; + avg_depth /= stop.sec - start.sec; + avg_speed /= stop.sec - start.sec; std::string l = casprintf_loc(translate("gettextFromC", "ΔT:%d:%02dmin"), delta_time / 60, delta_time % 60); @@ -1648,17 +1549,17 @@ std::vector compare_samples(const struct dive *d, const struct plot int total_volume_used = 0; bool cylindersizes_are_identical = true; bool sac_is_determinable = true; - for (int cylinder_index = 0; cylinder_index < pi->nr_cylinders; cylinder_index++) { + for (int cylinder_index = 0; cylinder_index < pi.nr_cylinders; cylinder_index++) { if (cylinder_is_used[cylinder_index]) { total_bar_used += bar_used[cylinder_index]; total_volume_used += volumes_used[cylinder_index]; - cylinder_t *cyl = get_cylinder(d, cylinder_index); + const cylinder_t *cyl = d->get_cylinder(cylinder_index); if (cyl->type.size.mliter) { if (cylinder_volume.mliter && cylinder_volume.mliter != cyl->type.size.mliter) { cylindersizes_are_identical = false; } else { - cylinder_volume.mliter = cyl->type.size.mliter; + cylinder_volume = cyl->type.size; } } else { sac_is_determinable = false; @@ -1678,7 +1579,7 @@ std::vector compare_samples(const struct dive *d, const struct plot const char *volume_unit; /* Mean pressure in ATM */ - double atm = depth_to_atm(avg_depth, d); + double atm = d->depth_to_atm(avg_depth); /* milliliters per minute */ int sac = lrint(total_volume_used / atm * 60 / delta_time); diff --git a/core/profile.h b/core/profile.h index 8812937d9..2e0d9aaec 100644 --- a/core/profile.h +++ b/core/profile.h @@ -2,20 +2,21 @@ #ifndef PROFILE_H #define PROFILE_H -#include "dive.h" -#include "sample.h" +#include "gas.h" // gas_pressures +#include "sample.h" // MAX_O2_SENSORS -#ifdef __cplusplus -extern "C" { -#endif +#include +#include +#include +#include -typedef enum { +enum velocity_t { STABLE, SLOW, MODERATE, FAST, CRAZY -} velocity_t; +}; enum plot_pressure { SENSOR_PR = 0, @@ -25,82 +26,89 @@ enum plot_pressure { struct membuffer; struct deco_state; +struct dive; struct divecomputer; /* * sensor data for a given cylinder */ struct plot_pressure_data { - int data[NUM_PLOT_PRESSURES]; + std::array data; }; struct plot_data { - unsigned int in_deco : 1; - int sec; - int temperature; + bool in_deco = false; + int sec = 0; + int temperature = 0; /* Depth info */ - int depth; - int ceiling; - int ceilings[16]; - int percentages[16]; - int ndl; - int tts; - int rbt; - int stoptime; - int stopdepth; - int cns; - int smoothed; - int sac; - int running_sum; + int depth = 0; + int ceiling = 0; + std::array ceilings; + std::array percentages; + int ndl = 0; + int tts = 0; + int rbt = 0; + int stoptime = 0; + int stopdepth = 0; + int cns = 0; + int smoothed = 0; + int sac = 0; + int running_sum = 0; struct gas_pressures pressures; + // TODO: make pressure_t default to 0 pressure_t o2pressure; // for rebreathers, this is consensus measured po2, or setpoint otherwise. 0 for OC. - pressure_t o2sensor[MAX_O2_SENSORS]; //for rebreathers with several sensors + std::array o2sensor; //for rebreathers with several sensors pressure_t o2setpoint; pressure_t scr_OC_pO2; - int mod, ead, end, eadd; - velocity_t velocity; - int speed; + int mod = 0, ead = 0, end = 0, eadd = 0; + velocity_t velocity = STABLE; + int speed = 0; /* values calculated by us */ - unsigned int in_deco_calc : 1; - int ndl_calc; - int tts_calc; - int stoptime_calc; - int stopdepth_calc; - int pressure_time; - int heartbeat; - int bearing; - double ambpressure; - double gfline; - double surface_gf; - double current_gf; - double density; - bool icd_warning; + bool in_deco_calc = false; + int ndl_calc = 0; + int tts_calc = 0; + int stoptime_calc = 0; + int stopdepth_calc = 0; + int pressure_time = 0; + int heartbeat = 0; + int bearing = 0; + double ambpressure = 0.0; + double gfline = 0.0; + double surface_gf = 0.0; + double current_gf = 0.0; + double density = 0.0; + bool icd_warning = false; }; /* Plot info with smoothing, velocity indication * and one-, two- and three-minute minimums and maximums */ struct plot_info { - int nr; - int nr_cylinders; - int maxtime; - int meandepth, maxdepth; - int minpressure, maxpressure; - int minhr, maxhr; - int mintemp, maxtemp; - enum {AIR, NITROX, TRIMIX, FREEDIVING} dive_type; - double endtempcoord; - double maxpp; - bool waypoint_above_ceiling; - struct plot_data *entry; - struct plot_pressure_data *pressures; /* cylinders.nr blocks of nr entries. */ + int nr = 0; // TODO: remove - redundant with entry.size() + int nr_cylinders = 0; + int maxtime = 0; + int meandepth = 0, maxdepth = 0; + int minpressure = 0, maxpressure = 0; + int minhr = 0, maxhr = 0; + int mintemp = 0, maxtemp = 0; + enum {AIR, NITROX, TRIMIX, FREEDIVING} dive_type = AIR; + double endtempcoord = 0.0; + double maxpp = 0.0; + bool waypoint_above_ceiling = false; + std::vector entry; + std::vector pressures; /* cylinders.size() blocks of nr entries. */ + + plot_info(); + ~plot_info(); + plot_info(const plot_info &) = default; + plot_info(plot_info &&) = default; + plot_info &operator=(const plot_info &) = default; + plot_info &operator=(plot_info &&) = default; }; #define AMB_PERCENTAGE 50.0 -extern void init_plot_info(struct plot_info *pi); /* when planner_dc is non-null, this is called in planner mode. */ -extern void create_plot_info_new(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi, const struct deco_state *planner_ds); -extern void free_plot_info_data(struct plot_info *pi); +extern struct plot_info create_plot_info_new(const struct dive *dive, const struct divecomputer *dc, const struct deco_state *planner_ds); /* * When showing dive profiles, we scale things to the @@ -110,46 +118,39 @@ extern void free_plot_info_data(struct plot_info *pi); * We also need to add 180 seconds at the end so the min/max * plots correctly */ -extern int get_maxtime(const struct plot_info *pi); +extern int get_maxtime(const struct plot_info &pi); /* get the maximum depth to which we want to plot */ -extern int get_maxdepth(const struct plot_info *pi); +extern int get_maxdepth(const struct plot_info &pi); -static inline int get_plot_pressure_data(const struct plot_info *pi, int idx, enum plot_pressure sensor, int cylinder) +static inline int get_plot_pressure_data(const struct plot_info &pi, int idx, enum plot_pressure sensor, int cylinder) { - return pi->pressures[cylinder + idx * pi->nr_cylinders].data[sensor]; + return pi.pressures[cylinder + idx * pi.nr_cylinders].data[sensor]; } -static inline void set_plot_pressure_data(struct plot_info *pi, int idx, enum plot_pressure sensor, int cylinder, int value) +static inline void set_plot_pressure_data(struct plot_info &pi, int idx, enum plot_pressure sensor, int cylinder, int value) { - pi->pressures[cylinder + idx * pi->nr_cylinders].data[sensor] = value; + pi.pressures[cylinder + idx * pi.nr_cylinders].data[sensor] = value; } -static inline int get_plot_sensor_pressure(const struct plot_info *pi, int idx, int cylinder) +static inline int get_plot_sensor_pressure(const struct plot_info &pi, int idx, int cylinder) { return get_plot_pressure_data(pi, idx, SENSOR_PR, cylinder); } -static inline int get_plot_interpolated_pressure(const struct plot_info *pi, int idx, int cylinder) +static inline int get_plot_interpolated_pressure(const struct plot_info &pi, int idx, int cylinder) { return get_plot_pressure_data(pi, idx, INTERPOLATED_PR, cylinder); } -static inline int get_plot_pressure(const struct plot_info *pi, int idx, int cylinder) +static inline int get_plot_pressure(const struct plot_info &pi, int idx, int cylinder) { int res = get_plot_sensor_pressure(pi, idx, cylinder); return res ? res : get_plot_interpolated_pressure(pi, idx, cylinder); } -#ifdef __cplusplus -} - -// C++ only formatting functions -#include -#include // Returns index of sample and array of strings describing the dive details at given time -std::pair> get_plot_details_new(const struct dive *d, const struct plot_info *pi, int time); -std::vector compare_samples(const struct dive *d, const struct plot_info *pi, int idx1, int idx2, bool sum); +std::pair> get_plot_details_new(const struct dive *d, const struct plot_info &pi, int time); +std::vector compare_samples(const struct dive *d, const struct plot_info &pi, int idx1, int idx2, bool sum); -#endif #endif // PROFILE_H diff --git a/core/qt-ble.cpp b/core/qt-ble.cpp index 3e00c205d..fdbef3bce 100644 --- a/core/qt-ble.cpp +++ b/core/qt-ble.cpp @@ -25,10 +25,6 @@ #define DEBUG_THRESHOLD 50 static int debugCounter; -#define IS_HW(_d) same_string((_d)->vendor, "Heinrichs Weikamp") -#define IS_SHEARWATER(_d) same_string((_d)->vendor, "Shearwater") -#define IS_GARMIN(_d) same_string((_d)->vendor, "Garmin") - #define MAXIMAL_HW_CREDIT 254 #define MINIMAL_HW_CREDIT 32 @@ -60,8 +56,6 @@ static std::string to_str(const T &v) return v.toString().toStdString(); } -extern "C" { - void BLEObject::serviceStateChanged(QLowEnergyService::ServiceState newState) { if (verbose > 2 || debugCounter < DEBUG_THRESHOLD) @@ -72,11 +66,26 @@ void BLEObject::serviceStateChanged(QLowEnergyService::ServiceState newState) report_info("%s %d", to_str(service->serviceUuid()).c_str(), static_cast(newState)); } +static bool is_hw(const device_data_t &d) +{ + return d.vendor == "Heinrichs Weikamp"; +} + +static bool is_shearwater(const device_data_t &d) +{ + return d.vendor == "Shearwater"; +} + +static bool is_garmin(const device_data_t &d) +{ + return d.vendor == "Garmin"; +} + void BLEObject::characteristcStateChanged(const QLowEnergyCharacteristic &c, const QByteArray &value) { if (verbose > 2 || debugCounter < DEBUG_THRESHOLD) report_info("%s packet RECV %s", current_time().c_str(), qPrintable(value.toHex())); - if (IS_HW(device)) { + if (is_hw(device)) { if (c.uuid() == telit[TELIT_DATA_TX] || c.uuid() == ublox[UBLOX_DATA_TX]) { hw_credit--; receivedPackets.append(value); @@ -92,7 +101,7 @@ void BLEObject::characteristcStateChanged(const QLowEnergyCharacteristic &c, con void BLEObject::characteristicWritten(const QLowEnergyCharacteristic &c, const QByteArray &value) { - if (IS_HW(device)) { + if (is_hw(device)) { if (c.uuid() == telit[TELIT_CREDITS_RX] || c.uuid() == ublox[UBLOX_CREDITS_RX]) { bool ok; hw_credit += value.toHex().toInt(&ok, 16); @@ -246,13 +255,13 @@ void BLEObject::addService(const QBluetoothUuid &newService) } } -BLEObject::BLEObject(QLowEnergyController *c, device_data_t *d) +BLEObject::BLEObject(QLowEnergyController *c, device_data_t &d) : + controller(c), + isCharacteristicWritten(false), + device(d), + timeout(BLE_TIMEOUT) { - controller = c; - device = d; debugCounter = 0; - isCharacteristicWritten = false; - timeout = BLE_TIMEOUT; } BLEObject::~BLEObject() @@ -405,7 +414,7 @@ dc_status_t BLEObject::read(void *data, size_t size, size_t *actual) // // That's wrong, but works for the simple case. // -dc_status_t BLEObject::select_preferred_service(void) +dc_status_t BLEObject::select_preferred_service() { // Wait for each service to finish discovering for (const QLowEnergyService *s: services) { @@ -549,9 +558,9 @@ dc_status_t BLEObject::setupHwTerminalIo(const QList & // Bluez is broken, and doesn't have a sane way to query // whether to use a random address or not. So we have to // fake it. -static int use_random_address(device_data_t *user_device) +static int use_random_address(const device_data_t &user_device) { - return IS_SHEARWATER(user_device) || IS_GARMIN(user_device); + return is_shearwater(user_device) || is_garmin(user_device); } #endif @@ -587,7 +596,7 @@ dc_status_t qt_ble_open(void **io, dc_context_t *, const char *devaddr, device_d report_info("qt_ble_open(%s)", devaddr); #if !defined(Q_OS_WIN) - if (use_random_address(user_device)) + if (use_random_address(*user_device)) controller->setRemoteAddressType(QLowEnergyController::RandomAddress); #endif @@ -616,7 +625,7 @@ dc_status_t qt_ble_open(void **io, dc_context_t *, const char *devaddr, device_d // We need to discover services etc here! // Note that ble takes ownership of controller and henceforth deleting ble will // take care of deleting controller. - BLEObject *ble = new BLEObject(controller, user_device); + BLEObject *ble = new BLEObject(controller, *user_device); // we used to call our addService function the moment a service was discovered, but that // could cause us to try to discover the details of a characteristic while we were still serching // for services, which can cause a failure in the Qt BLE stack. @@ -657,7 +666,7 @@ dc_status_t qt_ble_open(void **io, dc_context_t *, const char *devaddr, device_d /* Enable notifications */ QList list = ble->preferredService()->characteristics(); - if (IS_HW(user_device)) { + if (is_hw(*user_device)) { dc_status_t r = ble->setupHwTerminalIo(list); if (r != DC_STATUS_SUCCESS) { delete ble; @@ -747,6 +756,14 @@ dc_status_t qt_ble_poll(void *io, int timeout) return ble->poll(timeout); } +dc_status_t BLEObject::get_name(char *data, size_t size) +{ + if (device.btname.empty()) + return DC_STATUS_UNSUPPORTED; + strncpy(data, device.btname.c_str(), size); + return DC_STATUS_SUCCESS; +} + dc_status_t qt_ble_ioctl(void *io, unsigned int request, void *data, size_t size) { BLEObject *ble = (BLEObject *) io; @@ -758,5 +775,3 @@ dc_status_t qt_ble_ioctl(void *io, unsigned int request, void *data, size_t size return DC_STATUS_UNSUPPORTED; } } - -} /* extern "C" */ diff --git a/core/qt-ble.h b/core/qt-ble.h index b7e2cfcf7..f4750a9ae 100644 --- a/core/qt-ble.h +++ b/core/qt-ble.h @@ -21,24 +21,18 @@ class BLEObject : public QObject { Q_OBJECT - public: - BLEObject(QLowEnergyController *c, device_data_t *); + BLEObject(QLowEnergyController *c, device_data_t &); ~BLEObject(); inline void set_timeout(int value) { timeout = value; } dc_status_t write(const void* data, size_t size, size_t *actual); dc_status_t read(void* data, size_t size, size_t *actual); - inline dc_status_t get_name(char *res, size_t size) - { - if (!device->btname) return DC_STATUS_UNSUPPORTED; - strncpy(res, device->btname, size); - return DC_STATUS_SUCCESS; - } + dc_status_t get_name(char *res, size_t size); dc_status_t poll(int timeout); inline QLowEnergyService *preferredService() { return preferred; } inline int descriptorWritten() { return desc_written; } - dc_status_t select_preferred_service(void); + dc_status_t select_preferred_service(); public slots: void addService(const QBluetoothUuid &newService); @@ -51,11 +45,11 @@ public slots: private: QVector services; - QLowEnergyController *controller = nullptr; + QLowEnergyController *controller; QLowEnergyService *preferred = nullptr; QList receivedPackets; bool isCharacteristicWritten; - device_data_t *device; + device_data_t &device; unsigned int hw_credit = 0; unsigned int desc_written = 0; int timeout; @@ -73,8 +67,6 @@ private: }; }; - -extern "C" { dc_status_t qt_ble_open(void **io, dc_context_t *context, const char *devaddr, device_data_t *user_device); dc_status_t qt_ble_set_timeout(void *io, int timeout); dc_status_t qt_ble_poll(void *io, int timeout); @@ -82,6 +74,5 @@ dc_status_t qt_ble_read(void *io, void* data, size_t size, size_t *actual); dc_status_t qt_ble_write(void *io, const void* data, size_t size, size_t *actual); dc_status_t qt_ble_ioctl(void *io, unsigned int request, void *data, size_t size); dc_status_t qt_ble_close(void *io); -} #endif diff --git a/core/qt-init.cpp b/core/qt-init.cpp index 5b2793d35..9b8899f49 100644 --- a/core/qt-init.cpp +++ b/core/qt-init.cpp @@ -9,7 +9,7 @@ #include "errorhelper.h" #include "core/settings/qPref.h" -char *settings_suffix = NULL; +std::string settings_suffix; static QTranslator qtTranslator, ssrfTranslator, parentLanguageTranslator; void init_qt_late() @@ -29,17 +29,17 @@ void init_qt_late() QGuiApplication::setDesktopFileName("subsurface"); #endif // enable user specific settings (based on command line argument) - if (settings_suffix) { + if (!settings_suffix.empty()) { if (verbose) #if defined(SUBSURFACE_MOBILE) && ((defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || (defined(Q_OS_DARWIN) && !defined(Q_OS_IOS))) - report_info("using custom config for Subsurface-Mobile-%s", settings_suffix); + report_info("using custom config for Subsurface-Mobile-%s", settings_suffix.c_str()); #else - report_info("using custom config for Subsurface-%s", settings_suffix); + report_info("using custom config for Subsurface-%s", settings_suffix.c_str()); #endif #if defined(SUBSURFACE_MOBILE) && ((defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || (defined(Q_OS_DARWIN) && !defined(Q_OS_IOS))) - QCoreApplication::setApplicationName(QString("Subsurface-Mobile-%1").arg(settings_suffix)); + QCoreApplication::setApplicationName(QString::fromStdString("Subsurface-Mobile-" + settings_suffix)); #else - QCoreApplication::setApplicationName(QString("Subsurface-%1").arg(settings_suffix)); + QCoreApplication::setApplicationName(QString::fromStdString("Subsurface-" + settings_suffix)); #endif } else { #if defined(SUBSURFACE_MOBILE) && ((defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || (defined(Q_OS_DARWIN) && !defined(Q_OS_IOS))) diff --git a/core/qthelper.cpp b/core/qthelper.cpp index 758a2d082..9ddbd8590 100644 --- a/core/qthelper.cpp +++ b/core/qthelper.cpp @@ -12,6 +12,7 @@ #include "version.h" #include "errorhelper.h" #include "planner.h" +#include "range.h" #include "subsurface-time.h" #include "gettextfromc.h" #include "metadata.h" @@ -50,14 +51,14 @@ static inline QString degreeSigns() return QStringLiteral("dD\u00b0"); } -QString weight_string(int weight_in_grams) +static QString weight_string(weight_t weight) { QString str; if (get_units()->weight == units::KG) { - double kg = (double) weight_in_grams / 1000.0; + double kg = (double) weight.grams / 1000.0; str = QString("%L1").arg(kg, 0, 'f', kg >= 20.0 ? 0 : 1); } else { - double lbs = grams_to_lbs(weight_in_grams); + double lbs = grams_to_lbs(weight.grams); str = QString("%L1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1); } return str; @@ -268,31 +269,6 @@ bool parseGpsText(const QString &gps_text, double *latitude, double *longitude) pos == normalized.size(); } -#if 0 // we'll need something like this for the dive site management, eventually -bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out) -{ - location_t location; - bool ignore; - bool *parsed = parsed_out ?: &ignore; - *parsed = true; - - /* if we have a master and the dive's gps address is different from it, - * don't change the dive */ - if (master && !same_location(&master->location, &dive->location)) - return false; - - if (!(*parsed = parseGpsText(gps_text, location))) - return false; - - /* if dive gps didn't change, nothing changed */ - if (same_location(&dive->location, location)) - return false; - /* ok, update the dive and mark things changed */ - dive->location = location; - return true; -} -#endif - static xmlDocPtr get_stylesheet_doc(const xmlChar *uri, xmlDictPtr, int, void *, xsltLoadType) { std::string filename = std::string(":/xslt/") + (const char *)uri; @@ -310,7 +286,7 @@ static xmlDocPtr get_stylesheet_doc(const xmlChar *uri, xmlDictPtr, int, void *, return doc; } -extern "C" xsltStylesheetPtr get_stylesheet(const char *name) +xsltStylesheetPtr get_stylesheet(const char *name) { // this needs to be done only once, but doesn't hurt to run every time xsltSetLoaderFunc(get_stylesheet_doc); @@ -357,24 +333,12 @@ std::string move_away(const std::string &old_path) return newPath; } -std::string get_file_name(const char *fileName) +std::string get_file_name(const std::string &fileName) { - QFileInfo fileInfo(fileName); + QFileInfo fileInfo(fileName.c_str()); return fileInfo.fileName().toStdString(); } -extern "C" void copy_image_and_overwrite(const char *cfileName, const char *path, const char *cnewName) -{ - QString fileName(cfileName); - QString newName(path); - newName += cnewName; - QFile file(newName); - if (file.exists()) - file.remove(); - if (!QFile::copy(fileName, newName)) - report_info("copy of %s to %s failed", cfileName, qPrintable(newName)); -} - static bool lessThan(const QPair &a, const QPair &b) { return a.second < b.second; @@ -382,17 +346,15 @@ static bool lessThan(const QPair &a, const QPair &b) QVector> selectedDivesGasUsed() { - int j; QMap gasUsed; for (dive *d: getDiveSelection()) { - volume_t *diveGases = get_gas_used(d); - for (j = 0; j < d->cylinders.nr; j++) { + std::vector diveGases = get_gas_used(d); + for (size_t j = 0; j < d->cylinders.size(); j++) { if (diveGases[j].mliter) { - QString gasName = gasname(get_cylinder(d, j)->gasmix); + QString gasName = QString::fromStdString(d->get_cylinder(j)->gasmix.name()); gasUsed[gasName] += diveGases[j].mliter; } } - free(diveGases); } QVector> gasUsedOrdered; gasUsedOrdered.reserve(gasUsed.size()); @@ -440,7 +402,7 @@ std::string subsurface_user_agent() QString getUiLanguage() { - return prefs.locale.lang_locale; + return QString::fromStdString(prefs.locale.lang_locale); } static std::vector get_languages(const QLocale &loc) @@ -495,10 +457,9 @@ void initUiLanguage() #endif } - free((void*)prefs.locale.lang_locale); - prefs.locale.lang_locale = strdup(uiLang.c_str()); + prefs.locale.lang_locale = uiLang; - if (!prefs.date_format_override || empty_string(prefs.date_format)) { + if (!prefs.date_format_override || prefs.date_format.empty()) { // derive our standard date format from what the locale gives us // the long format uses long weekday and month names, so replace those with the short ones // for time we don't want the time zone designator and don't want leading zeroes on the hours @@ -507,23 +468,20 @@ void initUiLanguage() // special hack for Swedish as our switching from long weekday names to short weekday names // messes things up there dateFormat.replace("'en' 'den' d:'e'", " d"); - free((void *)prefs.date_format); - prefs.date_format = copy_qstring(dateFormat); + prefs.date_format = dateFormat.toStdString(); } - if (!prefs.date_format_override || empty_string(prefs.date_format_short)) { + if (!prefs.date_format_override || prefs.date_format_short.empty()) { // derive our standard date format from what the locale gives us shortDateFormat = loc.dateFormat(QLocale::ShortFormat); - free((void *)prefs.date_format_short); - prefs.date_format_short = copy_qstring(shortDateFormat); + prefs.date_format_short = shortDateFormat.toStdString(); } - if (!prefs.time_format_override || empty_string(prefs.time_format)) { + if (!prefs.time_format_override || prefs.time_format.empty()) { timeFormat = loc.timeFormat(); timeFormat.replace("(t)", "").replace(" t", "").replace("t", "").replace("hh", "h").replace("HH", "H").replace("'kl'.", ""); timeFormat.replace(".ss", "").replace(":ss", "").replace("ss", ""); - free((void *)prefs.time_format); - prefs.time_format = copy_qstring(timeFormat); + prefs.time_format = timeFormat.toStdString(); } } @@ -563,13 +521,8 @@ QString get_depth_unit() QString get_weight_string(weight_t weight, bool showunit) { - QString str = weight_string(weight.grams); - if (get_units()->weight == units::KG) { - str = QString("%1%2").arg(str, showunit ? gettextFromC::tr("kg") : QString()); - } else { - str = QString("%1%2").arg(str, showunit ? gettextFromC::tr("lbs") : QString()); - } - return str; + QString str = weight_string(weight); + return showunit ? str + get_weight_unit() : str; } QString get_weight_unit(bool metric) @@ -721,7 +674,7 @@ static const char *printing_templates = "printing_templates"; QString getPrintingTemplatePathUser() { // Function-local statics are initialized on first invocation - static QString path(QString(system_default_directory()) + + static QString path(QString::fromStdString(system_default_directory()) + QDir::separator() + QString(printing_templates)); return path; @@ -734,15 +687,6 @@ QString getPrintingTemplatePathBundle() return path; } -int gettimezoneoffset() -{ - QDateTime dt1, dt2; - dt1 = QDateTime::currentDateTime(); - dt2 = dt1.toUTC(); - dt1.setTimeSpec(Qt::UTC); - return dt2.secsTo(dt1); -} - QDateTime timestampToDateTime(timestamp_t when) { // Subsurface always uses "local time" as in "whatever was the local time at the location" @@ -842,33 +786,6 @@ int parseTemperatureToMkelvin(const QString &text) return mkelvin; } -int parseWeightToGrams(const QString &text) -{ - int grams; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegularExpression("[^0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - if (text.contains(gettextFromC::tr("kg"), Qt::CaseInsensitive)) { - grams = lrint(number * 1000); - } else if (text.contains(gettextFromC::tr("lbs"), Qt::CaseInsensitive)) { - grams = lbs_to_grams(number); - } else { - switch (prefs.units.weight) { - case units::KG: - grams = lrint(number * 1000); - break; - case units::LBS: - grams = lbs_to_grams(number); - break; - default: - grams = 0; - } - } - return grams; -} - int parsePressureToMbar(const QString &text) { int mbar; @@ -996,11 +913,11 @@ QString get_dive_date_string(timestamp_t when) { QDateTime ts; ts.setMSecsSinceEpoch(when * 1000L); - return loc.toString(ts.toUTC(), QString(prefs.date_format) + " " + prefs.time_format); + return loc.toString(ts.toUTC(), QString::fromStdString(prefs.date_format + " " + prefs.time_format)); } // Get local seconds since Epoch from ISO formatted UTC date time + offset string -extern "C" time_t get_dive_datetime_from_isostring(char *when) { +time_t get_dive_datetime_from_isostring(const char *when) { QDateTime divetime = QDateTime::fromString(when, Qt::ISODate); return (time_t)(divetime.toSecsSinceEpoch()); } @@ -1009,7 +926,7 @@ QString get_short_dive_date_string(timestamp_t when) { QDateTime ts; ts.setMSecsSinceEpoch(when * 1000L); - return loc.toString(ts.toUTC(), QString(prefs.date_format_short) + " " + prefs.time_format); + return loc.toString(ts.toUTC(), QString::fromStdString(prefs.date_format_short + " " + prefs.time_format)); } std::string get_dive_date_c_string(timestamp_t when) @@ -1021,19 +938,19 @@ static QString get_dive_only_date_string(timestamp_t when) { QDateTime ts; ts.setMSecsSinceEpoch(when * 1000L); - return loc.toString(ts.toUTC(), QString(prefs.date_format)); + return loc.toString(ts.toUTC(), QString::fromStdString(prefs.date_format)); } QString get_first_dive_date_string() { - const dive_table *dives = divelog.dives; - return dives->nr > 0 ? get_dive_only_date_string(dives->dives[0]->when) : gettextFromC::tr("no dives"); + const dive_table &dives = divelog.dives; + return !dives.empty() ? get_dive_only_date_string(dives[0]->when) : gettextFromC::tr("no dives"); } QString get_last_dive_date_string() { - const dive_table *dives = divelog.dives; - return dives->nr > 0 ? get_dive_only_date_string(dives->dives[dives->nr - 1]->when) : gettextFromC::tr("no dives"); + const dive_table &dives = divelog.dives; + return !dives.empty() ? get_dive_only_date_string(dives.back()->when) : gettextFromC::tr("no dives"); } std::string get_current_date() @@ -1041,7 +958,7 @@ std::string get_current_date() QDateTime ts(QDateTime::currentDateTime());; QString current_date; - current_date = loc.toString(ts, QString(prefs.date_format_short)); + current_date = loc.toString(ts, QString::fromStdString(prefs.date_format_short)); return current_date.toStdString(); } @@ -1049,14 +966,14 @@ std::string get_current_date() static QMutex hashOfMutex; static QHash localFilenameOf; -static const QString hashfile_name() +std::string hashfile_name() { - return QString(system_default_directory()).append("/hashes"); + return std::string(system_default_directory()) + "/hashes"; } static QString thumbnailDir() { - return QString(system_default_directory()) + "/thumbnails/"; + return QString::fromStdString(system_default_directory() + "/thumbnails/"); } // Calculate thumbnail filename by hashing name of file. @@ -1069,11 +986,6 @@ QString thumbnailFileName(const QString &filename) return thumbnailDir() + hash.result().toHex(); } -extern "C" char *hashfile_name_string() -{ - return copy_qstring(hashfile_name()); -} - // TODO: This is a temporary helper struct. Remove in due course with convertLocalFilename(). struct HashToFile { QByteArray hash; @@ -1116,7 +1028,7 @@ static void convertLocalFilename(const QHash &hashOf, const void read_hashes() { - QFile hashfile(hashfile_name()); + QFile hashfile(QString::fromStdString(hashfile_name())); if (hashfile.open(QIODevice::ReadOnly)) { QDataStream stream(&hashfile); QHash localFilenameByHash; @@ -1140,7 +1052,7 @@ void read_hashes() void write_hashes() { - QSaveFile hashfile(hashfile_name()); + QSaveFile hashfile(QString::fromStdString(hashfile_name())); QMutexLocker locker(&hashOfMutex); if (hashfile.open(QIODevice::WriteOnly)) { @@ -1179,6 +1091,17 @@ const QStringList videoExtensionsList = { ".avi", ".mp4", ".mov", ".mpeg", ".mpg", ".wmv" }; +// Raw extensions according to https://en.wikipedia.org/wiki/Raw_image_format +static const QStringList rawExtensionsList = { +#ifdef LIBRAW_SUPPORT + "*.3fr", "*.ari", "*.arw", "*.bay", "*.braw", "*.crw", "*.cr2", "*.cr3", "*.cap", + "*.data", "*.dcs", "*.dcr", "*.dng", "*.drf", "*.eip", "*.erf", "*.fff", "*.gpr", + "*.iiq", "*.k25", "*.kdc", "*.mdc", "*.mef", "*.mos", "*.mrw", "*.nef", "*.nrw", + "*.obm", "*.orf", "*.pef", "*.ptx", "*.pxn", "*.r3d", "*.raf", "*.raw", "*.rwl", + "*.rw2", "*.rwz", "*.sr2", "*.srf", "*.srw", "*.x3f" +#endif +}; + QStringList mediaExtensionFilters() { return imageExtensionFilters() + videoExtensionFilters(); @@ -1189,7 +1112,7 @@ QStringList imageExtensionFilters() QStringList filters; for (QString format: QImageReader::supportedImageFormats()) filters.append("*." + format); - return filters; + return filters + rawExtensionsList; } QStringList videoExtensionFilters() @@ -1200,9 +1123,9 @@ QStringList videoExtensionFilters() return filters; } -extern "C" const char *local_file_path(struct picture *picture) +std::string local_file_path(const struct picture &picture) { - return copy_qstring(localFilePath(picture->filename)); + return localFilePath(QString::fromStdString(picture.filename)).toStdString(); } QString get_gas_string(struct gasmix gas) @@ -1212,20 +1135,51 @@ QString get_gas_string(struct gasmix gas) return result; } -QStringList get_dive_gas_list(const struct dive *d) +QString get_dive_gas(const struct dive *d, int dcNr, int cylinderId) { - QStringList list; - for (int i = 0; i < d->cylinders.nr; i++) { - const cylinder_t *cyl = get_cylinder(d, i); - /* Check if we have the same gasmix two or more times - * If yes return more verbose string */ - int same_gas = same_gasmix_cylinder(cyl, i, d, true); - if (same_gas == -1) - list.push_back(get_gas_string(cyl->gasmix)); - else - list.push_back(get_gas_string(cyl->gasmix) + QString(" (%1 %2 ").arg(gettextFromC::tr("cyl.")).arg(i + 1) + - cyl->type.description + ")"); + const cylinder_t *cyl = d->get_cylinder(cylinderId); + const divecomputer *dc = d->get_dc(dcNr); + + bool showUse = (dc->divemode == CCR) | !is_cylinder_use_appropriate(*dc, *cyl, false); + + QString gasType = get_gas_string(cyl->gasmix); + QString gasName; + /* Check if we have the same gasmix two or more times + * If yes return more verbose string */ + int same_gas = same_gasmix_cylinder(*cyl, cylinderId, d, true); + if (same_gas != -1) { + gasType += QString(" (%1 %2").arg(gettextFromC::tr("cyl.")).arg(cylinderId + 1); + gasName = QString::fromStdString(d->get_cylinder(cylinderId)->type.description); } + + if (showUse) { + if (gasName.isNull()) + gasType += " ("; + else + gasType += ", "; + gasType += gettextFromC::tr(cylinderuse_text[d->get_cylinder(cylinderId)->cylinder_use]); + } + + if (!gasName.isNull()) + gasType += QString(", %1").arg(gasName); + + if (!gasName.isNull() || showUse) + gasType += ")"; + + return gasType; +} + +std::vector> get_dive_gas_list(const struct dive *d, int dcNr, bool showOnlyAppropriate) +{ + const divecomputer *dc = d->get_dc(dcNr); + std::vector> list; + for (unsigned int i = 0; i < d->cylinders.size(); i++) { + if (showOnlyAppropriate && !is_cylinder_use_appropriate(*dc, *d->get_cylinder(i), false)) + continue; + + list.push_back(std::pair(i, get_dive_gas(d, dcNr, i))); + } + return list; } @@ -1240,7 +1194,7 @@ QStringList stringToList(const QString &s) weight_t string_to_weight(const char *str) { const char *end; - double value = strtod_flags(str, &end, 0); + double value = permissive_strtod(str, &end); QString rest = QString(end).trimmed(); QString local_kg = gettextFromC::tr("kg"); QString local_lbs = gettextFromC::tr("lbs"); @@ -1264,7 +1218,7 @@ lbs: depth_t string_to_depth(const char *str) { const char *end; - double value = strtod_flags(str, &end, 0); + double value = permissive_strtod(str, &end); QString rest = QString(end).trimmed(); QString local_ft = gettextFromC::tr("ft"); QString local_m = gettextFromC::tr("m"); @@ -1289,7 +1243,7 @@ ft: pressure_t string_to_pressure(const char *str) { const char *end; - double value = strtod_flags(str, &end, 0); + double value = permissive_strtod(str, &end); QString rest = QString(end).trimmed(); QString local_psi = gettextFromC::tr("psi"); QString local_bar = gettextFromC::tr("bar"); @@ -1312,7 +1266,7 @@ psi: volume_t string_to_volume(const char *str, pressure_t workp) { const char *end; - double value = strtod_flags(str, &end, 0); + double value = permissive_strtod(str, &end); QString rest = QString(end).trimmed(); QString local_l = gettextFromC::tr("l"); QString local_cuft = gettextFromC::tr("cuft"); @@ -1343,17 +1297,18 @@ l: fraction_t string_to_fraction(const char *str) { const char *end; - double value = strtod_flags(str, &end, 0); + double value = permissive_strtod(str, &end); fraction_t fraction; fraction.permille = lrint(value * 10); /* * Don't permit values less than zero or greater than 100% */ + // TODO: use std::clamp() once we have comparison on unit types if (fraction.permille < 0) - fraction.permille = 0; + fraction = 0_percent; else if (fraction.permille > 1000) - fraction.permille = 1000; + fraction = 100_percent; return fraction; } @@ -1373,28 +1328,25 @@ std::optional getCloudURL() { std::string email(prefs.cloud_storage_email); sanitize_email(email); - if (email.empty() || empty_string(prefs.cloud_storage_password)) { + if (email.empty() || prefs.cloud_storage_password.empty()) { report_error("Please configure Cloud storage email and password in the preferences"); return {}; } - if (email != prefs.cloud_storage_email_encoded) { - free((void *)prefs.cloud_storage_email_encoded); - prefs.cloud_storage_email_encoded = strdup(email.c_str()); - } + prefs.cloud_storage_email_encoded = email; std::string filename = std::string(prefs.cloud_base_url) + "git/" + email + "[" + email + "]"; if (verbose) report_info("returning cloud URL %s", filename.c_str()); return filename; } -extern "C" void subsurface_mkdir(const char *dir) +void subsurface_mkdir(const char *dir) { QDir directory; if (!directory.mkpath(QString(dir))) report_info("failed to create path %s", dir); } -extern "C" enum deco_mode decoMode(bool in_planner) +enum deco_mode decoMode(bool in_planner) { return in_planner ? prefs.planner_deco_mode : prefs.display_deco_mode; } @@ -1403,11 +1355,11 @@ void init_proxy() { QNetworkProxy proxy; proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); - proxy.setHostName(prefs.proxy_host); + proxy.setHostName(QString::fromStdString(prefs.proxy_host)); proxy.setPort(prefs.proxy_port); if (prefs.proxy_auth) { - proxy.setUser(prefs.proxy_user); - proxy.setPassword(prefs.proxy_pass); + proxy.setUser(QString::fromStdString(prefs.proxy_user)); + proxy.setPassword(QString::fromStdString(prefs.proxy_pass)); } QNetworkProxy::setApplicationProxy(proxy); } @@ -1554,28 +1506,23 @@ void parse_seabear_header(const char *filename, struct xml_params *params) f.close(); } -extern "C" void print_qt_versions() +void print_qt_versions() { printf("%s\n", qPrintable(QStringLiteral("built with Qt Version %1, runtime from Qt Version %2").arg(QT_VERSION_STR).arg(qVersion()))); } QMutex planLock; -extern "C" void lock_planner() +void lock_planner() { planLock.lock(); } -extern "C" void unlock_planner() +void unlock_planner() { planLock.unlock(); } -char *copy_qstring(const QString &s) -{ - return strdup(qPrintable(s)); -} - // function to call to allow the UI to show updates for longer running activities void (*uiNotificationCallback)(QString msg) = nullptr; @@ -1627,7 +1574,7 @@ std::vector get_cylinder_map_for_add(int count, int n) return mapping; } -extern "C" void emit_reset_signal() +void emit_reset_signal() { emit diveListNotifier.dataReset(); } diff --git a/core/qthelper.h b/core/qthelper.h index 6bc89a11c..75ee8b15c 100644 --- a/core/qthelper.h +++ b/core/qthelper.h @@ -2,40 +2,34 @@ #ifndef QTHELPER_H #define QTHELPER_H +#include "core/pref.h" +#include "core/gettextfromc.h" +#include "subsurface-time.h" +#include +#include +#include #include #include -#include "core/pref.h" -#include "subsurface-time.h" struct picture; struct dive_trip; struct xml_params; - -// 1) Types +struct git_info; +class QImage; enum watertypes {FRESHWATER, BRACKISHWATER, EN13319WATER, SALTWATER, DC_WATERTYPE}; -// 2) Functions visible only to C++ parts - -#ifdef __cplusplus - -#include -#include -#include -#include "core/gettextfromc.h" -class QImage; - #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) #define SKIP_EMPTY Qt::SkipEmptyParts #else #define SKIP_EMPTY QString::SkipEmptyParts #endif -QString weight_string(int weight_in_grams); QString distance_string(int distanceInMeters); bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out = 0); QString get_gas_string(struct gasmix gas); -QStringList get_dive_gas_list(const struct dive *d); +QString get_dive_gas(const struct dive *d, int dcNr, int cylinderId); +std::vector> get_dive_gas_list(const struct dive *d, int dcNr, bool showOnlyAppropriate); QStringList stringToList(const QString &s); void read_hashes(); void write_hashes(); @@ -50,7 +44,6 @@ extern const QStringList videoExtensionsList; QStringList mediaExtensionFilters(); QStringList imageExtensionFilters(); QStringList videoExtensionFilters(); -char *copy_qstring(const QString &); QString get_depth_string(depth_t depth, bool showunit = false, bool showdecimal = true); QString get_depth_string(int mm, bool showunit = false, bool showdecimal = true); QString get_depth_unit(bool metric); @@ -71,13 +64,11 @@ QString get_water_type_string(int salinity); QString getSubsurfaceDataPath(QString folderToFind); QString getPrintingTemplatePathUser(); QString getPrintingTemplatePathBundle(); -int gettimezoneoffset(); QDateTime timestampToDateTime(timestamp_t when); timestamp_t dateTimeToTimestamp(const QDateTime &t); int parseDurationToSeconds(const QString &text); int parseLengthToMm(const QString &text); int parseTemperatureToMkelvin(const QString &text); -int parseWeightToGrams(const QString &text); int parsePressureToMbar(const QString &text); int parseGasMixO2(const QString &text); int parseGasMixHE(const QString &text); @@ -104,7 +95,7 @@ extern QString (*changesCallback)(); void uiNotification(const QString &msg); std::string get_changes_made(); std::string subsurface_user_agent(); -std::string get_file_name(const char *fileName); +std::string get_file_name(const std::string &fileName); std::string move_away(const std::string &path); #if defined __APPLE__ @@ -113,25 +104,14 @@ std::string move_away(const std::string &path); #define TITLE_OR_TEXT(_t, _m) _t, _m #endif -#endif - -// 3) Functions visible to C and C++ - -#ifdef __cplusplus -extern "C" { -#endif - -struct git_info; - bool canReachCloudServer(struct git_info *); void updateWindowTitle(); void subsurface_mkdir(const char *dir); -void copy_image_and_overwrite(const char *cfileName, const char *path, const char *cnewName); -const char *local_file_path(struct picture *picture); -char *hashfile_name_string(); +std::string local_file_path(const struct picture &picture); +std::string hashfile_name(); enum deco_mode decoMode(bool in_planner); void parse_seabear_header(const char *filename, struct xml_params *params); -time_t get_dive_datetime_from_isostring(char *when); +time_t get_dive_datetime_from_isostring(const char *when); void print_qt_versions(); void lock_planner(); void unlock_planner(); @@ -143,8 +123,4 @@ volume_t string_to_volume(const char *str, pressure_t workp); fraction_t string_to_fraction(const char *str); void emit_reset_signal(); -#ifdef __cplusplus -} -#endif - #endif // QTHELPER_H diff --git a/core/qtserialbluetooth.cpp b/core/qtserialbluetooth.cpp index 49d077c2b..956071ed6 100644 --- a/core/qtserialbluetooth.cpp +++ b/core/qtserialbluetooth.cpp @@ -31,14 +31,13 @@ static std::string to_str(const T &v) return v.toString().toStdString(); } -extern "C" { -typedef struct qt_serial_t { +struct qt_serial_t { /* * RFCOMM socket used for Bluetooth Serial communication. */ QBluetoothSocket *socket; long timeout; -} qt_serial_t; +}; static dc_status_t qt_serial_open(qt_serial_t **io, dc_context_t*, const char *devaddr) { @@ -347,5 +346,3 @@ rfcomm_stream_open(dc_iostream_t **iostream, dc_context_t *context, const char* return dc_custom_open (iostream, context, DC_TRANSPORT_BLUETOOTH, &callbacks, io); } - -} diff --git a/core/range.h b/core/range.h index ad38b9dc2..48178ecb3 100644 --- a/core/range.h +++ b/core/range.h @@ -32,7 +32,79 @@ void move_in_range(Range &v, int rangeBegin, int rangeEnd, int destination) std::rotate(it + destination, it + rangeBegin, it + rangeEnd); } -// A rudimentary adaptor for looping over ranges with an index: +// Small helper base class for iterator adapters. +template +class iterator_adapter { +protected: + Base it; +public: + iterator_adapter(Base it) : it(it) + { + } + bool operator==(const iterator_adapter &it2) const { + return it == it2.it; + } + bool operator!=(const iterator_adapter &it2) const + { + return it != it2.it; + } +}; + +// A rudimentary adapter for looping over pairs of elements in ranges: +// for (auto [it1, it2]: pairwise_range(v)) ... +// The pairs are overlapping, i.e. there is one less pair than elements: +// { 1, 2, 3, 4 } -> (1,2), (2,3), (3,4) +template +class pairwise_range +{ + Range &base; +public: + using base_iterator = decltype(std::begin(std::declval())); + using item_type = decltype(*std::begin(base)); + class iterator : public iterator_adapter { + public: + using iterator_adapter::iterator_adapter; + std::pair operator*() const + { + return { *this->it, *std::next(this->it) }; + } + iterator &operator++() + { + ++this->it; + return *this; + } + iterator &operator--() + { + --this->it; + return *this; + } + iterator operator++(int) + { + return iterator(this->it++); + } + iterator operator--(int) + { + return iterator(this->it--); + } + }; + + iterator begin() + { + return iterator(std::begin(base)); + } + iterator end() + { + return std::begin(base) == std::end(base) ? + iterator(std::begin(base)) : + iterator(std::prev(std::end(base))); + } + + pairwise_range(Range &base): base(base) + { + } +}; + +// A rudimentary adapter for looping over ranges with an index: // for (auto [idx, item]: enumerated_range(v)) ... // The index is a signed integer, since this is what we use more often. template @@ -41,30 +113,31 @@ class enumerated_range Range &base; public: using base_iterator = decltype(std::begin(std::declval())); - class iterator { + class iterator : public iterator_adapter{ int idx; - base_iterator it; public: - std::pair operator*() const + using iterator_adapter::iterator_adapter; + using item_type = decltype(*std::begin(base)); + std::pair operator*() const { - return { idx, *it }; + return { idx, *this->it }; } iterator &operator++() { ++idx; - ++it; + ++this->it; return *this; } - iterator(int idx, base_iterator it) : idx(idx), it(it) + iterator &operator--() { + --idx; + --this->it; + return *this; } - bool operator==(const iterator &it2) const + iterator &operator++(int) = delete; // Postfix increment/decrement not supported for now + iterator &operator--(int) = delete; // Postfix increment/decrement not supported for now + iterator(int idx, base_iterator it) : iterator_adapter(it), idx(idx) { - return it == it2.it; - } - bool operator!=(const iterator &it2) const - { - return it != it2.it; } }; @@ -82,6 +155,7 @@ public: } }; + // Find the index of an element in a range. Return -1 if not found // Range must have a random access iterator. template @@ -98,4 +172,38 @@ int index_of_if(const Range &range, Func f) return it == std::end(range) ? -1 : it - std::begin(range); } +// Not really appropriate here, but oh my. +template +bool range_contains(const Range &v, const Element &item) +{ + return std::find(std::begin(v), std::end(v), item) != v.end(); +} + +// Insert into an already sorted range +template +void range_insert_sorted(Range &v, Element &item, Comp comp) +{ + auto it = std::lower_bound(std::begin(v), std::end(v), item, + [&comp](auto &a, auto &b) { return comp(a, b) < 0; }); + v.insert(it, std::move(item)); +} + +// Insert into an already sorted range, but don't add an item twice +template +void range_insert_sorted_unique(Range &v, Element &item, Comp comp) +{ + auto it = std::lower_bound(std::begin(v), std::end(v), item, + [&comp](auto &a, auto &b) { return comp(a, b) < 0; }); + if (it == std::end(v) || comp(item, *it) != 0) + v.insert(it, std::move(item)); +} + +template +void range_remove(Range &v, const Element &item) +{ + auto it = std::find(std::begin(v), std::end(v), item); + if (it != std::end(v)) + v.erase(it); +} + #endif diff --git a/core/sample.cpp b/core/sample.cpp index ff2cbfa67..23d8899cf 100644 --- a/core/sample.cpp +++ b/core/sample.cpp @@ -2,27 +2,7 @@ #include "sample.h" -sample::sample() : - time({ 0 }), - stoptime({ 0 }), - ndl({ -1 }), - tts({ 0 }), - rbt({ 0 }), - depth({ 0 }), - stopdepth({ 0 }), - temperature({ 0 }), - pressure { { 0 }, { 0 } }, - setpoint({ 0 }), - o2sensor { { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 } }, - bearing({ -1 }), - sensor { 0, 0 }, - cns(0), - heartbeat(0), - sac({ 0 }), - in_deco(false), - manually_entered(false) -{ -} +sample::sample() = default; /* * Adding a cylinder pressure sample field is not quite as trivial as it @@ -37,7 +17,7 @@ sample::sample() : * from the previous sample, so the indices are pre-populated (but the * pressures obviously are not) */ -extern "C" void add_sample_pressure(struct sample *sample, int sensor, int mbar) +void add_sample_pressure(struct sample *sample, int sensor, int mbar) { int idx; diff --git a/core/sample.h b/core/sample.h index 7e3655d98..8b517a149 100644 --- a/core/sample.h +++ b/core/sample.h @@ -4,10 +4,6 @@ #include "units.h" -#ifdef __cplusplus -extern "C" { -#endif - #define MAX_SENSORS 2 #define MAX_O2_SENSORS 6 #define NO_SENSOR -1 @@ -16,7 +12,7 @@ struct sample // BASE TYPE BYTES UNITS RANGE { // --------- ----- ----- ----- ----------- duration_t time; // int32_t 4 seconds (0-34 yrs) elapsed dive time up to this sample duration_t stoptime; // int32_t 4 seconds (0-34 yrs) time duration of next deco stop - duration_t ndl; // int32_t 4 seconds (-1 no val, 0-34 yrs) time duration before no-deco limit + duration_t ndl = { .seconds = -1 };// int32_t 4 seconds (-1 no val, 0-34 yrs) time duration before no-deco limit duration_t tts; // int32_t 4 seconds (0-34 yrs) time duration to reach the surface duration_t rbt; // int32_t 4 seconds (0-34 yrs) remaining bottom time depth_t depth; // int32_t 4 mm (0-2000 km) dive depth of this sample @@ -25,23 +21,17 @@ struct sample // BASE TYPE BYTES UNITS RANGE pressure_t pressure[MAX_SENSORS]; // int32_t 2x4 mbar (0-2 Mbar) cylinder pressures (main and CCR o2) o2pressure_t setpoint; // uint16_t 2 mbar (0-65 bar) O2 partial pressure (will be setpoint) o2pressure_t o2sensor[MAX_O2_SENSORS];// uint16_t 6x2 mbar (0-65 bar) Up to 6 PO2 sensor values (rebreather) - bearing_t bearing; // int16_t 2 degrees (-1 no val, 0-360 deg) compass bearing - int16_t sensor[MAX_SENSORS]; // int16_t 2x2 sensorID (0-16k) ID of cylinder pressure sensor - uint16_t cns; // uint16_t 2 % (0-64k %) cns% accumulated - uint8_t heartbeat; // uint8_t 1 beats/m (0-255) heart rate measurement + bearing_t bearing = { .degrees = -1 };// int16_t 2 degrees (-1 no val, 0-360 deg) compass bearing + int16_t sensor[MAX_SENSORS] = {}; // int16_t 2x2 sensorID (0-16k) ID of cylinder pressure sensor + uint16_t cns = 0; // uint16_t 2 % (0-64k %) cns% accumulated + uint8_t heartbeat = 0; // uint8_t 1 beats/m (0-255) heart rate measurement volume_t sac; // 4 ml/min predefined SAC - bool in_deco; // bool 1 y/n y/n this sample is part of deco - bool manually_entered; // bool 1 y/n y/n this sample was entered by the user, + bool in_deco = false; // bool 1 y/n y/n this sample is part of deco + bool manually_entered = false; // bool 1 y/n y/n this sample was entered by the user, // not calculated when planning a dive -#ifdef __cplusplus sample(); // Default constructor -#endif }; // Total size of structure: 63 bytes, excluding padding at end extern void add_sample_pressure(struct sample *sample, int sensor, int mbar); -#ifdef __cplusplus -} -#endif - #endif diff --git a/core/save-git.cpp b/core/save-git.cpp index ff838dda5..b1c95b85b 100644 --- a/core/save-git.cpp +++ b/core/save-git.cpp @@ -4,7 +4,6 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "ssrf.h" #include #include #include @@ -34,6 +33,7 @@ #include "version.h" #include "picture.h" #include "qthelper.h" +#include "range.h" #include "gettext.h" #include "tag.h" #include "subsurface-time.h" @@ -47,7 +47,7 @@ static void cond_put_format(int cond, struct membuffer *b, const char *fmt, ...) } } -#define SAVE(str, x) cond_put_format(dive->x, b, str " %d\n", dive->x) +#define SAVE(str, x) cond_put_format(dive.x, b, str " %d\n", dive.x) static void quote(struct membuffer *b, const char *text) { @@ -97,35 +97,33 @@ static void show_utf8(struct membuffer *b, const char *prefix, const char *value } } -static void save_overview(struct membuffer *b, struct dive *dive) +static void save_overview(struct membuffer *b, const struct dive &dive) { - show_utf8(b, "divemaster ", dive->diveguide, "\n"); - show_utf8(b, "buddy ", dive->buddy, "\n"); - show_utf8(b, "suit ", dive->suit, "\n"); - show_utf8(b, "notes ", dive->notes, "\n"); + show_utf8(b, "divemaster ", dive.diveguide.c_str(), "\n"); + show_utf8(b, "buddy ", dive.buddy.c_str(), "\n"); + show_utf8(b, "suit ", dive.suit.c_str(), "\n"); + show_utf8(b, "notes ", dive.notes.c_str(), "\n"); } -static void save_tags(struct membuffer *b, struct tag_entry *tags) +static void save_tags(struct membuffer *b, const tag_list &tags) { const char *sep = " "; - if (!tags) + if (tags.empty()) return; put_string(b, "tags"); - while (tags) { - show_utf8(b, sep, tags->tag->source.empty() ? tags->tag->name.c_str() : tags->tag->source.c_str(), ""); + for (const divetag *tag: tags) { + show_utf8(b, sep, tag->source.empty() ? tag->name.c_str() : tag->source.c_str(), ""); sep = ", "; - tags = tags->next; } put_string(b, "\n"); } -static void save_extra_data(struct membuffer *b, struct extra_data *ed) +static void save_extra_data(struct membuffer *b, const struct divecomputer &dc) { - while (ed) { - if (ed->key && ed->value) - put_format(b, "keyvalue \"%s\" \"%s\"\n", ed->key ? : "", ed->value ? : ""); - ed = ed->next; + for (const auto &ed: dc.extra_data) { + if (!ed.key.empty() && !ed.value.empty()) + put_format(b, "keyvalue \"%s\" \"%s\"\n", ed.key.c_str(), ed.value.c_str()); } } @@ -141,81 +139,71 @@ static void put_gasmix(struct membuffer *b, struct gasmix mix) } } -static void save_cylinder_info(struct membuffer *b, struct dive *dive) +static void save_cylinder_info(struct membuffer *b, const struct dive &dive) { - int i, nr; - - nr = nr_cylinders(dive); - for (i = 0; i < nr; i++) { - cylinder_t *cylinder = get_cylinder(dive, i); - int volume = cylinder->type.size.mliter; - const char *description = cylinder->type.description; - int use = cylinder->cylinder_use; + for (auto &cyl: dive.cylinders) { + int volume = cyl.type.size.mliter; + int use = cyl.cylinder_use; put_string(b, "cylinder"); if (volume) put_milli(b, " vol=", volume, "l"); - put_pressure(b, cylinder->type.workingpressure, " workpressure=", "bar"); - show_utf8(b, " description=", description, ""); + put_pressure(b, cyl.type.workingpressure, " workpressure=", "bar"); + show_utf8(b, " description=", cyl.type.description.c_str(), ""); strip_mb(b); - put_gasmix(b, cylinder->gasmix); - put_pressure(b, cylinder->start, " start=", "bar"); - put_pressure(b, cylinder->end, " end=", "bar"); + put_gasmix(b, cyl.gasmix); + put_pressure(b, cyl.start, " start=", "bar"); + put_pressure(b, cyl.end, " end=", "bar"); if (use > OC_GAS && use < NUM_GAS_USE) show_utf8(b, " use=", cylinderuse_text[use], ""); - if (cylinder->depth.mm != 0) - put_milli(b, " depth=", cylinder->depth.mm, "m"); + if (cyl.depth.mm != 0) + put_milli(b, " depth=", cyl.depth.mm, "m"); put_string(b, "\n"); } } -static void save_weightsystem_info(struct membuffer *b, struct dive *dive) +static void save_weightsystem_info(struct membuffer *b, const struct dive &dive) { - int i, nr; - - nr = nr_weightsystems(dive); - for (i = 0; i < nr; i++) { - weightsystem_t ws = dive->weightsystems.weightsystems[i]; + for (auto &ws: dive.weightsystems) { int grams = ws.weight.grams; - const char *description = ws.description; put_string(b, "weightsystem"); put_milli(b, " weight=", grams, "kg"); - show_utf8(b, " description=", description, ""); + show_utf8(b, " description=", ws.description.c_str(), ""); put_string(b, "\n"); } } -static void save_dive_temperature(struct membuffer *b, struct dive *dive) +static void save_dive_temperature(struct membuffer *b, const struct dive &dive) { - if (dive->airtemp.mkelvin != dc_airtemp(&dive->dc)) - put_temperature(b, dive->airtemp, "airtemp ", "°C\n"); - if (dive->watertemp.mkelvin != dc_watertemp(&dive->dc)) - put_temperature(b, dive->watertemp, "watertemp ", "°C\n"); + if (dive.airtemp.mkelvin != dive.dc_airtemp().mkelvin) + put_temperature(b, dive.airtemp, "airtemp ", "°C\n"); + if (dive.watertemp.mkelvin != dive.dc_watertemp().mkelvin) + put_temperature(b, dive.watertemp, "watertemp ", "°C\n"); } -static void save_depths(struct membuffer *b, struct divecomputer *dc) +static void save_depths(struct membuffer *b, const struct divecomputer &dc) { - put_depth(b, dc->maxdepth, "maxdepth ", "m\n"); - put_depth(b, dc->meandepth, "meandepth ", "m\n"); + put_depth(b, dc.maxdepth, "maxdepth ", "m\n"); + put_depth(b, dc.meandepth, "meandepth ", "m\n"); } -static void save_temperatures(struct membuffer *b, struct divecomputer *dc) +static void save_temperatures(struct membuffer *b, const struct divecomputer &dc) { - put_temperature(b, dc->airtemp, "airtemp ", "°C\n"); - put_temperature(b, dc->watertemp, "watertemp ", "°C\n"); + put_temperature(b, dc.airtemp, "airtemp ", "°C\n"); + put_temperature(b, dc.watertemp, "watertemp ", "°C\n"); } -static void save_airpressure(struct membuffer *b, struct divecomputer *dc) +static void save_airpressure(struct membuffer *b, const struct divecomputer &dc) { - put_pressure(b, dc->surface_pressure, "surfacepressure ", "bar\n"); + put_pressure(b, dc.surface_pressure, "surfacepressure ", "bar\n"); } -static void save_salinity(struct membuffer *b, struct divecomputer *dc) +static void save_salinity(struct membuffer *b, const struct divecomputer &dc) { - if (!dc->salinity) + if (!dc.salinity) return; - put_salinity(b, dc->salinity, "salinity ", "g/l\n"); + put_salinity(b, dc.salinity, "salinity ", "g/l\n"); } static void show_date(struct membuffer *b, timestamp_t when) @@ -247,17 +235,17 @@ static void show_index(struct membuffer *b, int value, const char *pre, const ch * * For parsing, look at the units to figure out what the numbers are. */ -static void save_sample(struct membuffer *b, struct sample *sample, struct sample *old, int o2sensor) +static void save_sample(struct membuffer *b, const struct sample &sample, struct sample &old, int o2sensor) { int idx; - put_format(b, "%3u:%02u", FRACTION_TUPLE(sample->time.seconds, 60)); - put_milli(b, " ", sample->depth.mm, "m"); - put_temperature(b, sample->temperature, " ", "°C"); + put_format(b, "%3u:%02u", FRACTION_TUPLE(sample.time.seconds, 60)); + put_milli(b, " ", sample.depth.mm, "m"); + put_temperature(b, sample.temperature, " ", "°C"); for (idx = 0; idx < MAX_SENSORS; idx++) { - pressure_t p = sample->pressure[idx]; - int sensor = sample->sensor[idx]; + pressure_t p = sample.pressure[idx]; + int sensor = sample.sensor[idx]; if (sensor == NO_SENSOR) continue; @@ -268,7 +256,7 @@ static void save_sample(struct membuffer *b, struct sample *sample, struct sampl /* Old-style "o2sensor" syntax for CCR dives? */ if (o2sensor >= 0) { if (sensor == o2sensor) { - put_pressure(b, sample->pressure[1]," o2pressure=","bar"); + put_pressure(b, sample.pressure[1]," o2pressure=","bar"); continue; } @@ -277,11 +265,11 @@ static void save_sample(struct membuffer *b, struct sample *sample, struct sampl /* * Note: regardless of which index we used for the non-O2 * sensor, we know there is only one non-O2 sensor in legacy - * mode, and "old->sensor[0]" contains that index. + * mode, and "old.sensor[0]" contains that index. */ - if (sensor != old->sensor[0]) { + if (sensor != old.sensor[0]) { put_format(b, " sensor=%d", sensor); - old->sensor[0] = sensor; + old.sensor[0] = sensor; } continue; } @@ -292,159 +280,151 @@ static void save_sample(struct membuffer *b, struct sample *sample, struct sampl } /* the deco/ndl values are stored whenever they change */ - if (sample->ndl.seconds != old->ndl.seconds) { - put_format(b, " ndl=%u:%02u", FRACTION_TUPLE(sample->ndl.seconds, 60)); - old->ndl = sample->ndl; + if (sample.ndl.seconds != old.ndl.seconds) { + put_format(b, " ndl=%u:%02u", FRACTION_TUPLE(sample.ndl.seconds, 60)); + old.ndl = sample.ndl; } - if (sample->tts.seconds != old->tts.seconds) { - put_format(b, " tts=%u:%02u", FRACTION_TUPLE(sample->tts.seconds, 60)); - old->tts = sample->tts; + if (sample.tts.seconds != old.tts.seconds) { + put_format(b, " tts=%u:%02u", FRACTION_TUPLE(sample.tts.seconds, 60)); + old.tts = sample.tts; } - if (sample->in_deco != old->in_deco) { - put_format(b, " in_deco=%d", sample->in_deco ? 1 : 0); - old->in_deco = sample->in_deco; + if (sample.in_deco != old.in_deco) { + put_format(b, " in_deco=%d", sample.in_deco ? 1 : 0); + old.in_deco = sample.in_deco; } - if (sample->stoptime.seconds != old->stoptime.seconds) { - put_format(b, " stoptime=%u:%02u", FRACTION_TUPLE(sample->stoptime.seconds, 60)); - old->stoptime = sample->stoptime; + if (sample.stoptime.seconds != old.stoptime.seconds) { + put_format(b, " stoptime=%u:%02u", FRACTION_TUPLE(sample.stoptime.seconds, 60)); + old.stoptime = sample.stoptime; } - if (sample->stopdepth.mm != old->stopdepth.mm) { - put_milli(b, " stopdepth=", sample->stopdepth.mm, "m"); - old->stopdepth = sample->stopdepth; + if (sample.stopdepth.mm != old.stopdepth.mm) { + put_milli(b, " stopdepth=", sample.stopdepth.mm, "m"); + old.stopdepth = sample.stopdepth; } - if (sample->cns != old->cns) { - put_format(b, " cns=%u%%", sample->cns); - old->cns = sample->cns; + if (sample.cns != old.cns) { + put_format(b, " cns=%u%%", sample.cns); + old.cns = sample.cns; } - if (sample->rbt.seconds != old->rbt.seconds) { - put_format(b, " rbt=%u:%02u", FRACTION_TUPLE(sample->rbt.seconds, 60)); - old->rbt.seconds = sample->rbt.seconds; + if (sample.rbt.seconds != old.rbt.seconds) { + put_format(b, " rbt=%u:%02u", FRACTION_TUPLE(sample.rbt.seconds, 60)); + old.rbt.seconds = sample.rbt.seconds; } - if (sample->o2sensor[0].mbar != old->o2sensor[0].mbar) { - put_milli(b, " sensor1=", sample->o2sensor[0].mbar, "bar"); - old->o2sensor[0] = sample->o2sensor[0]; + if (sample.o2sensor[0].mbar != old.o2sensor[0].mbar) { + put_milli(b, " sensor1=", sample.o2sensor[0].mbar, "bar"); + old.o2sensor[0] = sample.o2sensor[0]; } - if ((sample->o2sensor[1].mbar) && (sample->o2sensor[1].mbar != old->o2sensor[1].mbar)) { - put_milli(b, " sensor2=", sample->o2sensor[1].mbar, "bar"); - old->o2sensor[1] = sample->o2sensor[1]; + if ((sample.o2sensor[1].mbar) && (sample.o2sensor[1].mbar != old.o2sensor[1].mbar)) { + put_milli(b, " sensor2=", sample.o2sensor[1].mbar, "bar"); + old.o2sensor[1] = sample.o2sensor[1]; } - if ((sample->o2sensor[2].mbar) && (sample->o2sensor[2].mbar != old->o2sensor[2].mbar)) { - put_milli(b, " sensor3=", sample->o2sensor[2].mbar, "bar"); - old->o2sensor[2] = sample->o2sensor[2]; + if ((sample.o2sensor[2].mbar) && (sample.o2sensor[2].mbar != old.o2sensor[2].mbar)) { + put_milli(b, " sensor3=", sample.o2sensor[2].mbar, "bar"); + old.o2sensor[2] = sample.o2sensor[2]; } - if ((sample->o2sensor[3].mbar) && (sample->o2sensor[3].mbar != old->o2sensor[3].mbar)) { - put_milli(b, " sensor4=", sample->o2sensor[3].mbar, "bar"); - old->o2sensor[3] = sample->o2sensor[3]; + if ((sample.o2sensor[3].mbar) && (sample.o2sensor[3].mbar != old.o2sensor[3].mbar)) { + put_milli(b, " sensor4=", sample.o2sensor[3].mbar, "bar"); + old.o2sensor[3] = sample.o2sensor[3]; } - if ((sample->o2sensor[4].mbar) && (sample->o2sensor[4].mbar != old->o2sensor[4].mbar)) { - put_milli(b, " sensor5=", sample->o2sensor[4].mbar, "bar"); - old->o2sensor[4] = sample->o2sensor[4]; + if ((sample.o2sensor[4].mbar) && (sample.o2sensor[4].mbar != old.o2sensor[4].mbar)) { + put_milli(b, " sensor5=", sample.o2sensor[4].mbar, "bar"); + old.o2sensor[4] = sample.o2sensor[4]; } - if ((sample->o2sensor[5].mbar) && (sample->o2sensor[5].mbar != old->o2sensor[5].mbar)) { - put_milli(b, " sensor6=", sample->o2sensor[5].mbar, "bar"); - old->o2sensor[5] = sample->o2sensor[5]; + if ((sample.o2sensor[5].mbar) && (sample.o2sensor[5].mbar != old.o2sensor[5].mbar)) { + put_milli(b, " sensor6=", sample.o2sensor[5].mbar, "bar"); + old.o2sensor[5] = sample.o2sensor[5]; } - if (sample->setpoint.mbar != old->setpoint.mbar) { - put_milli(b, " po2=", sample->setpoint.mbar, "bar"); - old->setpoint = sample->setpoint; + if (sample.setpoint.mbar != old.setpoint.mbar) { + put_milli(b, " po2=", sample.setpoint.mbar, "bar"); + old.setpoint = sample.setpoint; } - if (sample->heartbeat != old->heartbeat) { - show_index(b, sample->heartbeat, "heartbeat=", ""); - old->heartbeat = sample->heartbeat; + if (sample.heartbeat != old.heartbeat) { + show_index(b, sample.heartbeat, "heartbeat=", ""); + old.heartbeat = sample.heartbeat; } - if (sample->bearing.degrees != old->bearing.degrees) { - show_index(b, sample->bearing.degrees, "bearing=", "°"); - old->bearing.degrees = sample->bearing.degrees; + if (sample.bearing.degrees != old.bearing.degrees) { + show_index(b, sample.bearing.degrees, "bearing=", "°"); + old.bearing.degrees = sample.bearing.degrees; } put_format(b, "\n"); } -static void save_samples(struct membuffer *b, struct dive *dive, struct divecomputer *dc) +static void save_samples(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) { - int nr; int o2sensor; - struct sample *s; struct sample dummy; /* Is this a CCR dive with the old-style "o2pressure" sensor? */ - o2sensor = legacy_format_o2pressures(dive, dc); + o2sensor = legacy_format_o2pressures(&dive, &dc); if (o2sensor >= 0) { dummy.sensor[0] = !o2sensor; dummy.sensor[1] = o2sensor; } - s = dc->sample; - nr = dc->samples; - while (--nr >= 0) { - save_sample(b, s, &dummy, o2sensor); - s++; - } + for (const auto &s: dc.samples) + save_sample(b, s, dummy, o2sensor); } -static void save_one_event(struct membuffer *b, struct dive *dive, struct event *ev) +static void save_one_event(struct membuffer *b, const struct dive &dive, const struct event &ev) { - put_format(b, "event %d:%02d", FRACTION_TUPLE(ev->time.seconds, 60)); - show_index(b, ev->type, "type=", ""); - show_index(b, ev->flags, "flags=", ""); + put_format(b, "event %d:%02d", FRACTION_TUPLE(ev.time.seconds, 60)); + show_index(b, ev.type, "type=", ""); + show_index(b, ev.flags, "flags=", ""); - if (!strcmp(ev->name,"modechange")) - show_utf8(b, " divemode=", divemode_text[ev->value], ""); + if (ev.name == "modechange") + show_utf8(b, " divemode=", divemode_text[ev.value], ""); else - show_index(b, ev->value, "value=", ""); - show_utf8(b, " name=", ev->name, ""); - if (event_is_gaschange(ev)) { - struct gasmix mix = get_gasmix_from_event(dive, ev); - if (ev->gas.index >= 0) - show_integer(b, ev->gas.index, "cylinder=", ""); + show_index(b, ev.value, "value=", ""); + show_utf8(b, " name=", ev.name.c_str(), ""); + if (ev.is_gaschange()) { + struct gasmix mix = dive.get_gasmix_from_event(ev); + if (ev.gas.index >= 0) + show_integer(b, ev.gas.index, "cylinder=", ""); put_gasmix(b, mix); } put_string(b, "\n"); } -static void save_events(struct membuffer *b, struct dive *dive, struct event *ev) +static void save_events(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) { - while (ev) { + for (auto &ev: dc.events) save_one_event(b, dive, ev); - ev = ev->next; - } } -static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer *dc) +static void save_dc(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) { - show_utf8(b, "model ", dc->model, "\n"); - if (dc->last_manual_time.seconds) - put_duration(b, dc->last_manual_time, "lastmanualtime ", "min\n"); - if (dc->deviceid) - put_format(b, "deviceid %08x\n", dc->deviceid); - if (dc->diveid) - put_format(b, "diveid %08x\n", dc->diveid); - if (dc->when && dc->when != dive->when) - show_date(b, dc->when); - if (dc->duration.seconds && dc->duration.seconds != dive->dc.duration.seconds) - put_duration(b, dc->duration, "duration ", "min\n"); - if (dc->divemode != OC) { - put_format(b, "dctype %s\n", divemode_text[dc->divemode]); - put_format(b, "numberofoxygensensors %d\n",dc->no_o2sensors); + show_utf8(b, "model ", dc.model.c_str(), "\n"); + if (dc.last_manual_time.seconds) + put_duration(b, dc.last_manual_time, "lastmanualtime ", "min\n"); + if (dc.deviceid) + put_format(b, "deviceid %08x\n", dc.deviceid); + if (dc.diveid) + put_format(b, "diveid %08x\n", dc.diveid); + if (dc.when && dc.when != dive.when) + show_date(b, dc.when); + if (dc.duration.seconds && dc.duration.seconds != dive.dcs[0].duration.seconds) + put_duration(b, dc.duration, "duration ", "min\n"); + if (dc.divemode != OC) { + put_format(b, "dctype %s\n", divemode_text[dc.divemode]); + put_format(b, "numberofoxygensensors %d\n", dc.no_o2sensors); } save_depths(b, dc); save_temperatures(b, dc); save_airpressure(b, dc); save_salinity(b, dc); - put_duration(b, dc->surfacetime, "surfacetime ", "min\n"); + put_duration(b, dc.surfacetime, "surfacetime ", "min\n"); - save_extra_data(b, dc->extra_data); - save_events(b, dive, dc->events); + save_extra_data(b, dc); + save_events(b, dive, dc); save_samples(b, dive, dc); } @@ -452,28 +432,28 @@ static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer * Note that we don't save the date and time or dive * number: they are encoded in the filename. */ -static void create_dive_buffer(struct dive *dive, struct membuffer *b) +static void create_dive_buffer(const struct dive &dive, struct membuffer *b) { - pressure_t surface_pressure = un_fixup_surface_pressure(dive); - if (dive->dc.duration.seconds > 0) - put_format(b, "duration %u:%02u min\n", FRACTION_TUPLE(dive->dc.duration.seconds, 60)); + pressure_t surface_pressure = dive.un_fixup_surface_pressure(); + if (dive.dcs[0].duration.seconds > 0) + put_format(b, "duration %u:%02u min\n", FRACTION_TUPLE(dive.dcs[0].duration.seconds, 60)); SAVE("rating", rating); SAVE("visibility", visibility); SAVE("wavesize", wavesize); SAVE("current", current); SAVE("surge", surge); SAVE("chill", chill); - if (dive->user_salinity) - put_format(b, "watersalinity %d g/l\n", (int)(dive->user_salinity/10)); + if (dive.user_salinity) + put_format(b, "watersalinity %d g/l\n", (int)(dive.user_salinity/10)); if (surface_pressure.mbar) SAVE("airpressure", surface_pressure.mbar); - cond_put_format(dive->notrip, b, "notrip\n"); - cond_put_format(dive->invalid, b, "invalid\n"); - save_tags(b, dive->tag_list); - if (dive->dive_site) - put_format(b, "divesiteid %08x\n", dive->dive_site->uuid); - if (verbose && dive->dive_site) - report_info("removed reference to non-existant dive site with uuid %08x\n", dive->dive_site->uuid); + cond_put_format(dive.notrip, b, "notrip\n"); + cond_put_format(dive.invalid, b, "invalid\n"); + save_tags(b, dive.tags); + if (dive.dive_site) + put_format(b, "divesiteid %08x\n", dive.dive_site->uuid); + if (verbose && dive.dive_site) + report_info("removed reference to non-existant dive site with uuid %08x\n", dive.dive_site->uuid); save_overview(b, dive); save_cylinder_info(b, dive); save_weightsystem_info(b, dive); @@ -513,7 +493,7 @@ struct dir { static int tree_insert(git_treebuilder *dir, const char *name, int mkunique, git_oid *id, git_filemode_t mode) { int ret; - membufferpp uniquename; + membuffer uniquename; if (mkunique && git_treebuilder_get(dir, name)) { char hex[8]; @@ -560,7 +540,7 @@ static struct dir *new_directory(git_repository *repo, struct dir *parent, struc static struct dir *mktree(git_repository *repo, struct dir *dir, const char *fmt, ...) { - membufferpp buf; + membuffer buf; VA_BUF(&buf, fmt); for (auto &subdir: dir->subdirs) { @@ -586,12 +566,12 @@ static struct dir *mktree(git_repository *repo, struct dir *dir, const char *fmt * We do *not* want to use localized weekdays and cause peoples save * formats to depend on their locale. */ -static void create_dive_name(struct dive *dive, struct membuffer *name, struct tm *dirtm) +static void create_dive_name(struct dive &dive, struct membuffer *name, struct tm *dirtm) { struct tm tm; static const char weekday[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - utc_mkdate(dive->when, &tm); + utc_mkdate(dive.when, &tm); if (tm.tm_year != dirtm->tm_year) put_format(name, "%04u-", tm.tm_year); if (tm.tm_mon != dirtm->tm_mon) @@ -610,7 +590,7 @@ static int blob_insert(git_repository *repo, struct dir *tree, struct membuffer { int ret; git_oid blob_id; - struct membufferpp name; + membuffer name; ret = git_blob_create_frombuffer(&blob_id, repo, b->buffer, b->len); if (ret) @@ -621,10 +601,10 @@ static int blob_insert(git_repository *repo, struct dir *tree, struct membuffer return ret; } -static int save_one_divecomputer(git_repository *repo, struct dir *tree, struct dive *dive, struct divecomputer *dc, int idx) +static int save_one_divecomputer(git_repository *repo, struct dir *tree, const struct dive &dive, const struct divecomputer &dc, int idx) { int ret; - struct membufferpp buf; + membuffer buf; save_dc(&buf, dive, dc); ret = blob_insert(repo, tree, &buf, "Divecomputer%c%03u", idx ? '-' : 0, idx); @@ -633,15 +613,15 @@ static int save_one_divecomputer(git_repository *repo, struct dir *tree, struct return ret; } -static int save_one_picture(git_repository *repo, struct dir *dir, struct picture *pic) +static int save_one_picture(git_repository *repo, struct dir *dir, const struct picture &pic) { - int offset = pic->offset.seconds; - struct membufferpp buf; + int offset = pic.offset.seconds; + membuffer buf; char sign = '+'; unsigned h; - show_utf8(&buf, "filename ", pic->filename, "\n"); - put_location(&buf, &pic->location, "gps ", "\n"); + show_utf8(&buf, "filename ", pic.filename.c_str(), "\n"); + put_location(&buf, &pic.location, "gps ", "\n"); /* Picture loading will load even negative offsets.. */ if (offset < 0) { @@ -656,21 +636,19 @@ static int save_one_picture(git_repository *repo, struct dir *dir, struct pictur sign, h, FRACTION_TUPLE(offset, 60)); } -static int save_pictures(git_repository *repo, struct dir *dir, struct dive *dive) +static int save_pictures(git_repository *repo, struct dir *dir, const struct dive &dive) { - if (dive->pictures.nr > 0) { + if (!dive.pictures.empty()) { dir = mktree(repo, dir, "Pictures"); - FOR_EACH_PICTURE(dive) { + for (auto &picture: dive.pictures) save_one_picture(repo, dir, picture); - } } return 0; } -static int save_one_dive(git_repository *repo, struct dir *tree, struct dive *dive, struct tm *tm, bool cached_ok) +static int save_one_dive(git_repository *repo, struct dir *tree, struct dive &dive, struct tm *tm, bool cached_ok) { - struct divecomputer *dc; - struct membufferpp buf, name; + membuffer buf, name; struct dir *subdir; int ret, nr; @@ -681,9 +659,9 @@ static int save_one_dive(git_repository *repo, struct dir *tree, struct dive *di * If the dive git ID is valid, we just create the whole directory * with that ID */ - if (cached_ok && dive_cache_is_valid(dive)) { + if (cached_ok && dive.cache_is_valid()) { git_oid oid; - git_oid_fromraw(&oid, dive->git_id); + git_oid_fromraw(&oid, dive.git_id.data()); ret = tree_insert(tree->files, mb_cstring(&name), 1, &oid, GIT_FILEMODE_TREE); if (ret) @@ -695,7 +673,7 @@ static int save_one_dive(git_repository *repo, struct dir *tree, struct dive *di subdir->unique = true; create_dive_buffer(dive, &buf); - nr = dive->number; + nr = dive.number; ret = blob_insert(repo, subdir, &buf, "Dive%c%d", nr ? '-' : 0, nr); if (ret) @@ -706,12 +684,9 @@ static int save_one_dive(git_repository *repo, struct dir *tree, struct dive *di * computer, use index 0 for that (which disables the index * generation when naming it). */ - dc = &dive->dc; - nr = dc->next ? 1 : 0; - do { + nr = dive.dcs.size() > 1 ? 1 : 0; + for (auto &dc: dive.dcs) save_one_divecomputer(repo, subdir, dive, dc, nr++); - dc = dc->next; - } while (dc); /* Save the picture data, if any */ save_pictures(repo, subdir, dive); @@ -732,11 +707,12 @@ static int save_one_dive(git_repository *repo, struct dir *tree, struct dive *di * similar. */ #define MAXTRIPNAME 15 -static void create_trip_name(dive_trip_t *trip, struct membuffer *name, struct tm *tm) +static void create_trip_name(dive_trip *trip, struct membuffer *name, struct tm *tm) { put_format(name, "%02u-", tm->tm_mday); - if (trip->location) { - char ascii_loc[MAXTRIPNAME+1], *p = trip->location; + if (!trip->location.empty()) { + char ascii_loc[MAXTRIPNAME+1]; + const char *p = trip->location.c_str(); int i; for (i = 0; i < MAXTRIPNAME; ) { @@ -766,19 +742,19 @@ static void create_trip_name(dive_trip_t *trip, struct membuffer *name, struct t put_string(name, "trip"); } -static int save_trip_description(git_repository *repo, struct dir *dir, dive_trip_t *trip, struct tm *tm) +static int save_trip_description(git_repository *repo, struct dir *dir, dive_trip *trip, struct tm *tm) { int ret; git_oid blob_id; - struct membufferpp desc; + membuffer desc; put_format(&desc, "date %04u-%02u-%02u\n", tm->tm_year, tm->tm_mon + 1, tm->tm_mday); put_format(&desc, "time %02u:%02u:%02u\n", tm->tm_hour, tm->tm_min, tm->tm_sec); - show_utf8(&desc, "location ", trip->location, "\n"); - show_utf8(&desc, "notes ", trip->notes, "\n"); + show_utf8(&desc, "location ", trip->location.c_str(), "\n"); + show_utf8(&desc, "notes ", trip->notes.c_str(), "\n"); ret = git_blob_create_frombuffer(&blob_id, repo, desc.buffer, desc.len); if (ret) @@ -805,12 +781,10 @@ static void verify_shared_date(timestamp_t when, struct tm *tm) #define MIN_TIMESTAMP (0) #define MAX_TIMESTAMP (0x7fffffffffffffff) -static int save_one_trip(git_repository *repo, struct dir *tree, dive_trip_t *trip, struct tm *tm, bool cached_ok) +static int save_one_trip(git_repository *repo, struct dir *tree, dive_trip *trip, struct tm *tm, bool cached_ok) { - int i; - struct dive *dive; struct dir *subdir; - struct membufferpp name; + membuffer name; timestamp_t first, last; /* Create trip directory */ @@ -824,7 +798,7 @@ static int save_one_trip(git_repository *repo, struct dir *tree, dive_trip_t *tr /* Make sure we write out the dates to the dives consistently */ first = MAX_TIMESTAMP; last = MIN_TIMESTAMP; - for_each_dive(i, dive) { + for (auto &dive: divelog.dives) { if (dive->divetrip != trip) continue; if (dive->when < first) @@ -836,9 +810,9 @@ static int save_one_trip(git_repository *repo, struct dir *tree, dive_trip_t *tr verify_shared_date(last, tm); /* Save each dive in the directory */ - for_each_dive(i, dive) { + for (auto &dive: divelog.dives) { if (dive->divetrip == trip) - save_one_dive(repo, subdir, dive, tm, cached_ok); + save_one_dive(repo, subdir, *dive, tm, cached_ok); } return 0; @@ -861,44 +835,34 @@ static void save_units(void *_b) prefs.units.vertical_speed_time == units::SECONDS ? "SECONDS" : "MINUTES"); } -static void save_one_device(struct membuffer *b, const struct device *d) +static void save_one_device(struct membuffer *b, const struct device &d) { - const char *model = device_get_model(d); - const char *nickname = device_get_nickname(d); - const char *serial = device_get_serial(d); - - if (empty_string(serial)) serial = NULL; - if (empty_string(nickname)) nickname = NULL; - if (!nickname || !serial) + if (d.nickName.empty() || d.serialNumber.empty()) return; - show_utf8(b, "divecomputerid ", model, ""); - put_format(b, " deviceid=%08x", calculate_string_hash(serial)); - show_utf8(b, " serial=", serial, ""); - show_utf8(b, " nickname=", nickname, ""); + show_utf8(b, "divecomputerid ", d.model.c_str(), ""); + put_format(b, " deviceid=%08x", calculate_string_hash(d.serialNumber.c_str())); + show_utf8(b, " serial=", d.serialNumber.c_str(), ""); + show_utf8(b, " nickname=", d.nickName.c_str(), ""); put_string(b, "\n"); } -static void save_one_fingerprint(struct membuffer *b, int i) +static void save_one_fingerprint(struct membuffer *b, const fingerprint_record &fp) { put_format(b, "fingerprint model=%08x serial=%08x deviceid=%08x diveid=%08x data=\"%s\"\n", - fp_get_model(&fingerprint_table, i), - fp_get_serial(&fingerprint_table, i), - fp_get_deviceid(&fingerprint_table, i), - fp_get_diveid(&fingerprint_table, i), - fp_get_data(&fingerprint_table, i).c_str()); + fp.model, fp.serial, fp.fdeviceid, fp.fdiveid, fp.get_data().c_str()); } static void save_settings(git_repository *repo, struct dir *tree) { - struct membufferpp b; + membuffer b; - put_format(&b, "version %d\n", DATAFORMAT_VERSION); - for (int i = 0; i < nr_devices(divelog.devices); i++) - save_one_device(&b, get_device(divelog.devices, i)); + put_format(&b, "version %d\n", dataformat_version); + for (auto &dev: divelog.devices) + save_one_device(&b, dev); /* save the fingerprint data */ - for (int i = 0; i < nr_fingerprints(&fingerprint_table); i++) - save_one_fingerprint(&b, i); + for (auto &fp: fingerprints) + save_one_fingerprint(&b, fp); cond_put_format(divelog.autogroup, &b, "autogroup\n"); save_units(&b); @@ -919,25 +883,23 @@ static void save_settings(git_repository *repo, struct dir *tree) static void save_divesites(git_repository *repo, struct dir *tree) { struct dir *subdir; - struct membufferpp dirname; + membuffer dirname; put_format(&dirname, "01-Divesites"); subdir = new_directory(repo, tree, &dirname); - purge_empty_dive_sites(divelog.sites); - for (int i = 0; i < divelog.sites->nr; i++) { - struct membufferpp b; - struct dive_site *ds = get_dive_site(i, divelog.sites); - struct membufferpp site_file_name; + divelog.sites.purge_empty(); + for (const auto &ds: divelog.sites) { + membuffer b; + membuffer site_file_name; put_format(&site_file_name, "Site-%08x", ds->uuid); - show_utf8(&b, "name ", ds->name, "\n"); - show_utf8(&b, "description ", ds->description, "\n"); - show_utf8(&b, "notes ", ds->notes, "\n"); + show_utf8(&b, "name ", ds->name.c_str(), "\n"); + show_utf8(&b, "description ", ds->description.c_str(), "\n"); + show_utf8(&b, "notes ", ds->notes.c_str(), "\n"); put_location(&b, &ds->location, "gps ", "\n"); - for (int j = 0; j < ds->taxonomy.nr; j++) { - struct taxonomy *t = &ds->taxonomy.category[j]; - if (t->category != TC_NONE && t->value) { - put_format(&b, "geo cat %d origin %d ", t->category, t->origin); - show_utf8(&b, "", t->value, "\n" ); + for (const auto &t: ds->taxonomy) { + if (t.category != TC_NONE && !t.value.empty()) { + put_format(&b, "geo cat %d origin %d ", t.category, t.origin); + show_utf8(&b, "", t.value.c_str(), "\n" ); } } blob_insert(repo, subdir, &b, mb_cstring(&site_file_name)); @@ -953,21 +915,20 @@ static void save_divesites(git_repository *repo, struct dir *tree) * Whether stringmode or rangemode exist depends on the type of the constraint. * Any constraint can be negated. */ -static void format_one_filter_constraint(int preset_id, int constraint_id, struct membuffer *b) +static void format_one_filter_constraint(const filter_constraint &constraint, struct membuffer *b) { - const struct filter_constraint *constraint = filter_preset_constraint(preset_id, constraint_id); - const char *type = filter_constraint_type_to_string(constraint->type); + const char *type = filter_constraint_type_to_string(constraint.type); show_utf8(b, "constraint type=", type, ""); - if (filter_constraint_has_string_mode(constraint->type)) { - const char *mode = filter_constraint_string_mode_to_string(constraint->string_mode); + if (filter_constraint_has_string_mode(constraint.type)) { + const char *mode = filter_constraint_string_mode_to_string(constraint.string_mode); show_utf8(b, " stringmode=", mode, ""); } - if (filter_constraint_has_range_mode(constraint->type)) { - const char *mode = filter_constraint_range_mode_to_string(constraint->range_mode); + if (filter_constraint_has_range_mode(constraint.type)) { + const char *mode = filter_constraint_range_mode_to_string(constraint.range_mode); show_utf8(b, " rangemode=", mode, ""); } - if (constraint->negate) + if (constraint.negate) put_format(b, " negate"); std::string data = filter_constraint_data_to_string(constraint); show_utf8(b, " data=", data.c_str(), "\n"); @@ -982,35 +943,34 @@ static void format_one_filter_constraint(int preset_id, int constraint_id, struc * fulltext mode "fulltext mode" query "the query as entered by the user" * The format of the "constraint" entry is described in the format_one_filter_constraint() function. */ -static void format_one_filter_preset(int preset_id, struct membuffer *b) +static void format_one_filter_preset(const filter_preset &preset, struct membuffer *b) { - std::string name = filter_preset_name(preset_id); - show_utf8(b, "name ", name.c_str(), "\n"); + show_utf8(b, "name ", preset.name.c_str(), "\n"); - std::string fulltext = filter_preset_fulltext_query(preset_id); + std::string fulltext = preset.fulltext_query(); if (!fulltext.empty()) { - show_utf8(b, "fulltext mode=", filter_preset_fulltext_mode(preset_id), ""); + show_utf8(b, "fulltext mode=", preset.fulltext_mode(), ""); show_utf8(b, " query=", fulltext.c_str(), "\n"); } - for (int i = 0; i < filter_preset_constraint_count(preset_id); i++) - format_one_filter_constraint(preset_id, i, b); + for (auto &constraint: preset.data.constraints) + format_one_filter_constraint(constraint, b); } static void save_filter_presets(git_repository *repo, struct dir *tree) { - struct membufferpp dirname; + membuffer dirname; struct dir *filter_dir; put_format(&dirname, "02-Filterpresets"); filter_dir = new_directory(repo, tree, &dirname); - for (int i = 0; i < filter_presets_count(); i++) + for (auto [i, filter_preset]: enumerated_range(divelog.filter_presets)) { - membufferpp preset_name; - membufferpp preset_buffer; + membuffer preset_name; + membuffer preset_buffer; put_format(&preset_name, "Preset-%03d", i); - format_one_filter_preset(i, &preset_buffer); + format_one_filter_preset(filter_preset, &preset_buffer); blob_insert(repo, filter_dir, &preset_buffer, mb_cstring(&preset_name)); } @@ -1018,26 +978,22 @@ static void save_filter_presets(git_repository *repo, struct dir *tree) static int create_git_tree(git_repository *repo, struct dir *root, bool select_only, bool cached_ok) { - int i; - struct dive *dive; - dive_trip_t *trip; - git_storage_update_progress(translate("gettextFromC", "Start saving data")); save_settings(repo, root); save_divesites(repo, root); save_filter_presets(repo, root); - for (i = 0; i < divelog.trips->nr; ++i) - divelog.trips->trips[i]->saved = 0; + for (auto &trip: divelog.trips) + trip->saved = false; /* save the dives */ git_storage_update_progress(translate("gettextFromC", "Start saving dives")); - for_each_dive(i, dive) { + for (auto &dive: divelog.dives) { struct tm tm; struct dir *tree; - trip = dive->divetrip; + dive_trip *trip = dive->divetrip; if (select_only) { if (!dive->selected) @@ -1047,7 +1003,7 @@ static int create_git_tree(git_repository *repo, struct dir *root, bool select_o } /* Create the date-based hierarchy */ - utc_mkdate(trip ? trip_date(trip) : dive->when, &tm); + utc_mkdate(trip ? trip->date() : dive->when, &tm); tree = mktree(repo, root, "%04d", tm.tm_year); tree = mktree(repo, tree, "%02d", tm.tm_mon + 1); @@ -1062,7 +1018,7 @@ static int create_git_tree(git_repository *repo, struct dir *root, bool select_o continue; } - save_one_dive(repo, tree, dive, &tm, cached_ok); + save_one_dive(repo, tree, *dive, &tm, cached_ok); } git_storage_update_progress(translate("gettextFromC", "Done creating local cache")); return 0; @@ -1106,7 +1062,7 @@ static git_tree *get_git_tree(git_repository *repo, git_object *parent) return tree; } -extern "C" int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree) +int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; @@ -1117,7 +1073,7 @@ extern "C" int update_git_checkout(git_repository *repo, git_object *parent, git return git_checkout_tree(repo, (git_object *) tree, &opts); } -extern "C" int get_authorship(git_repository *repo, git_signature **authorp) +int get_authorship(git_repository *repo, git_signature **authorp) { if (git_signature_default(authorp, repo) == 0) return 0; @@ -1133,33 +1089,34 @@ extern "C" int get_authorship(git_repository *repo, git_signature **authorp) static void create_commit_message(struct membuffer *msg, bool create_empty) { - int nr = divelog.dives->nr; - struct dive *dive = get_dive(nr-1); + int nr = static_cast(divelog.dives.size()); std::string changes_made = get_changes_made(); if (create_empty) { put_string(msg, "Initial commit to create empty repo.\n\n"); } else if (!changes_made.empty()) { put_format(msg, "Changes made: \n\n%s\n", changes_made.c_str()); - } else if (dive) { - dive_trip_t *trip = dive->divetrip; - const char *location = get_dive_location(dive) ? : "no location"; - struct divecomputer *dc = &dive->dc; + } else if (!divelog.dives.empty()) { + const struct dive &dive = *divelog.dives.back(); + dive_trip *trip = dive.divetrip; + std::string location = dive.get_location(); + if (location.empty()) + location = "no location"; const char *sep = "\n"; - if (dive->number) - nr = dive->number; + if (dive.number) + nr = dive.number; - put_format(msg, "dive %d: %s", nr, location); - if (trip && !empty_string(trip->location) && strcmp(trip->location, location)) - put_format(msg, " (%s)", trip->location); + put_format(msg, "dive %d: %s", nr, location.c_str()); + if (trip && !trip->location.empty() && location != trip->location) + put_format(msg, " (%s)", trip->location.c_str()); put_format(msg, "\n"); - do { - if (!empty_string(dc->model)) { - put_format(msg, "%s%s", sep, dc->model); + for (auto &dc: dive.dcs) { + if (!dc.model.empty()) { + put_format(msg, "%s%s", sep, dc.model.c_str()); sep = ", "; } - } while ((dc = dc->next) != NULL); + } put_format(msg, "\n"); } put_format(msg, "Created by %s\n", subsurface_user_agent().c_str()); @@ -1221,7 +1178,7 @@ static int create_new_commit(struct git_info *info, git_oid *tree_id, bool creat /* Else we do want to create the new branch, but with the old commit */ commit = (git_commit *) parent; } else { - struct membufferpp commit_msg; + membuffer commit_msg; create_commit_message(&commit_msg, create_empty); if (git_commit_create_v(&commit_id, info->repo, NULL, author, author, NULL, mb_cstring(&commit_msg), tree, parent != NULL, parent)) { diff --git a/core/save-html.cpp b/core/save-html.cpp index cb1c785e6..436fdd571 100644 --- a/core/save-html.cpp +++ b/core/save-html.cpp @@ -21,6 +21,7 @@ #include #include +#include static void write_attribute(struct membuffer *b, const char *att_name, const char *value, const char *separator) { @@ -31,13 +32,24 @@ static void write_attribute(struct membuffer *b, const char *att_name, const cha put_format(b, "\"%s", separator); } -static void save_photos(struct membuffer *b, const char *photos_dir, struct dive *dive) +static void copy_image_and_overwrite(const std::string &cfileName, const std::string &path, const std::string &cnewName) { - if (dive->pictures.nr <= 0) + QString fileName = QString::fromStdString(cfileName); + std::string newName = path + cnewName; + QFile file(QString::fromStdString(newName)); + if (file.exists()) + file.remove(); + if (!QFile::copy(fileName, QString::fromStdString(newName))) + report_info("copy of %s to %s failed", cfileName.c_str(), newName.c_str()); +} + +static void save_photos(struct membuffer *b, const char *photos_dir, const struct dive &dive) +{ + if (dive.pictures.empty()) return; const char *separator = "\"photos\":["; - FOR_EACH_PICTURE(dive) { + for (auto &picture: dive.pictures) { put_string(b, separator); separator = ", "; std::string fname = get_file_name(local_file_path(picture)); @@ -49,22 +61,21 @@ static void save_photos(struct membuffer *b, const char *photos_dir, struct dive put_string(b, "],"); } -static void write_divecomputers(struct membuffer *b, struct dive *dive) +static void write_divecomputers(struct membuffer *b, const struct dive &dive) { put_string(b, "\"divecomputers\":["); - struct divecomputer *dc; const char *separator = ""; - for_each_dc (dive, dc) { + for (auto &dc: dive.dcs) { put_string(b, separator); separator = ", "; put_format(b, "{"); - write_attribute(b, "model", dc->model, ", "); - if (dc->deviceid) - put_format(b, "\"deviceid\":\"%08x\", ", dc->deviceid); + write_attribute(b, "model", dc.model.c_str(), ", "); + if (dc.deviceid) + put_format(b, "\"deviceid\":\"%08x\", ", dc.deviceid); else put_string(b, "\"deviceid\":\"--\", "); - if (dc->diveid) - put_format(b, "\"diveid\":\"%08x\" ", dc->diveid); + if (dc.diveid) + put_format(b, "\"diveid\":\"%08x\" ", dc.diveid); else put_string(b, "\"diveid\":\"--\" "); put_format(b, "}"); @@ -72,99 +83,84 @@ static void write_divecomputers(struct membuffer *b, struct dive *dive) put_string(b, "],"); } -static void write_dive_status(struct membuffer *b, struct dive *dive) +static void write_dive_status(struct membuffer *b, const struct dive &dive) { - put_format(b, "\"sac\":\"%d\",", dive->sac); - put_format(b, "\"otu\":\"%d\",", dive->otu); - put_format(b, "\"cns\":\"%d\",", dive->cns); + put_format(b, "\"sac\":\"%d\",", dive.sac); + put_format(b, "\"otu\":\"%d\",", dive.otu); + put_format(b, "\"cns\":\"%d\",", dive.cns); } -static void put_HTML_bookmarks(struct membuffer *b, struct dive *dive) +static void put_HTML_bookmarks(struct membuffer *b, const struct dive &dive) { - struct event *ev = dive->dc.events; - - if (!ev) - return; - const char *separator = "\"events\":["; - do { + for (const auto &ev: dive.dcs[0].events) { put_string(b, separator); separator = ", "; put_string(b, "{\"name\":\""); - put_quoted(b, ev->name, 1, 0); + put_quoted(b, ev.name.c_str(), 1, 0); put_string(b, "\","); - put_format(b, "\"value\":\"%d\",", ev->value); - put_format(b, "\"type\":\"%d\",", ev->type); - put_format(b, "\"time\":\"%d\"}", ev->time.seconds); - ev = ev->next; - } while (ev); + put_format(b, "\"value\":\"%d\",", ev.value); + put_format(b, "\"type\":\"%d\",", ev.type); + put_format(b, "\"time\":\"%d\"}", ev.time.seconds); + } put_string(b, "],"); } -static void put_weightsystem_HTML(struct membuffer *b, struct dive *dive) +static void put_weightsystem_HTML(struct membuffer *b, const struct dive &dive) { - int i, nr; - - nr = nr_weightsystems(dive); - put_string(b, "\"Weights\":["); const char *separator = ""; - for (i = 0; i < nr; i++) { - weightsystem_t ws = dive->weightsystems.weightsystems[i]; + for (auto &ws: dive.weightsystems) { int grams = ws.weight.grams; - const char *description = ws.description; put_string(b, separator); separator = ", "; put_string(b, "{"); put_HTML_weight_units(b, grams, "\"weight\":\"", "\","); - write_attribute(b, "description", description, " "); + write_attribute(b, "description", ws.description.c_str(), " "); put_string(b, "}"); } put_string(b, "],"); } -static void put_cylinder_HTML(struct membuffer *b, struct dive *dive) +static void put_cylinder_HTML(struct membuffer *b, const struct dive &dive) { - int i, nr; const char *separator = "\"Cylinders\":["; - nr = nr_cylinders(dive); - if (!nr) + if (dive.cylinders.empty()) put_string(b, separator); - for (i = 0; i < nr; i++) { - cylinder_t *cylinder = get_cylinder(dive, i); + for (auto &cyl: dive.cylinders) { put_format(b, "%s{", separator); separator = ", "; - write_attribute(b, "Type", cylinder->type.description, ", "); - if (cylinder->type.size.mliter) { - int volume = cylinder->type.size.mliter; - if (prefs.units.volume == units::CUFT && cylinder->type.workingpressure.mbar) - volume = lrint(volume * bar_to_atm(cylinder->type.workingpressure.mbar / 1000.0)); + write_attribute(b, "Type", cyl.type.description.c_str(), ", "); + if (cyl.type.size.mliter) { + int volume = cyl.type.size.mliter; + if (prefs.units.volume == units::CUFT && cyl.type.workingpressure.mbar) + volume = lrint(volume * bar_to_atm(cyl.type.workingpressure.mbar / 1000.0)); put_HTML_volume_units(b, volume, "\"Size\":\"", " \", "); } else { write_attribute(b, "Size", "--", ", "); } - put_HTML_pressure_units(b, cylinder->type.workingpressure, "\"WPressure\":\"", " \", "); + put_HTML_pressure_units(b, cyl.type.workingpressure, "\"WPressure\":\"", " \", "); - if (cylinder->start.mbar) { - put_HTML_pressure_units(b, cylinder->start, "\"SPressure\":\"", " \", "); + if (cyl.start.mbar) { + put_HTML_pressure_units(b, cyl.start, "\"SPressure\":\"", " \", "); } else { write_attribute(b, "SPressure", "--", ", "); } - if (cylinder->end.mbar) { - put_HTML_pressure_units(b, cylinder->end, "\"EPressure\":\"", " \", "); + if (cyl.end.mbar) { + put_HTML_pressure_units(b, cyl.end, "\"EPressure\":\"", " \", "); } else { write_attribute(b, "EPressure", "--", ", "); } - if (cylinder->gasmix.o2.permille) { - put_format(b, "\"O2\":\"%u.%u%%\",", FRACTION_TUPLE(cylinder->gasmix.o2.permille, 10)); - put_format(b, "\"He\":\"%u.%u%%\"", FRACTION_TUPLE(cylinder->gasmix.he.permille, 10)); + if (cyl.gasmix.o2.permille) { + put_format(b, "\"O2\":\"%u.%u%%\",", FRACTION_TUPLE(cyl.gasmix.o2.permille, 10)); + put_format(b, "\"He\":\"%u.%u%%\"", FRACTION_TUPLE(cyl.gasmix.he.permille, 10)); } else { write_attribute(b, "O2", "Air", ""); } @@ -176,28 +172,25 @@ static void put_cylinder_HTML(struct membuffer *b, struct dive *dive) } -static void put_HTML_samples(struct membuffer *b, struct dive *dive) +static void put_HTML_samples(struct membuffer *b, const struct dive &dive) { - int i; - put_format(b, "\"maxdepth\":%d,", dive->dc.maxdepth.mm); - put_format(b, "\"duration\":%d,", dive->dc.duration.seconds); - struct sample *s = dive->dc.sample; + put_format(b, "\"maxdepth\":%d,", dive.dcs[0].maxdepth.mm); + put_format(b, "\"duration\":%d,", dive.dcs[0].duration.seconds); - if (!dive->dc.samples) + if (dive.dcs[0].samples.empty()) return; const char *separator = "\"samples\":["; - for (i = 0; i < dive->dc.samples; i++) { - put_format(b, "%s[%d,%d,%d,%d]", separator, s->time.seconds, s->depth.mm, s->pressure[0].mbar, s->temperature.mkelvin); + for (auto &s: dive.dcs[0].samples) { + put_format(b, "%s[%d,%d,%d,%d]", separator, s.time.seconds, s.depth.mm, s.pressure[0].mbar, s.temperature.mkelvin); separator = ", "; - s++; } put_string(b, "],"); } -static void put_HTML_coordinates(struct membuffer *b, struct dive *dive) +static void put_HTML_coordinates(struct membuffer *b, const struct dive &dive) { - struct dive_site *ds = get_dive_site_for_dive(dive); + struct dive_site *ds = dive.dive_site; if (!ds) return; degrees_t latitude = ds->location.lat; @@ -213,10 +206,10 @@ static void put_HTML_coordinates(struct membuffer *b, struct dive *dive) put_string(b, "},"); } -void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +void put_HTML_date(struct membuffer *b, const struct dive &dive, const char *pre, const char *post) { struct tm tm; - utc_mkdate(dive->when, &tm); + utc_mkdate(dive.when, &tm); put_format(b, "%s%04u-%02u-%02u%s", pre, tm.tm_year, tm.tm_mon + 1, tm.tm_mday, post); } @@ -226,14 +219,13 @@ void put_HTML_quoted(struct membuffer *b, const char *text) put_quoted(b, text, is_attribute, is_html); } -void put_HTML_notes(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +void put_HTML_notes(struct membuffer *b, const struct dive &dive, const char *pre, const char *post) { put_string(b, pre); - if (dive->notes) { - put_HTML_quoted(b, dive->notes); - } else { + if (!dive.notes.empty()) + put_HTML_quoted(b, dive.notes.c_str()); + else put_string(b, "--"); - } put_string(b, post); } @@ -271,24 +263,24 @@ void put_HTML_weight_units(struct membuffer *b, unsigned int grams, const char * put_format(b, "%s%.1f %s%s", pre, value, unit, post); } -void put_HTML_time(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +void put_HTML_time(struct membuffer *b, const struct dive &dive, const char *pre, const char *post) { struct tm tm; - utc_mkdate(dive->when, &tm); + utc_mkdate(dive.when, &tm); put_format(b, "%s%02u:%02u:%02u%s", pre, tm.tm_hour, tm.tm_min, tm.tm_sec, post); } -void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +void put_HTML_depth(struct membuffer *b, const struct dive &dive, const char *pre, const char *post) { const char *unit; double value; const struct units *units_p = get_units(); - if (!dive->maxdepth.mm) { + if (!dive.maxdepth.mm) { put_format(b, "%s--%s", pre, post); return; } - value = get_depth_units(dive->maxdepth.mm, NULL, &unit); + value = get_depth_units(dive.maxdepth.mm, NULL, &unit); switch (units_p->length) { case units::METERS: @@ -301,77 +293,75 @@ void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, con } } -void put_HTML_airtemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +void put_HTML_airtemp(struct membuffer *b, const struct dive &dive, const char *pre, const char *post) { const char *unit; double value; - if (!dive->airtemp.mkelvin) { + if (!dive.airtemp.mkelvin) { put_format(b, "%s--%s", pre, post); return; } - value = get_temp_units(dive->airtemp.mkelvin, &unit); + value = get_temp_units(dive.airtemp.mkelvin, &unit); put_format(b, "%s%.1f %s%s", pre, value, unit, post); } -void put_HTML_watertemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +void put_HTML_watertemp(struct membuffer *b, const struct dive &dive, const char *pre, const char *post) { const char *unit; double value; - if (!dive->watertemp.mkelvin) { + if (!dive.watertemp.mkelvin) { put_format(b, "%s--%s", pre, post); return; } - value = get_temp_units(dive->watertemp.mkelvin, &unit); + value = get_temp_units(dive.watertemp.mkelvin, &unit); put_format(b, "%s%.1f %s%s", pre, value, unit, post); } -static void put_HTML_tags(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +static void put_HTML_tags(struct membuffer *b, const struct dive &dive, const char *pre, const char *post) { put_string(b, pre); - struct tag_entry *tag = dive->tag_list; - if (!tag) + if (dive.tags.empty()) put_string(b, "[\"--\""); const char *separator = "["; - while (tag) { + for (const divetag *tag: dive.tags) { put_format(b, "%s\"", separator); separator = ", "; - put_HTML_quoted(b, tag->tag->name.c_str()); + put_HTML_quoted(b, tag->name.c_str()); put_string(b, "\""); - tag = tag->next; } put_string(b, "]"); put_string(b, post); } /* if exporting list_only mode, we neglect exporting the samples, bookmarks and cylinders */ -static void write_one_dive(struct membuffer *b, struct dive *dive, const char *photos_dir, int *dive_no, bool list_only) +static void write_one_dive(struct membuffer *b, const struct dive &dive, const char *photos_dir, int *dive_no, bool list_only) { put_string(b, "{"); put_format(b, "\"number\":%d,", *dive_no); - put_format(b, "\"subsurface_number\":%d,", dive->number); + put_format(b, "\"subsurface_number\":%d,", dive.number); put_HTML_date(b, dive, "\"date\":\"", "\","); put_HTML_time(b, dive, "\"time\":\"", "\","); - write_attribute(b, "location", get_dive_location(dive), ", "); + write_attribute(b, "location", dive.get_location().c_str(), ", "); put_HTML_coordinates(b, dive); - put_format(b, "\"rating\":%d,", dive->rating); - put_format(b, "\"visibility\":%d,", dive->visibility); - put_format(b, "\"current\":%d,", dive->current); - put_format(b, "\"wavesize\":%d,", dive->wavesize); - put_format(b, "\"surge\":%d,", dive->surge); - put_format(b, "\"chill\":%d,", dive->chill); + put_format(b, "\"rating\":%d,", dive.rating); + put_format(b, "\"visibility\":%d,", dive.visibility); + put_format(b, "\"current\":%d,", dive.current); + put_format(b, "\"wavesize\":%d,", dive.wavesize); + put_format(b, "\"surge\":%d,", dive.surge); + put_format(b, "\"chill\":%d,", dive.chill); put_format(b, "\"dive_duration\":\"%u:%02u min\",", - FRACTION_TUPLE(dive->duration.seconds, 60)); + FRACTION_TUPLE(dive.duration.seconds, 60)); put_string(b, "\"temperature\":{"); put_HTML_airtemp(b, dive, "\"air\":\"", "\","); put_HTML_watertemp(b, dive, "\"water\":\"", "\""); put_string(b, " },"); - write_attribute(b, "buddy", dive->buddy, ", "); - write_attribute(b, "diveguide", dive->diveguide, ", "); - write_attribute(b, "suit", dive->suit, ", "); + write_attribute(b, "buddy", dive.buddy.c_str(), ", "); + write_attribute(b, "diveguide", dive.diveguide.c_str(), ", "); + write_attribute(b, "suit", dive.suit.c_str(), ", "); put_HTML_tags(b, dive, "\"tags\":", ","); if (!list_only) { put_cylinder_HTML(b, dive); @@ -390,12 +380,10 @@ static void write_one_dive(struct membuffer *b, struct dive *dive, const char *p static void write_no_trip(struct membuffer *b, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) { - int i; - struct dive *dive; const char *separator = ""; bool found_sel_dive = 0; - for_each_dive (i, dive) { + for (auto &dive: divelog.dives) { // write dive if it doesn't belong to any trip and the dive is selected // or we are in exporting all dives mode. if (!dive->divetrip && (dive->selected || !selected_only)) { @@ -408,21 +396,19 @@ static void write_no_trip(struct membuffer *b, int *dive_no, bool selected_only, } put_string(b, separator); separator = ", "; - write_one_dive(b, dive, photos_dir, dive_no, list_only); + write_one_dive(b, *dive, photos_dir, dive_no, list_only); } } if (found_sel_dive) put_format(b, "]}\n\n"); } -static void write_trip(struct membuffer *b, dive_trip_t *trip, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) +static void write_trip(struct membuffer *b, dive_trip *trip, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) { - struct dive *dive; const char *separator = ""; bool found_sel_dive = 0; - for (int i = 0; i < trip->dives.nr; i++) { - dive = trip->dives.dives[i]; + for (auto dive: trip->dives) { if (!dive->selected && selected_only) continue; @@ -431,12 +417,12 @@ static void write_trip(struct membuffer *b, dive_trip_t *trip, int *dive_no, boo found_sel_dive = 1; put_format(b, "%c {", *sep); (*sep) = ','; - write_attribute(b, "name", trip->location, ", "); + write_attribute(b, "name", trip->location.c_str(), ", "); put_format(b, "\"dives\":["); } put_string(b, separator); separator = ", "; - write_one_dive(b, dive, photos_dir, dive_no, list_only); + write_one_dive(b, *dive, photos_dir, dive_no, list_only); } // close the trip object if contain dives. @@ -446,17 +432,15 @@ static void write_trip(struct membuffer *b, dive_trip_t *trip, int *dive_no, boo static void write_trips(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only) { - int i, dive_no = 0; - struct dive *dive; - dive_trip_t *trip; + int dive_no = 0; char sep_ = ' '; char *sep = &sep_; - for (i = 0; i < divelog.trips->nr; ++i) - divelog.trips->trips[i]->saved = 0; + for (auto &trip: divelog.trips) + trip->saved = 0; - for_each_dive (i, dive) { - trip = dive->divetrip; + for (auto &dive: divelog.dives) { + dive_trip *trip = dive->divetrip; /*Continue if the dive have no trips or we have seen this trip before*/ if (!trip || trip->saved) @@ -482,7 +466,7 @@ void export_HTML(const char *file_name, const char *photos_dir, const bool selec { FILE *f; - struct membufferpp buf; + membuffer buf; export_list(&buf, photos_dir, selected_only, list_only); f = subsurface_fopen(file_name, "w+"); @@ -498,7 +482,7 @@ void export_translation(const char *file_name) { FILE *f; - struct membufferpp buf; + membuffer buf; //export translated words here put_format(&buf, "translate={"); diff --git a/core/save-html.h b/core/save-html.h index df3bb97af..03eb66c73 100644 --- a/core/save-html.h +++ b/core/save-html.h @@ -4,18 +4,14 @@ #include "membuffer.h" -#ifdef __cplusplus -extern "C" { -#endif - struct dive; -void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_airtemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_watertemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_time(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_notes(struct membuffer *b, struct dive *dive, const char *pre, const char *post); +void put_HTML_date(struct membuffer *b, const struct dive &dive, const char *pre, const char *post); +void put_HTML_depth(struct membuffer *b, const struct dive &dive, const char *pre, const char *post); +void put_HTML_airtemp(struct membuffer *b, const struct dive &dive, const char *pre, const char *post); +void put_HTML_watertemp(struct membuffer *b, const struct dive &dive, const char *pre, const char *post); +void put_HTML_time(struct membuffer *b, const struct dive &dive, const char *pre, const char *post); +void put_HTML_notes(struct membuffer *b, const struct dive &dive, const char *pre, const char *post); void put_HTML_quoted(struct membuffer *b, const char *text); void put_HTML_pressure_units(struct membuffer *b, pressure_t pressure, const char *pre, const char *post); void put_HTML_weight_units(struct membuffer *b, unsigned int grams, const char *pre, const char *post); @@ -23,11 +19,6 @@ void put_HTML_volume_units(struct membuffer *b, unsigned int ml, const char *pre void export_HTML(const char *file_name, const char *photos_dir, const bool selected_only, const bool list_only); void export_list(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only); - void export_translation(const char *file_name); -#ifdef __cplusplus -} -#endif - #endif diff --git a/core/save-profiledata.c b/core/save-profiledata.cpp similarity index 50% rename from core/save-profiledata.c rename to core/save-profiledata.cpp index fa83e5bb1..4befa7d0b 100644 --- a/core/save-profiledata.c +++ b/core/save-profiledata.cpp @@ -1,9 +1,13 @@ +#include "core/save-profiledata.h" +#include "core/dive.h" +#include "core/divelist.h" +#include "core/divelog.h" #include "core/profile.h" #include "core/errorhelper.h" #include "core/file.h" +#include "core/format.h" #include "core/membuffer.h" #include "core/subsurface-string.h" -#include "core/save-profiledata.h" #include "core/version.h" #include @@ -32,54 +36,54 @@ static void put_double(struct membuffer *b, double val) put_format(b, "\"%f\", ", val); } -static void put_video_time(struct membuffer *b, int secs) +static std::string video_time(int secs) { int hours = secs / 3600; secs -= hours * 3600; int mins = secs / 60; secs -= mins * 60; - put_format(b, "%d:%02d:%02d.000,", hours, mins, secs); + return format_string_std("%d:%02d:%02d.000,", hours, mins, secs); } -static void put_pd(struct membuffer *b, const struct plot_info *pi, int idx) +static void put_pd(struct membuffer *b, const struct plot_info &pi, int idx) { - const struct plot_data *entry = pi->entry + idx; + const struct plot_data &entry = pi.entry[idx]; - put_int(b, entry->in_deco); - put_int(b, entry->sec); - for (int c = 0; c < pi->nr_cylinders; c++) { + put_int(b, entry.in_deco); + put_int(b, entry.sec); + for (int c = 0; c < pi.nr_cylinders; c++) { put_int(b, get_plot_sensor_pressure(pi, idx, c)); put_int(b, get_plot_interpolated_pressure(pi, idx, c)); } - put_int(b, entry->temperature); - put_int(b, entry->depth); - put_int(b, entry->ceiling); + put_int(b, entry.temperature); + put_int(b, entry.depth); + put_int(b, entry.ceiling); for (int i = 0; i < 16; i++) - put_int(b, entry->ceilings[i]); + put_int(b, entry.ceilings[i]); for (int i = 0; i < 16; i++) - put_int(b, entry->percentages[i]); - put_int(b, entry->ndl); - put_int(b, entry->tts); - put_int(b, entry->rbt); - put_int(b, entry->stoptime); - put_int(b, entry->stopdepth); - put_int(b, entry->cns); - put_int(b, entry->smoothed); - put_int(b, entry->sac); - put_int(b, entry->running_sum); - put_double(b, entry->pressures.o2); - put_double(b, entry->pressures.n2); - put_double(b, entry->pressures.he); - put_int(b, entry->o2pressure.mbar); + put_int(b, entry.percentages[i]); + put_int(b, entry.ndl); + put_int(b, entry.tts); + put_int(b, entry.rbt); + put_int(b, entry.stoptime); + put_int(b, entry.stopdepth); + put_int(b, entry.cns); + put_int(b, entry.smoothed); + put_int(b, entry.sac); + put_int(b, entry.running_sum); + put_double(b, entry.pressures.o2); + put_double(b, entry.pressures.n2); + put_double(b, entry.pressures.he); + put_int(b, entry.o2pressure.mbar); for (int i = 0; i < MAX_O2_SENSORS; i++) - put_int(b, entry->o2sensor[i].mbar); - put_int(b, entry->o2setpoint.mbar); - put_int(b, entry->scr_OC_pO2.mbar); - put_int(b, entry->mod); - put_int(b, entry->ead); - put_int(b, entry->end); - put_int(b, entry->eadd); - switch (entry->velocity) { + put_int(b, entry.o2sensor[i].mbar); + put_int(b, entry.o2setpoint.mbar); + put_int(b, entry.scr_OC_pO2.mbar); + put_int(b, entry.mod); + put_int(b, entry.ead); + put_int(b, entry.end); + put_int(b, entry.eadd); + switch (entry.velocity) { case STABLE: put_csv_string(b, "STABLE"); break; @@ -96,20 +100,20 @@ static void put_pd(struct membuffer *b, const struct plot_info *pi, int idx) put_csv_string(b, "CRAZY"); break; } - put_int(b, entry->speed); - put_int(b, entry->in_deco_calc); - put_int(b, entry->ndl_calc); - put_int(b, entry->tts_calc); - put_int(b, entry->stoptime_calc); - put_int(b, entry->stopdepth_calc); - put_int(b, entry->pressure_time); - put_int(b, entry->heartbeat); - put_int(b, entry->bearing); - put_double(b, entry->ambpressure); - put_double(b, entry->gfline); - put_double(b, entry->surface_gf); - put_double(b, entry->density); - put_int_with_nl(b, entry->icd_warning ? 1 : 0); + put_int(b, entry.speed); + put_int(b, entry.in_deco_calc); + put_int(b, entry.ndl_calc); + put_int(b, entry.tts_calc); + put_int(b, entry.stoptime_calc); + put_int(b, entry.stopdepth_calc); + put_int(b, entry.pressure_time); + put_int(b, entry.heartbeat); + put_int(b, entry.bearing); + put_double(b, entry.ambpressure); + put_double(b, entry.gfline); + put_double(b, entry.surface_gf); + put_double(b, entry.density); + put_int_with_nl(b, entry.icd_warning ? 1 : 0); } static void put_headers(struct membuffer *b, int nr_cylinders) @@ -166,87 +170,80 @@ static void put_headers(struct membuffer *b, int nr_cylinders) put_csv_string_with_nl(b, "icd_warning"); } -static void put_st_event(struct membuffer *b, struct plot_data *entry, int offset, int length) +static std::string format_st_event(const plot_data &entry, const plot_data &next_entry, int offset, int length) { double value; int decimals; const char *unit; - if (entry->sec < offset || entry->sec > offset + length) - return; + if (entry.sec < offset || entry.sec > offset + length) + return {}; - put_format(b, "Dialogue: 0,"); - put_video_time(b, entry->sec - offset); - put_video_time(b, (entry+1)->sec - offset < length ? (entry+1)->sec - offset : length); - put_format(b, "Default,,0,0,0,,"); - put_format(b, "%d:%02d ", FRACTION_TUPLE(entry->sec, 60)); - value = get_depth_units(entry->depth, &decimals, &unit); - put_format(b, "D=%02.2f %s ", value, unit); - if (entry->temperature) { - value = get_temp_units(entry->temperature, &unit); - put_format(b, "T=%.1f%s ", value, unit); + std::string res = "Dialogue: 0,"; + res += video_time(entry.sec - offset); + res += video_time(next_entry.sec - offset < length ? next_entry.sec - offset : length); + res += "Default,,0,0,0,,"; + res += format_string_std("%d:%02d ", FRACTION_TUPLE(entry.sec, 60)); + value = get_depth_units(entry.depth, &decimals, &unit); + res += format_string_std("D=%02.2f %s ", value, unit); + if (entry.temperature) { + value = get_temp_units(entry.temperature, &unit); + res += format_string_std("T=%.1f%s ", value, unit); } // Only show NDL if it is not essentially infinite, show TTS for mandatory stops. - if (entry->ndl_calc < 3600) { - if (entry->ndl_calc > 0) - put_format(b, "NDL=%d:%02d ", FRACTION_TUPLE(entry->ndl_calc, 60)); + if (entry.ndl_calc < 3600) { + if (entry.ndl_calc > 0) + res += format_string_std("NDL=%d:%02d ", FRACTION_TUPLE(entry.ndl_calc, 60)); else - if (entry->tts_calc > 0) - put_format(b, "TTS=%d:%02d ", FRACTION_TUPLE(entry->tts_calc, 60)); + if (entry.tts_calc > 0) + res += format_string_std("TTS=%d:%02d ", FRACTION_TUPLE(entry.tts_calc, 60)); } - if (entry->surface_gf > 0.0) { - put_format(b, "sGF=%.1f%% ", entry->surface_gf); + if (entry.surface_gf > 0.0) { + res += format_string_std("sGF=%.1f%% ", entry.surface_gf); } - put_format(b, "\n"); + res += "\n"; + return res; } static void save_profiles_buffer(struct membuffer *b, bool select_only) { - int i; - struct dive *dive; - struct plot_info pi; struct deco_state *planner_deco_state = NULL; - init_plot_info(&pi); - for_each_dive(i, dive) { + for(auto &dive: divelog.dives) { if (select_only && !dive->selected) continue; - create_plot_info_new(dive, &dive->dc, &pi, planner_deco_state); + plot_info pi = create_plot_info_new(dive.get(), &dive->dcs[0], planner_deco_state); put_headers(b, pi.nr_cylinders); for (int i = 0; i < pi.nr; i++) - put_pd(b, &pi, i); + put_pd(b, pi, i); put_format(b, "\n"); - free_plot_info_data(&pi); } } -void save_subtitles_buffer(struct membuffer *b, struct dive *dive, int offset, int length) +std::string save_subtitles_buffer(struct dive *dive, int offset, int length) { - struct plot_info pi; struct deco_state *planner_deco_state = NULL; - init_plot_info(&pi); - create_plot_info_new(dive, &dive->dc, &pi, planner_deco_state); + plot_info pi = create_plot_info_new(dive, &dive->dcs[0], planner_deco_state); - put_format(b, "[Script Info]\n"); - put_format(b, "; Script generated by Subsurface %s\n", subsurface_canonical_version()); - put_format(b, "ScriptType: v4.00+\nPlayResX: 384\nPlayResY: 288\n\n"); - put_format(b, "[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n"); - put_format(b, "Style: Default,Arial,12,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,7,10,10,10,0\n\n"); - put_format(b, "[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"); + std::string res; + res += "[Script Info]\n"; + res += "; Script generated by Subsurface " + std::string(subsurface_canonical_version()) + "%s\n"; + res += "ScriptType: v4.00+\nPlayResX: 384\nPlayResY: 288\n\n"; + res += "[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n"; + res += "Style: Default,Arial,12,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,7,10,10,10,0\n\n"; + res += "[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"; - for (int i = 0; i < pi.nr; i++) { - put_st_event(b, &pi.entry[i], offset, length); - } - put_format(b, "\n"); - - free_plot_info_data(&pi); + for (int i = 0; i + 1 < pi.nr; i++) + res += format_st_event(pi.entry[i], pi.entry[i + 1], offset, length); + res += "\n"; + return res; } int save_profiledata(const char *filename, bool select_only) { - struct membuffer buf = { 0 }; + membuffer buf; FILE *f; int error = 0; @@ -265,6 +262,5 @@ int save_profiledata(const char *filename, bool select_only) if (error) report_error("Save failed (%s)", strerror(errno)); - free_buffer(&buf); return error; } diff --git a/core/save-profiledata.h b/core/save-profiledata.h index 3720dc46c..0533020d5 100644 --- a/core/save-profiledata.h +++ b/core/save-profiledata.h @@ -2,14 +2,9 @@ #ifndef SAVE_PROFILE_DATA_H #define SAVE_PROFILE_DATA_H -#ifdef __cplusplus -extern "C" { -#endif +#include int save_profiledata(const char *filename, bool selected_only); -void save_subtitles_buffer(struct membuffer *b, struct dive *dive, int offset, int length); +std::string save_subtitles_buffer(struct dive *dive, int offset, int length); -#ifdef __cplusplus -} -#endif #endif // SAVE_PROFILE_DATA_H diff --git a/core/save-xml.cpp b/core/save-xml.cpp index 21ed7e089..6f6924216 100644 --- a/core/save-xml.cpp +++ b/core/save-xml.cpp @@ -12,19 +12,19 @@ #include #include +#include "device.h" #include "dive.h" #include "divelog.h" #include "divesite.h" #include "errorhelper.h" #include "extradata.h" +#include "event.h" #include "filterconstraint.h" #include "filterpreset.h" #include "sample.h" #include "subsurface-string.h" #include "subsurface-time.h" #include "trip.h" -#include "device.h" -#include "event.h" #include "file.h" #include "membuffer.h" #include "picture.h" @@ -32,6 +32,7 @@ #include "qthelper.h" #include "gettext.h" #include "tag.h" +#include "version.h" #include "xmlparams.h" /* @@ -100,67 +101,67 @@ static void show_utf8_blanked(struct membuffer *b, const char *text, const char show_utf8(b, copy.c_str(), pre, post, is_attribute); } -static void save_depths(struct membuffer *b, struct divecomputer *dc) +static void save_depths(struct membuffer *b, const struct divecomputer &dc) { /* What's the point of this dive entry again? */ - if (!dc->maxdepth.mm && !dc->meandepth.mm) + if (!dc.maxdepth.mm && !dc.meandepth.mm) return; put_string(b, " maxdepth, " max='", " m'"); - put_depth(b, dc->meandepth, " mean='", " m'"); + put_depth(b, dc.maxdepth, " max='", " m'"); + put_depth(b, dc.meandepth, " mean='", " m'"); put_string(b, " />\n"); } -static void save_dive_temperature(struct membuffer *b, struct dive *dive) +static void save_dive_temperature(struct membuffer *b, const struct dive &dive) { - if (!dive->airtemp.mkelvin && !dive->watertemp.mkelvin) + if (!dive.airtemp.mkelvin && !dive.watertemp.mkelvin) return; - if (dive->airtemp.mkelvin == dc_airtemp(&dive->dc) && dive->watertemp.mkelvin == dc_watertemp(&dive->dc)) + if (dive.airtemp.mkelvin == dive.dc_airtemp().mkelvin && dive.watertemp.mkelvin == dive.dc_watertemp().mkelvin) return; put_string(b, " airtemp.mkelvin != dc_airtemp(&dive->dc)) - put_temperature(b, dive->airtemp, " air='", " C'"); - if (dive->watertemp.mkelvin != dc_watertemp(&dive->dc)) - put_temperature(b, dive->watertemp, " water='", " C'"); + if (dive.airtemp.mkelvin != dive.dc_airtemp().mkelvin) + put_temperature(b, dive.airtemp, " air='", " C'"); + if (dive.watertemp.mkelvin != dive.dc_watertemp().mkelvin) + put_temperature(b, dive.watertemp, " water='", " C'"); put_string(b, "/>\n"); } -static void save_temperatures(struct membuffer *b, struct divecomputer *dc) +static void save_temperatures(struct membuffer *b, const struct divecomputer &dc) { - if (!dc->airtemp.mkelvin && !dc->watertemp.mkelvin) + if (!dc.airtemp.mkelvin && !dc.watertemp.mkelvin) return; put_string(b, " airtemp, " air='", " C'"); - put_temperature(b, dc->watertemp, " water='", " C'"); + put_temperature(b, dc.airtemp, " air='", " C'"); + put_temperature(b, dc.watertemp, " water='", " C'"); put_string(b, " />\n"); } -static void save_airpressure(struct membuffer *b, struct divecomputer *dc) +static void save_airpressure(struct membuffer *b, const struct divecomputer &dc) { - if (!dc->surface_pressure.mbar) + if (!dc.surface_pressure.mbar) return; put_string(b, " surface_pressure, " pressure='", " bar'"); + put_pressure(b, dc.surface_pressure, " pressure='", " bar'"); put_string(b, " />\n"); } -static void save_salinity(struct membuffer *b, struct divecomputer *dc) +static void save_salinity(struct membuffer *b, const struct divecomputer &dc) { - if (!dc->salinity) + if (!dc.salinity) return; put_string(b, " salinity, " salinity='", " g/l'"); + put_salinity(b, dc.salinity, " salinity='", " g/l'"); put_string(b, " />\n"); } -static void save_overview(struct membuffer *b, struct dive *dive, bool anonymize) +static void save_overview(struct membuffer *b, const struct dive &dive, bool anonymize) { - show_utf8_blanked(b, dive->diveguide, " ", "\n", 0, anonymize); - show_utf8_blanked(b, dive->buddy, " ", "\n", 0, anonymize); - show_utf8_blanked(b, dive->notes, " ", "\n", 0, anonymize); - show_utf8_blanked(b, dive->suit, " ", "\n", 0, anonymize); + show_utf8_blanked(b, dive.diveguide.c_str(), " ", "\n", 0, anonymize); + show_utf8_blanked(b, dive.buddy.c_str(), " ", "\n", 0, anonymize); + show_utf8_blanked(b, dive.notes.c_str(), " ", "\n", 0, anonymize); + show_utf8_blanked(b, dive.suit.c_str(), " ", "\n", 0, anonymize); } static void put_gasmix(struct membuffer *b, struct gasmix mix) @@ -175,48 +176,36 @@ static void put_gasmix(struct membuffer *b, struct gasmix mix) } } -static void save_cylinder_info(struct membuffer *b, struct dive *dive) +static void save_cylinder_info(struct membuffer *b, const struct dive &dive) { - int i, nr; - - nr = nr_cylinders(dive); - - for (i = 0; i < nr; i++) { - cylinder_t *cylinder = get_cylinder(dive, i); - int volume = cylinder->type.size.mliter; - const char *description = cylinder->type.description; - int use = cylinder->cylinder_use; + for (auto &cyl: dive.cylinders) { + int volume = cyl.type.size.mliter; + int use = cyl.cylinder_use; put_format(b, " type.workingpressure, " workpressure='", " bar'"); - show_utf8(b, description, " description='", "'", 1); - put_gasmix(b, cylinder->gasmix); - put_pressure(b, cylinder->start, " start='", " bar'"); - put_pressure(b, cylinder->end, " end='", " bar'"); + put_pressure(b, cyl.type.workingpressure, " workpressure='", " bar'"); + show_utf8(b, cyl.type.description.c_str(), " description='", "'", 1); + put_gasmix(b, cyl.gasmix); + put_pressure(b, cyl.start, " start='", " bar'"); + put_pressure(b, cyl.end, " end='", " bar'"); if (use > OC_GAS && use < NUM_GAS_USE) show_utf8(b, cylinderuse_text[use], " use='", "'", 1); - if (cylinder->depth.mm != 0) - put_milli(b, " depth='", cylinder->depth.mm, " m'"); + if (cyl.depth.mm != 0) + put_milli(b, " depth='", cyl.depth.mm, " m'"); put_format(b, " />\n"); } } -static void save_weightsystem_info(struct membuffer *b, struct dive *dive) +static void save_weightsystem_info(struct membuffer *b, const struct dive &dive) { - int i, nr; - - nr = nr_weightsystems(dive); - - for (i = 0; i < nr; i++) { - weightsystem_t ws = dive->weightsystems.weightsystems[i]; + for (auto &ws: dive.weightsystems) { int grams = ws.weight.grams; - const char *description = ws.description; put_format(b, " \n"); } } @@ -232,15 +221,15 @@ static void show_index(struct membuffer *b, int value, const char *pre, const ch show_integer(b, value, pre, post); } -static void save_sample(struct membuffer *b, struct sample *sample, struct sample *old, int o2sensor) +static void save_sample(struct membuffer *b, const struct sample &sample, struct sample &old, int o2sensor) { int idx; - put_format(b, " time.seconds, 60)); - put_milli(b, " depth='", sample->depth.mm, " m'"); - if (sample->temperature.mkelvin && sample->temperature.mkelvin != old->temperature.mkelvin) { - put_temperature(b, sample->temperature, " temp='", " C'"); - old->temperature = sample->temperature; + put_format(b, " pressure[idx]; - int sensor = sample->sensor[idx]; + pressure_t p = sample.pressure[idx]; + int sensor = sample.sensor[idx]; if (sensor == NO_SENSOR) continue; @@ -264,9 +253,9 @@ static void save_sample(struct membuffer *b, struct sample *sample, struct sampl continue; } put_pressure(b, p, " pressure='", " bar'"); - if (sensor != old->sensor[0]) { + if (sensor != old.sensor[0]) { put_format(b, " sensor='%d'", sensor); - old->sensor[0] = sensor; + old.sensor[0] = sensor; } continue; } @@ -277,135 +266,131 @@ static void save_sample(struct membuffer *b, struct sample *sample, struct sampl } /* the deco/ndl values are stored whenever they change */ - if (sample->ndl.seconds != old->ndl.seconds) { - put_format(b, " ndl='%u:%02u min'", FRACTION_TUPLE(sample->ndl.seconds, 60)); - old->ndl = sample->ndl; + if (sample.ndl.seconds != old.ndl.seconds) { + put_format(b, " ndl='%u:%02u min'", FRACTION_TUPLE(sample.ndl.seconds, 60)); + old.ndl = sample.ndl; } - if (sample->tts.seconds != old->tts.seconds) { - put_format(b, " tts='%u:%02u min'", FRACTION_TUPLE(sample->tts.seconds, 60)); - old->tts = sample->tts; + if (sample.tts.seconds != old.tts.seconds) { + put_format(b, " tts='%u:%02u min'", FRACTION_TUPLE(sample.tts.seconds, 60)); + old.tts = sample.tts; } - if (sample->rbt.seconds != old->rbt.seconds) { - put_format(b, " rbt='%u:%02u min'", FRACTION_TUPLE(sample->rbt.seconds, 60)); - old->rbt = sample->rbt; + if (sample.rbt.seconds != old.rbt.seconds) { + put_format(b, " rbt='%u:%02u min'", FRACTION_TUPLE(sample.rbt.seconds, 60)); + old.rbt = sample.rbt; } - if (sample->in_deco != old->in_deco) { - put_format(b, " in_deco='%d'", sample->in_deco ? 1 : 0); - old->in_deco = sample->in_deco; + if (sample.in_deco != old.in_deco) { + put_format(b, " in_deco='%d'", sample.in_deco ? 1 : 0); + old.in_deco = sample.in_deco; } - if (sample->stoptime.seconds != old->stoptime.seconds) { - put_format(b, " stoptime='%u:%02u min'", FRACTION_TUPLE(sample->stoptime.seconds, 60)); - old->stoptime = sample->stoptime; + if (sample.stoptime.seconds != old.stoptime.seconds) { + put_format(b, " stoptime='%u:%02u min'", FRACTION_TUPLE(sample.stoptime.seconds, 60)); + old.stoptime = sample.stoptime; } - if (sample->stopdepth.mm != old->stopdepth.mm) { - put_milli(b, " stopdepth='", sample->stopdepth.mm, " m'"); - old->stopdepth = sample->stopdepth; + if (sample.stopdepth.mm != old.stopdepth.mm) { + put_milli(b, " stopdepth='", sample.stopdepth.mm, " m'"); + old.stopdepth = sample.stopdepth; } - if (sample->cns != old->cns) { - put_format(b, " cns='%u%%'", sample->cns); - old->cns = sample->cns; + if (sample.cns != old.cns) { + put_format(b, " cns='%u%%'", sample.cns); + old.cns = sample.cns; } - if ((sample->o2sensor[0].mbar) && (sample->o2sensor[0].mbar != old->o2sensor[0].mbar)) { - put_milli(b, " sensor1='", sample->o2sensor[0].mbar, " bar'"); - old->o2sensor[0] = sample->o2sensor[0]; + if ((sample.o2sensor[0].mbar) && (sample.o2sensor[0].mbar != old.o2sensor[0].mbar)) { + put_milli(b, " sensor1='", sample.o2sensor[0].mbar, " bar'"); + old.o2sensor[0] = sample.o2sensor[0]; } - if ((sample->o2sensor[1].mbar) && (sample->o2sensor[1].mbar != old->o2sensor[1].mbar)) { - put_milli(b, " sensor2='", sample->o2sensor[1].mbar, " bar'"); - old->o2sensor[1] = sample->o2sensor[1]; + if ((sample.o2sensor[1].mbar) && (sample.o2sensor[1].mbar != old.o2sensor[1].mbar)) { + put_milli(b, " sensor2='", sample.o2sensor[1].mbar, " bar'"); + old.o2sensor[1] = sample.o2sensor[1]; } - if ((sample->o2sensor[2].mbar) && (sample->o2sensor[2].mbar != old->o2sensor[2].mbar)) { - put_milli(b, " sensor3='", sample->o2sensor[2].mbar, " bar'"); - old->o2sensor[2] = sample->o2sensor[2]; + if ((sample.o2sensor[2].mbar) && (sample.o2sensor[2].mbar != old.o2sensor[2].mbar)) { + put_milli(b, " sensor3='", sample.o2sensor[2].mbar, " bar'"); + old.o2sensor[2] = sample.o2sensor[2]; } - if ((sample->o2sensor[3].mbar) && (sample->o2sensor[3].mbar != old->o2sensor[3].mbar)) { - put_milli(b, " sensor4='", sample->o2sensor[3].mbar, " bar'"); - old->o2sensor[3] = sample->o2sensor[3]; + if ((sample.o2sensor[3].mbar) && (sample.o2sensor[3].mbar != old.o2sensor[3].mbar)) { + put_milli(b, " sensor4='", sample.o2sensor[3].mbar, " bar'"); + old.o2sensor[3] = sample.o2sensor[3]; } - if ((sample->o2sensor[4].mbar) && (sample->o2sensor[4].mbar != old->o2sensor[4].mbar)) { - put_milli(b, " sensor5='", sample->o2sensor[4].mbar, " bar'"); - old->o2sensor[4] = sample->o2sensor[4]; + if ((sample.o2sensor[4].mbar) && (sample.o2sensor[4].mbar != old.o2sensor[4].mbar)) { + put_milli(b, " sensor5='", sample.o2sensor[4].mbar, " bar'"); + old.o2sensor[4] = sample.o2sensor[4]; } - if ((sample->o2sensor[5].mbar) && (sample->o2sensor[5].mbar != old->o2sensor[5].mbar)) { - put_milli(b, " sensor6='", sample->o2sensor[5].mbar, " bar'"); - old->o2sensor[5] = sample->o2sensor[5]; + if ((sample.o2sensor[5].mbar) && (sample.o2sensor[5].mbar != old.o2sensor[5].mbar)) { + put_milli(b, " sensor6='", sample.o2sensor[5].mbar, " bar'"); + old.o2sensor[5] = sample.o2sensor[5]; } - if (sample->setpoint.mbar != old->setpoint.mbar) { - put_milli(b, " po2='", sample->setpoint.mbar, " bar'"); - old->setpoint = sample->setpoint; + if (sample.setpoint.mbar != old.setpoint.mbar) { + put_milli(b, " po2='", sample.setpoint.mbar, " bar'"); + old.setpoint = sample.setpoint; } - if (sample->heartbeat != old->heartbeat) { - show_index(b, sample->heartbeat, "heartbeat='", "'"); - old->heartbeat = sample->heartbeat; + if (sample.heartbeat != old.heartbeat) { + show_index(b, sample.heartbeat, "heartbeat='", "'"); + old.heartbeat = sample.heartbeat; } - if (sample->bearing.degrees != old->bearing.degrees) { - show_index(b, sample->bearing.degrees, "bearing='", "'"); - old->bearing.degrees = sample->bearing.degrees; + if (sample.bearing.degrees != old.bearing.degrees) { + show_index(b, sample.bearing.degrees, "bearing='", "'"); + old.bearing.degrees = sample.bearing.degrees; } put_format(b, " />\n"); } -static void save_one_event(struct membuffer *b, struct dive *dive, struct event *ev) +static void save_one_event(struct membuffer *b, const struct dive &dive, const struct event &ev) { - put_format(b, " time.seconds, 60)); - show_index(b, ev->type, "type='", "'"); - show_index(b, ev->flags, "flags='", "'"); - if (!strcmp(ev->name,"modechange")) - show_utf8(b, divemode_text[ev->value], " divemode='", "'",1); + put_format(b, " value, "value='", "'"); - show_utf8(b, ev->name, " name='", "'", 1); - if (event_is_gaschange(ev)) { - struct gasmix mix = get_gasmix_from_event(dive, ev); - if (ev->gas.index >= 0) - show_integer(b, ev->gas.index, "cylinder='", "'"); + show_index(b, ev.value, "value='", "'"); + show_utf8(b, ev.name.c_str(), " name='", "'", 1); + if (ev.is_gaschange()) { + struct gasmix mix = dive.get_gasmix_from_event(ev); + if (ev.gas.index >= 0) + show_integer(b, ev.gas.index, "cylinder='", "'"); put_gasmix(b, mix); } put_format(b, " />\n"); } -static void save_events(struct membuffer *b, struct dive *dive, struct event *ev) +static void save_events(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) { - while (ev) { + for (auto &ev: dc.events) save_one_event(b, dive, ev); - ev = ev->next; - } } -static void save_tags(struct membuffer *b, struct tag_entry *entry) +static void save_tags(struct membuffer *b, const tag_list &tags) { - if (entry) { + if (!tags.empty()) { const char *sep = " tags='"; - do { - const struct divetag *tag = entry->tag; + for (const divetag *tag: tags) { put_string(b, sep); /* If the tag has been translated, write the source to the xml file */ quote(b, tag->source.empty() ? tag->name.c_str() : tag->source.c_str(), 1); sep = ", "; - } while ((entry = entry->next) != NULL); + } put_string(b, "'"); } } -static void save_extra_data(struct membuffer *b, struct extra_data *ed) +static void save_extra_data(struct membuffer *b, const struct divecomputer &dc) { - while (ed) { - if (ed->key && ed->value) { + for (const auto &ed: dc.extra_data) { + if (!ed.key.empty() && !ed.value.empty()) { put_string(b, " key, " key='", "'", 1); - show_utf8(b, ed->value, " value='", "'", 1); + show_utf8(b, ed.key.c_str(), " key='", "'", 1); + show_utf8(b, ed.value.c_str(), " value='", "'", 1); put_string(b, " />\n"); } - ed = ed->next; } } @@ -422,69 +407,62 @@ static void show_date(struct membuffer *b, timestamp_t when) tm.tm_hour, tm.tm_min, tm.tm_sec); } -static void save_samples(struct membuffer *b, struct dive *dive, struct divecomputer *dc) +static void save_samples(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) { - int nr; - int o2sensor; - struct sample *s; struct sample dummy; /* Set up default pressure sensor indices */ - o2sensor = legacy_format_o2pressures(dive, dc); + int o2sensor = legacy_format_o2pressures(&dive, &dc); if (o2sensor >= 0) { dummy.sensor[0] = !o2sensor; dummy.sensor[1] = o2sensor; } - s = dc->sample; - nr = dc->samples; - while (--nr >= 0) { - save_sample(b, s, &dummy, o2sensor); - s++; - } + for (const auto &s: dc.samples) + save_sample(b, s, dummy, o2sensor); } -static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer *dc) +static void save_dc(struct membuffer *b, const struct dive &dive, const struct divecomputer &dc) { put_format(b, " model, " model='", "'", 1); - if (dc->last_manual_time.seconds) - put_duration(b, dc->last_manual_time, " last-manual-time='", " min'"); - if (dc->deviceid) - put_format(b, " deviceid='%08x'", dc->deviceid); - if (dc->diveid) - put_format(b, " diveid='%08x'", dc->diveid); - if (dc->when && dc->when != dive->when) - show_date(b, dc->when); - if (dc->duration.seconds && dc->duration.seconds != dive->dc.duration.seconds) - put_duration(b, dc->duration, " duration='", " min'"); - if (dc->divemode != OC) { - int i = (int)dc->divemode; + show_utf8(b, dc.model.c_str(), " model='", "'", 1); + if (dc.last_manual_time.seconds) + put_duration(b, dc.last_manual_time, " last-manual-time='", " min'"); + if (dc.deviceid) + put_format(b, " deviceid='%08x'", dc.deviceid); + if (dc.diveid) + put_format(b, " diveid='%08x'", dc.diveid); + if (dc.when && dc.when != dive.when) + show_date(b, dc.when); + if (dc.duration.seconds && dc.duration.seconds != dive.dcs[0].duration.seconds) + put_duration(b, dc.duration, " duration='", " min'"); + if (dc.divemode != OC) { + int i = (int)dc.divemode; if (i >= 0 && i < NUM_DIVEMODE) show_utf8(b, divemode_text[i], " dctype='", "'", 1); - if (dc->no_o2sensors) - put_format(b," no_o2sensors='%d'", dc->no_o2sensors); + if (dc.no_o2sensors) + put_format(b," no_o2sensors='%d'", dc.no_o2sensors); } put_format(b, ">\n"); save_depths(b, dc); save_temperatures(b, dc); save_airpressure(b, dc); save_salinity(b, dc); - put_duration(b, dc->surfacetime, " ", " min\n"); - save_extra_data(b, dc->extra_data); - save_events(b, dive, dc->events); + put_duration(b, dc.surfacetime, " ", " min\n"); + save_extra_data(b, dc); + save_events(b, dive, dc); save_samples(b, dive, dc); put_format(b, " \n"); } -static void save_picture(struct membuffer *b, struct picture *pic) +static void save_picture(struct membuffer *b, const struct picture &pic) { put_string(b, " offset.seconds) { - int offset = pic->offset.seconds; + if (pic.offset.seconds) { + int offset = pic.offset.seconds; char sign = '+'; if (offset < 0) { sign = '-'; @@ -492,56 +470,55 @@ static void save_picture(struct membuffer *b, struct picture *pic) } put_format(b, " offset='%c%u:%02u min'", sign, FRACTION_TUPLE(offset, 60)); } - put_location(b, &pic->location, " gps='","'"); + put_location(b, &pic.location, " gps='","'"); put_string(b, "/>\n"); } -extern "C" void save_one_dive_to_mb(struct membuffer *b, struct dive *dive, bool anonymize) +void save_one_dive_to_mb(struct membuffer *b, const struct dive &dive, bool anonymize) { - struct divecomputer *dc; - pressure_t surface_pressure = un_fixup_surface_pressure(dive); + pressure_t surface_pressure = dive.un_fixup_surface_pressure(); put_string(b, "number) - put_format(b, " number='%d'", dive->number); - if (dive->notrip) + if (dive.number) + put_format(b, " number='%d'", dive.number); + if (dive.notrip) put_format(b, " tripflag='NOTRIP'"); - if (dive->rating) - put_format(b, " rating='%d'", dive->rating); - if (dive->visibility) - put_format(b, " visibility='%d'", dive->visibility); - if (dive->wavesize) - put_format(b, " wavesize='%d'", dive->wavesize); - if (dive->current) - put_format(b, " current='%d'", dive->current); - if (dive->surge) - put_format(b, " surge='%d'", dive->surge); - if (dive->chill) - put_format(b, " chill='%d'", dive->chill); - if (dive->invalid) + if (dive.rating) + put_format(b, " rating='%d'", dive.rating); + if (dive.visibility) + put_format(b, " visibility='%d'", dive.visibility); + if (dive.wavesize) + put_format(b, " wavesize='%d'", dive.wavesize); + if (dive.current) + put_format(b, " current='%d'", dive.current); + if (dive.surge) + put_format(b, " surge='%d'", dive.surge); + if (dive.chill) + put_format(b, " chill='%d'", dive.chill); + if (dive.invalid) put_format(b, " invalid='1'"); // These three are calculated, and not read when loading. // But saving them into the XML is useful for data export. - if (dive->sac > 100) - put_format(b, " sac='%d.%03d l/min'", FRACTION_TUPLE(dive->sac, 1000)); - if (dive->otu) - put_format(b, " otu='%d'", dive->otu); - if (dive->maxcns) - put_format(b, " cns='%d%%'", dive->maxcns); + if (dive.sac > 100) + put_format(b, " sac='%d.%03d l/min'", FRACTION_TUPLE(dive.sac, 1000)); + if (dive.otu) + put_format(b, " otu='%d'", dive.otu); + if (dive.maxcns) + put_format(b, " cns='%d%%'", dive.maxcns); - save_tags(b, dive->tag_list); - if (dive->dive_site) - put_format(b, " divesiteid='%8x'", dive->dive_site->uuid); - if (dive->user_salinity) - put_salinity(b, dive->user_salinity, " watersalinity='", " g/l'"); - show_date(b, dive->when); + save_tags(b, dive.tags); + if (dive.dive_site) + put_format(b, " divesiteid='%8x'", dive.dive_site->uuid); + if (dive.user_salinity) + put_salinity(b, dive.user_salinity, " watersalinity='", " g/l'"); + show_date(b, dive.when); if (surface_pressure.mbar) put_pressure(b, surface_pressure, " airpressure='", " bar'"); - if (dive->dc.duration.seconds > 0) + if (dive.dcs[0].duration.seconds > 0) put_format(b, " duration='%u:%02u min'>\n", - FRACTION_TUPLE(dive->dc.duration.seconds, 60)); + FRACTION_TUPLE(dive.dcs[0].duration.seconds, 60)); else put_format(b, ">\n"); save_overview(b, dive, anonymize); @@ -549,16 +526,16 @@ extern "C" void save_one_dive_to_mb(struct membuffer *b, struct dive *dive, bool save_weightsystem_info(b, dive); save_dive_temperature(b, dive); /* Save the dive computer data */ - for_each_dc(dive, dc) + for (auto &dc: dive.dcs) save_dc(b, dive, dc); - FOR_EACH_PICTURE(dive) + for (auto &picture: dive.pictures) save_picture(b, picture); put_format(b, "\n"); } -extern "C" int save_dive(FILE *f, struct dive *dive, bool anonymize) +int save_dive(FILE *f, const struct dive &dive, bool anonymize) { - struct membufferpp buf; + membuffer buf; save_one_dive_to_mb(&buf, dive, anonymize); flush_buffer(&buf, f); @@ -566,16 +543,13 @@ extern "C" int save_dive(FILE *f, struct dive *dive, bool anonymize) return 0; } -static void save_trip(struct membuffer *b, dive_trip_t *trip, bool anonymize) +static void save_trip(struct membuffer *b, dive_trip &trip, bool anonymize) { - int i; - struct dive *dive; - put_format(b, "location, " location=\'", "\'", 1); + show_date(b, trip.date()); + show_utf8(b, trip.location.c_str(), " location=\'", "\'", 1); put_format(b, ">\n"); - show_utf8(b, trip->notes, "", "\n", 0); + show_utf8(b, trip.notes.c_str(), "", "\n", 0); /* * Incredibly cheesy: we want to save the dives sorted, and they @@ -583,89 +557,69 @@ static void save_trip(struct membuffer *b, dive_trip_t *trip, bool anonymize) * list in the trip, we just traverse the global dive array and * check the divetrip pointer.. */ - for_each_dive(i, dive) { - if (dive->divetrip == trip) - save_one_dive_to_mb(b, dive, anonymize); + for (auto &dive: divelog.dives) { + if (dive->divetrip == &trip) + save_one_dive_to_mb(b, *dive, anonymize); } put_format(b, "\n"); } -static void save_one_device(struct membuffer *b, const struct device *d) +static void save_one_device(struct membuffer *b, const struct device &d) { - const char *model = device_get_model(d); - const char *nickname = device_get_nickname(d); - const char *serial_nr = device_get_serial(d); - /* Nicknames that are empty or the same as the device model are not interesting */ - if (empty_string(nickname) || !strcmp(model, nickname)) - nickname = NULL; - - /* Serial numbers that are empty are not interesting */ - if (empty_string(serial_nr)) - serial_nr = NULL; - - /* Do we have anything interesting about this dive computer to save? */ - if (!serial_nr || !nickname) + if (d.nickName.empty() || d.serialNumber.empty() || d.model == d.nickName) return; put_format(b, "\n"); } -static void save_one_fingerprint(struct membuffer *b, int i) +static void save_one_fingerprint(struct membuffer *b, const fingerprint_record &fp) { put_format(b, "\n", - fp_get_model(&fingerprint_table, i), - fp_get_serial(&fingerprint_table, i), - fp_get_deviceid(&fingerprint_table, i), - fp_get_diveid(&fingerprint_table, i), - fp_get_data(&fingerprint_table, i).c_str()); + fp.model, fp.serial, fp.fdeviceid, fp.fdiveid, fp.get_data().c_str()); } -extern "C" int save_dives(const char *filename) +int save_dives(const char *filename) { return save_dives_logic(filename, false, false); } static void save_filter_presets(struct membuffer *b) { - int i; - - if (filter_presets_count() <= 0) + if (divelog.filter_presets.empty()) return; put_format(b, "\n"); - for (i = 0; i < filter_presets_count(); i++) { - std::string name = filter_preset_name(i); + for (auto &filter_preset: divelog.filter_presets) { put_format(b, " \n"); - std::string fulltext = filter_preset_fulltext_query(i); + std::string fulltext = filter_preset.fulltext_query(); if (!fulltext.empty()) { - const char *fulltext_mode = filter_preset_fulltext_mode(i); + const char *fulltext_mode = filter_preset.fulltext_mode(); show_utf8(b, fulltext_mode, " ", 1); show_utf8(b, fulltext.c_str(), "", "\n", 0); } - for (int j = 0; j < filter_preset_constraint_count(i); j++) { - const struct filter_constraint *constraint = filter_preset_constraint(i, j); - const char *type = filter_constraint_type_to_string(constraint->type); + for (auto &constraint: filter_preset.data.constraints) { + const char *type = filter_constraint_type_to_string(constraint.type); put_format(b, " type)) { - const char *mode = filter_constraint_string_mode_to_string(constraint->string_mode); + if (filter_constraint_has_string_mode(constraint.type)) { + const char *mode = filter_constraint_string_mode_to_string(constraint.string_mode); show_utf8(b, mode, " string_mode='", "'", 1); } - if (filter_constraint_has_range_mode(constraint->type)) { - const char *mode = filter_constraint_range_mode_to_string(constraint->range_mode); + if (filter_constraint_has_range_mode(constraint.type)) { + const char *mode = filter_constraint_range_mode_to_string(constraint.range_mode); show_utf8(b, mode, " range_mode='", "'", 1); } - if (constraint->negate) + if (constraint.negate) put_format(b, " negate='1'"); put_format(b, ">"); std::string data = filter_constraint_data_to_string(constraint); @@ -679,21 +633,16 @@ static void save_filter_presets(struct membuffer *b) static void save_dives_buffer(struct membuffer *b, bool select_only, bool anonymize) { - int i; - struct dive *dive; - dive_trip_t *trip; - - put_format(b, "\n\n", DATAFORMAT_VERSION); + put_format(b, "\n\n", dataformat_version); /* save the dive computer nicknames, if any */ - for (int i = 0; i < nr_devices(divelog.devices); i++) { - const struct device *d = get_device(divelog.devices, i); + for (auto &d: divelog.devices) { if (!select_only || device_used_by_selected_dive(d)) save_one_device(b, d); } /* save the fingerprint data */ - for (int i = 0; i < nr_fingerprints(&fingerprint_table); i++) - save_one_fingerprint(b, i); + for (auto &fp: fingerprints) + save_one_fingerprint(b, fp); if (divelog.autogroup) put_format(b, " \n"); @@ -701,55 +650,49 @@ static void save_dives_buffer(struct membuffer *b, bool select_only, bool anonym /* save the dive sites */ put_format(b, "\n"); - for (i = 0; i < divelog.sites->nr; i++) { - struct dive_site *ds = get_dive_site(i, divelog.sites); + for (const auto &ds: divelog.sites) { /* Don't export empty dive sites */ - if (dive_site_is_empty(ds)) + if (ds->is_empty()) continue; /* Only write used dive sites when exporting selected dives */ - if (select_only && !is_dive_site_selected(ds)) + if (select_only && !ds->is_selected()) continue; put_format(b, "uuid); - show_utf8_blanked(b, ds->name, " name='", "'", 1, anonymize); + show_utf8_blanked(b, ds->name.c_str(), " name='", "'", 1, anonymize); put_location(b, &ds->location, " gps='", "'"); - show_utf8_blanked(b, ds->description, " description='", "'", 1, anonymize); + show_utf8_blanked(b, ds->description.c_str(), " description='", "'", 1, anonymize); put_format(b, ">\n"); - show_utf8_blanked(b, ds->notes, " ", " \n", 0, anonymize); - if (ds->taxonomy.nr) { - for (int j = 0; j < ds->taxonomy.nr; j++) { - struct taxonomy *t = &ds->taxonomy.category[j]; - if (t->category != TC_NONE && t->value) { - put_format(b, " category); - put_format(b, " origin='%d'", t->origin); - show_utf8_blanked(b, t->value, " value='", "'", 1, anonymize); - put_format(b, "/>\n"); - } + show_utf8_blanked(b, ds->notes.c_str(), " ", " \n", 0, anonymize); + for (auto const &t: ds->taxonomy) { + if (t.category != TC_NONE && !t.value.empty()) { + put_format(b, " \n"); } } put_format(b, "\n"); } put_format(b, "\n\n"); - for (i = 0; i < divelog.trips->nr; ++i) - divelog.trips->trips[i]->saved = 0; + for (auto &trip: divelog.trips) + trip->saved = 0; /* save the filter presets */ save_filter_presets(b); /* save the dives */ - for_each_dive(i, dive) { + for (auto &dive: divelog.dives) { if (select_only) { - if (!dive->selected) continue; - save_one_dive_to_mb(b, dive, anonymize); - + save_one_dive_to_mb(b, *dive, anonymize); } else { - trip = dive->divetrip; + dive_trip *trip = dive->divetrip; /* Bare dive without a trip? */ if (!trip) { - save_one_dive_to_mb(b, dive, anonymize); + save_one_dive_to_mb(b, *dive, anonymize); continue; } @@ -759,7 +702,7 @@ static void save_dives_buffer(struct membuffer *b, bool select_only, bool anonym /* We haven't seen this trip before - save it and all dives */ trip->saved = 1; - save_trip(b, trip, anonymize); + save_trip(b, *trip, anonymize); } } put_format(b, "\n\n"); @@ -801,7 +744,7 @@ static void try_to_backup(const char *filename) while (extension[i][0] != '\0') { int elen = strlen(extension[i]); if (strcasecmp(filename + flen - elen, extension[i]) == 0) { - if (last_xml_version < DATAFORMAT_VERSION) { + if (last_xml_version < dataformat_version) { std::string special_ext = std::string(extension[i]) + ".v" + std::to_string(last_xml_version); save_backup(filename, extension[i], special_ext.c_str()); } else { @@ -813,9 +756,9 @@ static void try_to_backup(const char *filename) } } -extern "C" int save_dives_logic(const char *filename, const bool select_only, bool anonymize) +int save_dives_logic(const char *filename, const bool select_only, bool anonymize) { - struct membufferpp buf; + membuffer buf; struct git_info info; FILE *f; int error = 0; @@ -857,7 +800,7 @@ int export_dives_xslt(const char *filename, const bool selected, const int units static int export_dives_xslt_doit(const char *filename, struct xml_params *params, bool selected, int units, const char *export_xslt, bool anonymize) { FILE *f; - struct membufferpp buf; + membuffer buf; xmlDoc *doc; xsltStylesheetPtr xslt = NULL; xmlDoc *transformed; @@ -909,27 +852,24 @@ static int export_dives_xslt_doit(const char *filename, struct xml_params *param static void save_dive_sites_buffer(struct membuffer *b, const struct dive_site *sites[], int nr_sites, bool anonymize) { int i; - put_format(b, "\n", DATAFORMAT_VERSION); + put_format(b, "\n", dataformat_version); /* save the dive sites */ for (i = 0; i < nr_sites; i++) { const struct dive_site *ds = sites[i]; put_format(b, "uuid); - show_utf8_blanked(b, ds->name, " name='", "'", 1, anonymize); + show_utf8_blanked(b, ds->name.c_str(), " name='", "'", 1, anonymize); put_location(b, &ds->location, " gps='", "'"); - show_utf8_blanked(b, ds->description, " description='", "'", 1, anonymize); + show_utf8_blanked(b, ds->description.c_str(), " description='", "'", 1, anonymize); put_format(b, ">\n"); - show_utf8_blanked(b, ds->notes, " ", " \n", 0, anonymize); - if (ds->taxonomy.nr) { - for (int j = 0; j < ds->taxonomy.nr; j++) { - struct taxonomy *t = &ds->taxonomy.category[j]; - if (t->category != TC_NONE && t->value) { - put_format(b, " category); - put_format(b, " origin='%d'", t->origin); - show_utf8_blanked(b, t->value, " value='", "'", 1, anonymize); - put_format(b, "/>\n"); - } + show_utf8_blanked(b, ds->notes.c_str(), " ", " \n", 0, anonymize); + for (const auto &t: ds->taxonomy) { + if (t.category != TC_NONE && !t.value.empty()) { + put_format(b, " \n"); } } put_format(b, "\n"); @@ -937,9 +877,9 @@ static void save_dive_sites_buffer(struct membuffer *b, const struct dive_site * put_format(b, "\n"); } -extern "C" int save_dive_sites_logic(const char *filename, const struct dive_site *sites[], int nr_sites, bool anonymize) +int save_dive_sites_logic(const char *filename, const struct dive_site *sites[], int nr_sites, bool anonymize) { - struct membufferpp buf; + membuffer buf; FILE *f; int error = 0; diff --git a/core/selection.cpp b/core/selection.cpp index 616ad6f5b..061104f19 100644 --- a/core/selection.cpp +++ b/core/selection.cpp @@ -14,34 +14,22 @@ struct dive *current_dive = NULL; int amount_selected; static int amount_trips_selected; -extern "C" struct dive *first_selected_dive() +struct dive *first_selected_dive() { - int idx; - struct dive *d; - - for_each_dive (idx, d) { - if (d->selected) - return d; - } - return NULL; + auto it = std::find_if(divelog.dives.begin(), divelog.dives.end(), + [](auto &d) { return d->selected; }); + return it != divelog.dives.end() ? it->get() : nullptr; } -extern "C" struct dive *last_selected_dive() +struct dive *last_selected_dive() { - int idx; - struct dive *d, *ret = NULL; - - for_each_dive (idx, d) { - if (d->selected) - ret = d; - } - return ret; + auto it = std::find_if(divelog.dives.rbegin(), divelog.dives.rend(), + [](auto &d) { return d->selected; }); + return it != divelog.dives.rend() ? it->get() : nullptr; } -extern "C" bool consecutive_selected() +bool consecutive_selected() { - struct dive *d; - int i; bool consecutive = true; bool firstfound = false; bool lastfound = false; @@ -49,7 +37,7 @@ extern "C" bool consecutive_selected() if (amount_selected == 0 || amount_selected == 1) return true; - for_each_dive(i, d) { + for (auto &d: divelog.dives) { if (d->selected) { if (!firstfound) firstfound = true; @@ -63,13 +51,10 @@ extern "C" bool consecutive_selected() } #if DEBUG_SELECTION_TRACKING -extern "C" void dump_selection(void) +void dump_selection() { - int i; - struct dive *dive; - printf("currently selected are %u dives:", amount_selected); - for_each_dive(i, dive) { + for (auto &dive: divelog.dives) { if (dive->selected) printf(" %d", i); } @@ -116,7 +101,7 @@ static void setClosestCurrentDive(timestamp_t when, const std::vector &s // No selected dive is visible! Take the closest dive. Note, this might // return null, but that just means unsetting the current dive (as no // dive is visible anyway). - current_dive = find_next_visible_dive(when); + current_dive = divelog.dives.find_next_visible_dive(when); if (current_dive) { current_dive->selected = true; amount_selected++; @@ -137,10 +122,8 @@ QVector setSelectionCore(const std::vector &selection, dive *cur divesToSelect.reserve(selection.size()); // TODO: We might want to keep track of selected dives in a more efficient way! - int i; - dive *d; amount_selected = 0; // We recalculate amount_selected - for_each_dive(i, d) { + for (auto &d: divelog.dives) { // We only modify dives that are currently visible. if (d->hidden_by_filter) { d->selected = false; // Note, not necessary, just to be sure @@ -150,11 +133,11 @@ QVector setSelectionCore(const std::vector &selection, dive *cur // Search the dive in the list of selected dives. // TODO: By sorting the list in the same way as the backend, this could be made more efficient. - bool newState = std::find(selection.begin(), selection.end(), d) != selection.end(); + bool newState = std::find(selection.begin(), selection.end(), d.get()) != selection.end(); if (newState) { ++amount_selected; - divesToSelect.push_back(d); + divesToSelect.push_back(d.get()); } d->selected = newState; } @@ -173,8 +156,8 @@ QVector setSelectionCore(const std::vector &selection, dive *cur static void clear_trip_selection() { amount_trips_selected = 0; - for (int i = 0; i < divelog.trips->nr; ++i) - divelog.trips->trips[i]->selected = false; + for (auto &trip: divelog.trips) + trip->selected = false; } // Reset the selection to the dives of the "selection" vector and send the appropriate signals. @@ -217,22 +200,18 @@ void setTripSelection(dive_trip *trip, dive *currentDive) return; current_dive = currentDive; - for (int i = 0; i < divelog.dives->nr; ++i) { - dive &d = *divelog.dives->dives[i]; - d.selected = d.divetrip == trip; - } - for (int i = 0; i < divelog.trips->nr; ++i) { - dive_trip *t = divelog.trips->trips[i]; - t->selected = t == trip; - } + for (auto &d: divelog.dives) + d->selected = d->divetrip == trip; + for (auto &t: divelog.trips) + t->selected = t.get() == trip; - amount_selected = trip->dives.nr; + amount_selected = static_cast(trip->dives.size()); amount_trips_selected = 1; emit diveListNotifier.tripSelected(trip, currentDive); } -extern "C" void select_single_dive(dive *d) +void select_single_dive(dive *d) { if (d) setSelection(std::vector{ d }, d, -1); @@ -247,11 +226,9 @@ std::vector getDiveSelection() std::vector res; res.reserve(amount_selected); - int i; - dive *d; - for_each_dive(i, d) { + for (auto &d: divelog.dives) { if (d->selected) - res.push_back(d); + res.push_back(d.get()); } return res; } @@ -259,7 +236,7 @@ std::vector getDiveSelection() bool diveInSelection(const std::vector &selection, const dive *d) { // Do a binary search using the ordering of the dive list. - auto it = std::lower_bound(selection.begin(), selection.end(), d, dive_less_than); + auto it = std::lower_bound(selection.begin(), selection.end(), d, dive_less_than_ptr); return it != selection.end() && *it == d; } @@ -267,7 +244,7 @@ void updateSelection(std::vector &selection, const std::vector & { // We could sort the array and merge the vectors as we do in the undo code. But is it necessary? for (dive *d: add) { - auto it = std::lower_bound(selection.begin(), selection.end(), d, dive_less_than); + auto it = std::lower_bound(selection.begin(), selection.end(), d, dive_less_than_ptr); if (it != selection.end() && *it == d) continue; // Ooops. Already there? selection.insert(it, d); @@ -275,7 +252,7 @@ void updateSelection(std::vector &selection, const std::vector & // Likewise, we could sort the array and be smarter here. Again, is it necessary? for (dive *d: remove) { - auto it = std::lower_bound(selection.begin(), selection.end(), d, dive_less_than); + auto it = std::lower_bound(selection.begin(), selection.end(), d, dive_less_than_ptr); if (it == selection.end() || *it != d) continue; // Ooops. Not there? selection.erase(it); @@ -283,19 +260,18 @@ void updateSelection(std::vector &selection, const std::vector & } // Select the first dive that is visible -extern "C" void select_newest_visible_dive() +void select_newest_visible_dive() { - for (int i = divelog.dives->nr - 1; i >= 0; --i) { - dive *d = divelog.dives->dives[i]; - if (!d->hidden_by_filter) - return select_single_dive(d); + for (auto it = divelog.dives.rbegin(); it != divelog.dives.rend(); ++it) { + if (!(*it)->hidden_by_filter) + return select_single_dive(it->get()); } // No visible dive -> deselect all select_single_dive(nullptr); } -extern "C" void select_trip(struct dive_trip *trip) +void select_trip(struct dive_trip *trip) { if (trip && !trip->selected) { trip->selected = true; @@ -303,7 +279,7 @@ extern "C" void select_trip(struct dive_trip *trip) } } -extern "C" void deselect_trip(struct dive_trip *trip) +void deselect_trip(struct dive_trip *trip) { if (trip && trip->selected) { trip->selected = false; @@ -311,13 +287,13 @@ extern "C" void deselect_trip(struct dive_trip *trip) } } -extern "C" struct dive_trip *single_selected_trip() +struct dive_trip *single_selected_trip() { if (amount_trips_selected != 1) return NULL; - for (int i = 0; i < divelog.trips->nr; ++i) { - if (divelog.trips->trips[i]->selected) - return divelog.trips->trips[i]; + for (auto &trip: divelog.trips) { + if (trip->selected) + return trip.get(); } report_info("warning: found no selected trip even though one should be selected"); return NULL; // shouldn't happen diff --git a/core/selection.h b/core/selection.h index 6c330f3bd..a1e92e8a4 100644 --- a/core/selection.h +++ b/core/selection.h @@ -4,41 +4,28 @@ #ifndef SELECTION_H #define SELECTION_H +#include +#include + struct dive; extern int amount_selected; extern struct dive *current_dive; -/*** C and C++ functions ***/ - -#ifdef __cplusplus -extern "C" { -#endif - -extern struct dive *first_selected_dive(void); -extern struct dive *last_selected_dive(void); -extern bool consecutive_selected(void); +extern struct dive *first_selected_dive(); +extern struct dive *last_selected_dive(); +extern bool consecutive_selected(); extern void select_newest_visible_dive(); extern void select_single_dive(struct dive *d); // wrapper for setSelection() with a single dive. NULL clears the selection. extern void select_trip(struct dive_trip *trip); extern void deselect_trip(struct dive_trip *trip); extern struct dive_trip *single_selected_trip(); // returns trip if exactly one trip is selected, NULL otherwise. -extern void clear_selection(void); +extern void clear_selection(); #if DEBUG_SELECTION_TRACKING -extern void dump_selection(void); +extern void dump_selection(); #endif -#ifdef __cplusplus -} -#endif - -/*** C++-only functions ***/ - -#ifdef __cplusplus -#include -#include - // Reset the selection to the dives of the "selection" vector and send the appropriate signals. // Set the current dive to "currentDive" and the current dive computer to "currentDc". // "currentDive" must be an element of "selection" (or null if "seletion" is empty). @@ -64,6 +51,4 @@ std::vector getDiveSelection(); bool diveInSelection(const std::vector &selection, const dive *d); void updateSelection(std::vector &selection, const std::vector &add, const std::vector &remove); -#endif // __cplusplus - #endif // SELECTION_H diff --git a/core/serial_ftdi.c b/core/serial_ftdi.cpp similarity index 90% rename from core/serial_ftdi.c rename to core/serial_ftdi.cpp index 0b31c81d2..0be317f7f 100644 --- a/core/serial_ftdi.c +++ b/core/serial_ftdi.cpp @@ -22,7 +22,7 @@ * MA 02110-1301 USA */ -#include // malloc, free +#include #include // strerror #include // errno #include // gettimeofday @@ -49,15 +49,15 @@ #define VID 0x0403 // Vendor ID of FTDI -typedef struct ftdi_serial_t { +struct ftdi_serial_t { /* Library context. */ - dc_context_t *context; + dc_context_t *context = nullptr; /* * The file descriptor corresponding to the serial port. * Also a libftdi_ftdi_ctx could be used? */ - struct ftdi_context *ftdi_ctx; - long timeout; + struct ftdi_context *ftdi_ctx = nullptr; + long timeout = -1; // Default to blocking reads. /* * Serial port settings are saved into this variable immediately * after the port is opened. These settings are restored when the @@ -66,16 +66,21 @@ typedef struct ftdi_serial_t { * Custom implementation using libftdi functions could be done. */ - unsigned int baudrate; - unsigned int nbits; - unsigned int databits; - unsigned int stopbits; - unsigned int parity; -} ftdi_serial_t; + // Default to full-duplex. + unsigned int baudrate = 0; + unsigned int nbits = 0; + unsigned int databits = 0; + unsigned int stopbits = 0; + unsigned int parity = 0; + ~ftdi_serial_t() { + if (ftdi_ctx) + ftdi_free(ftdi_ctx); + } +}; static dc_status_t serial_ftdi_get_available (void *io, size_t *value) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -101,7 +106,7 @@ static dc_status_t serial_ftdi_get_transmitted (ftdi_serial_t *device) /* * Get an msec value on some random base */ -static unsigned int serial_ftdi_get_msec(void) +static unsigned int serial_ftdi_get_msec() { #ifdef _WIN32 return GetTickCount(); @@ -114,7 +119,7 @@ static unsigned int serial_ftdi_get_msec(void) static dc_status_t serial_ftdi_sleep (void *io, unsigned int timeout) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -173,17 +178,11 @@ static dc_status_t serial_ftdi_open (void **io, dc_context_t *context) { INFO("serial_ftdi_open called"); // Allocate memory. - ftdi_serial_t *device = (ftdi_serial_t *) malloc (sizeof (ftdi_serial_t)); - if (device == NULL) { - INFO("couldn't allocate memory"); - SYSERROR (errno); - return DC_STATUS_NOMEMORY; - } + auto device = std::make_unique(); INFO("setting up ftdi_ctx"); struct ftdi_context *ftdi_ctx = ftdi_new(); if (ftdi_ctx == NULL) { INFO("failed ftdi_new()"); - free(device); SYSERROR (errno); return DC_STATUS_NOMEMORY; } @@ -191,48 +190,34 @@ static dc_status_t serial_ftdi_open (void **io, dc_context_t *context) // Library context. //device->context = context; - // Default to blocking reads. - device->timeout = -1; - - // Default to full-duplex. - device->baudrate = 0; - device->nbits = 0; - device->databits = 0; - device->stopbits = 0; - device->parity = 0; - // Initialize device ftdi context INFO("initialize ftdi_ctx"); ftdi_init(ftdi_ctx); if (ftdi_set_interface(ftdi_ctx,INTERFACE_ANY)) { - free(device); ERROR ("%s", ftdi_get_error_string(ftdi_ctx)); return DC_STATUS_IO; } INFO("call serial_ftdi_open_device"); if (serial_ftdi_open_device(ftdi_ctx) < 0) { - free(device); ERROR ("%s", ftdi_get_error_string(ftdi_ctx)); return DC_STATUS_IO; } if (ftdi_usb_reset(ftdi_ctx)) { - free(device); ERROR ("%s", ftdi_get_error_string(ftdi_ctx)); return DC_STATUS_IO; } if (ftdi_usb_purge_buffers(ftdi_ctx)) { - free(device); ERROR ("%s", ftdi_get_error_string(ftdi_ctx)); return DC_STATUS_IO; } device->ftdi_ctx = ftdi_ctx; - *io = device; + *io = device.release(); return DC_STATUS_SUCCESS; } @@ -242,7 +227,7 @@ static dc_status_t serial_ftdi_open (void **io, dc_context_t *context) // static dc_status_t serial_ftdi_close (void *io) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_SUCCESS; @@ -254,13 +239,11 @@ static dc_status_t serial_ftdi_close (void *io) if (ret < 0) { ERROR ("Unable to close the ftdi device : %d (%s)", ret, ftdi_get_error_string(device->ftdi_ctx)); - return ret; + return (dc_status_t)ret; } - ftdi_free(device->ftdi_ctx); - // Free memory. - free (device); + delete device; return DC_STATUS_SUCCESS; } @@ -270,7 +253,7 @@ static dc_status_t serial_ftdi_close (void *io) // static dc_status_t serial_ftdi_configure (void *io, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -373,7 +356,7 @@ static dc_status_t serial_ftdi_configure (void *io, unsigned int baudrate, unsig // static dc_status_t serial_ftdi_set_timeout (void *io, int timeout) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -387,7 +370,7 @@ static dc_status_t serial_ftdi_set_timeout (void *io, int timeout) static dc_status_t serial_ftdi_read (void *io, void *data, size_t size, size_t *actual) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -396,7 +379,7 @@ static dc_status_t serial_ftdi_read (void *io, void *data, size_t size, size_t * long timeout = device->timeout; // Simulate blocking read as 10s timeout - if (timeout == -1) + if (timeout <= -1) timeout = 10000; unsigned int start_time = serial_ftdi_get_msec(); @@ -409,8 +392,8 @@ static dc_status_t serial_ftdi_read (void *io, void *data, size_t size, size_t * ERROR ("%s", ftdi_get_error_string(device->ftdi_ctx)); return DC_STATUS_IO; //Error during read call. } else if (n == 0) { - if (serial_ftdi_get_msec() - start_time > timeout) { - ERROR("FTDI read timed out."); + if (serial_ftdi_get_msec() - start_time > (unsigned int)timeout) { + ERROR("%s", "FTDI read timed out."); return DC_STATUS_TIMEOUT; } serial_ftdi_sleep (device, 1); @@ -429,7 +412,7 @@ static dc_status_t serial_ftdi_read (void *io, void *data, size_t size, size_t * static dc_status_t serial_ftdi_write (void *io, const void *data, size_t size, size_t *actual) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -460,7 +443,7 @@ static dc_status_t serial_ftdi_write (void *io, const void *data, size_t size, s static dc_status_t serial_ftdi_purge (void *io, dc_direction_t queue) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -497,14 +480,16 @@ static dc_status_t serial_ftdi_purge (void *io, dc_direction_t queue) static dc_status_t serial_ftdi_set_break (void *io, unsigned int level) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; INFO ("Break: value=%i", level); - if (ftdi_set_line_property2(device->ftdi_ctx, device->databits, device->stopbits, device->parity, level)) { + if (ftdi_set_line_property2(device->ftdi_ctx, (ftdi_bits_type)device->databits, + (ftdi_stopbits_type)device->stopbits, (ftdi_parity_type)device->parity, + (ftdi_break_type)level)) { ERROR ("%s", ftdi_get_error_string(device->ftdi_ctx)); return DC_STATUS_IO; } @@ -514,7 +499,7 @@ static dc_status_t serial_ftdi_set_break (void *io, unsigned int level) static dc_status_t serial_ftdi_set_dtr (void *io, unsigned int value) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -531,7 +516,7 @@ static dc_status_t serial_ftdi_set_dtr (void *io, unsigned int value) static dc_status_t serial_ftdi_set_rts (void *io, unsigned int level) { - ftdi_serial_t *device = io; + ftdi_serial_t *device = (ftdi_serial_t *)io; if (device == NULL) return DC_STATUS_INVALIDARGS; diff --git a/core/settings/qPrefCloudStorage.cpp b/core/settings/qPrefCloudStorage.cpp index d08b80ce3..bba272b76 100644 --- a/core/settings/qPrefCloudStorage.cpp +++ b/core/settings/qPrefCloudStorage.cpp @@ -27,11 +27,10 @@ HANDLE_PREFERENCE_BOOL(CloudStorage, "cloud_auto_sync", cloud_auto_sync); void qPrefCloudStorage::set_cloud_base_url(const QString &value) { - if (value != prefs.cloud_base_url) { + if (value.toStdString() != prefs.cloud_base_url) { // only free and set if not default - if (prefs.cloud_base_url != default_prefs.cloud_base_url) { - qPrefPrivate::copy_txt(&prefs.cloud_base_url, value); - } + if (prefs.cloud_base_url != default_prefs.cloud_base_url) + prefs.cloud_base_url = value.toStdString(); disk_cloud_base_url(true); emit instance()->cloud_base_urlChanged(value); @@ -42,14 +41,15 @@ void qPrefCloudStorage::store_cloud_base_url(const QString &value) { // this is used if we want to update the on-disk settings without changing // the runtime preference - qPrefPrivate::propSetValue(keyFromGroupAndName(group, "cloud_base_url"), value, default_prefs.cloud_base_url); + qPrefPrivate::propSetValue(keyFromGroupAndName(group, "cloud_base_url"), value, QString::fromStdString(default_prefs.cloud_base_url)); } void qPrefCloudStorage::disk_cloud_base_url(bool doSync) { // we don't allow to automatically write back the prefs value for the cloud_base_url. // in order to do that you need to use the explicit function above store_cloud_base_url() if (!doSync) - prefs.cloud_base_url = copy_qstring(qPrefPrivate::propValue(keyFromGroupAndName(group, "cloud_base_url"), default_prefs.cloud_base_url).toString()); + prefs.cloud_base_url = qPrefPrivate::propValue(keyFromGroupAndName(group, "cloud_base_url"), + QString::fromStdString(default_prefs.cloud_base_url)).toString().toStdString(); } HANDLE_PREFERENCE_TXT(CloudStorage, "email", cloud_storage_email); @@ -58,8 +58,8 @@ HANDLE_PREFERENCE_TXT(CloudStorage, "email_encoded", cloud_storage_email_encoded void qPrefCloudStorage::set_cloud_storage_password(const QString &value) { - if (value != prefs.cloud_storage_password) { - qPrefPrivate::copy_txt(&prefs.cloud_storage_password, value); + if (value.toStdString() != prefs.cloud_storage_password) { + prefs.cloud_storage_password = value.toStdString(); disk_cloud_storage_password(true); emit instance()->cloud_storage_passwordChanged(value); } @@ -68,9 +68,11 @@ void qPrefCloudStorage::disk_cloud_storage_password(bool doSync) { if (doSync) { if (prefs.save_password_local) - qPrefPrivate::propSetValue(keyFromGroupAndName(group, "password"), prefs.cloud_storage_password, default_prefs.cloud_storage_password); + qPrefPrivate::propSetValue(keyFromGroupAndName(group, "password"), prefs.cloud_storage_password, + default_prefs.cloud_storage_password); } else { - prefs.cloud_storage_password = copy_qstring(qPrefPrivate::propValue(keyFromGroupAndName(group, "password"), default_prefs.cloud_storage_password).toString()); + prefs.cloud_storage_password = qPrefPrivate::propValue(keyFromGroupAndName(group, "password"), + default_prefs.cloud_storage_password).toString().toStdString(); } } @@ -84,17 +86,17 @@ HANDLE_PREFERENCE_BOOL(CloudStorage, "save_password_local", save_password_local) QString qPrefCloudStorage::diveshare_uid() { - return qPrefPrivate::propValue(keyFromGroupAndName("", "diveshareExport/uid"), "").toString(); + return qPrefPrivate::propValue(keyFromGroupAndName("", "diveshareExport/uid"), QString()).toString(); } void qPrefCloudStorage::set_diveshare_uid(const QString &value) { - qPrefPrivate::propSetValue(keyFromGroupAndName("", "diveshareExport/uid"), value, ""); + qPrefPrivate::propSetValue(keyFromGroupAndName("", "diveshareExport/uid"), value, QString()); emit instance()->diveshare_uidChanged(value); } bool qPrefCloudStorage::diveshare_private() { - return qPrefPrivate::propValue(keyFromGroupAndName("", "diveshareExport/private"), "").toBool(); + return qPrefPrivate::propValue(keyFromGroupAndName("", "diveshareExport/private"), QString()).toBool(); } void qPrefCloudStorage::set_diveshare_private(bool value) { @@ -104,21 +106,20 @@ void qPrefCloudStorage::set_diveshare_private(bool value) QString qPrefCloudStorage::divelogde_user() { - return qPrefPrivate::propValue(keyFromGroupAndName("", "divelogde_user"), "").toString(); + return qPrefPrivate::propValue(keyFromGroupAndName("", "divelogde_user"), QString()).toString(); } void qPrefCloudStorage::set_divelogde_user(const QString &value) { - qPrefPrivate::propSetValue(keyFromGroupAndName("", "divelogde_user"), value, ""); + qPrefPrivate::propSetValue(keyFromGroupAndName("", "divelogde_user"), value, QString()); emit instance()->divelogde_userChanged(value); } - QString qPrefCloudStorage::divelogde_pass() { - return qPrefPrivate::propValue(keyFromGroupAndName("", "divelogde_pass"), "").toString(); + return qPrefPrivate::propValue(keyFromGroupAndName("", "divelogde_pass"), QString()).toString(); } void qPrefCloudStorage::set_divelogde_pass(const QString &value) { - qPrefPrivate::propSetValue(keyFromGroupAndName("", "divelogde_pass"), value, ""); + qPrefPrivate::propSetValue(keyFromGroupAndName("", "divelogde_pass"), value, QString()); emit instance()->divelogde_passChanged(value); } diff --git a/core/settings/qPrefCloudStorage.h b/core/settings/qPrefCloudStorage.h index 288b6e37e..3f54facc8 100644 --- a/core/settings/qPrefCloudStorage.h +++ b/core/settings/qPrefCloudStorage.h @@ -41,11 +41,11 @@ public: Q_ENUM(cloud_status); static bool cloud_auto_sync() { return prefs.cloud_auto_sync; } - static QString cloud_base_url() { return prefs.cloud_base_url; } - static QString cloud_storage_email() { return prefs.cloud_storage_email; } - static QString cloud_storage_email_encoded() { return prefs.cloud_storage_email_encoded; } - static QString cloud_storage_password() { return prefs.cloud_storage_password; } - static QString cloud_storage_pin() { return prefs.cloud_storage_pin; } + static QString cloud_base_url() { return QString::fromStdString(prefs.cloud_base_url); } + static QString cloud_storage_email() { return QString::fromStdString(prefs.cloud_storage_email); } + static QString cloud_storage_email_encoded() { return QString::fromStdString(prefs.cloud_storage_email_encoded); } + static QString cloud_storage_password() { return QString::fromStdString(prefs.cloud_storage_password); } + static QString cloud_storage_pin() { return QString::fromStdString(prefs.cloud_storage_pin); } static int cloud_timeout() { return prefs.cloud_timeout; } static int cloud_verification_status() { return prefs.cloud_verification_status; } static bool save_password_local() { return prefs.save_password_local; } diff --git a/core/settings/qPrefDisplay.cpp b/core/settings/qPrefDisplay.cpp index 4e8b83d62..7eeb98e06 100644 --- a/core/settings/qPrefDisplay.cpp +++ b/core/settings/qPrefDisplay.cpp @@ -77,9 +77,9 @@ void qPrefDisplay::set_divelist_font(const QString &value) if (value.contains(",")) newValue = value.left(value.indexOf(",")); - if (newValue != prefs.divelist_font && - !subsurface_ignore_font(qPrintable(newValue))) { - qPrefPrivate::copy_txt(&prefs.divelist_font, value); + if (newValue.toStdString() != prefs.divelist_font && + !subsurface_ignore_font(newValue.toStdString())) { + prefs.divelist_font = value.toStdString(); disk_divelist_font(true); qApp->setFont(QFont(newValue)); @@ -170,12 +170,10 @@ void qPrefDisplay::setCorrectFont() QString fontName = defaultFont.toString(); if (fontName.contains(",")) fontName = fontName.left(fontName.indexOf(",")); - if (subsurface_ignore_font(qPrintable(fontName))) { - defaultFont = QFont(prefs.divelist_font); - } else { - free((void *)prefs.divelist_font); - prefs.divelist_font = copy_qstring(fontName); - } + if (subsurface_ignore_font(fontName.toStdString())) + defaultFont = QFont(QString::fromStdString(prefs.divelist_font)); + else + prefs.divelist_font = fontName.toStdString(); defaultFont.setPointSizeF(prefs.font_size * prefs.mobile_scale); qApp->setFont(defaultFont); diff --git a/core/settings/qPrefDisplay.h b/core/settings/qPrefDisplay.h index 91311cd01..bb1e96a76 100644 --- a/core/settings/qPrefDisplay.h +++ b/core/settings/qPrefDisplay.h @@ -38,7 +38,7 @@ public: public: static int animation_speed() { return prefs.animation_speed; } - static QString divelist_font() { return prefs.divelist_font; } + static QString divelist_font() { return QString::fromStdString(prefs.divelist_font); } static double font_size() { return prefs.font_size; } static double mobile_scale() { return prefs.mobile_scale; } static bool display_invalid_dives() { return prefs.display_invalid_dives; } diff --git a/core/settings/qPrefDiveComputer.h b/core/settings/qPrefDiveComputer.h index 644a547e9..c3baddeff 100644 --- a/core/settings/qPrefDiveComputer.h +++ b/core/settings/qPrefDiveComputer.h @@ -6,11 +6,11 @@ #include #define IMPLEMENT5GETTERS(name) \ - static QString name() { return prefs.dive_computer.name; } \ - static QString name##1() { return prefs.dive_computer##1 .name; } \ - static QString name##2() { return prefs.dive_computer##2 .name; } \ - static QString name##3() { return prefs.dive_computer##3 .name; } \ - static QString name##4() { return prefs.dive_computer##4 .name; } + static QString name() { return QString::fromStdString(prefs.dive_computer.name); } \ + static QString name##1() { return QString::fromStdString(prefs.dive_computer##1 .name); } \ + static QString name##2() { return QString::fromStdString(prefs.dive_computer##2 .name); } \ + static QString name##3() { return QString::fromStdString(prefs.dive_computer##3 .name); } \ + static QString name##4() { return QString::fromStdString(prefs.dive_computer##4 .name); } class qPrefDiveComputer : public QObject { Q_OBJECT diff --git a/core/settings/qPrefEquipment.h b/core/settings/qPrefEquipment.h index e3a0713cc..5b9cdc54c 100644 --- a/core/settings/qPrefEquipment.h +++ b/core/settings/qPrefEquipment.h @@ -20,7 +20,7 @@ public: static void sync() { loadSync(true); } public: - static QString default_cylinder() { return prefs.default_cylinder; } + static QString default_cylinder() { return QString::fromStdString(prefs.default_cylinder); } static bool include_unused_tanks() { return prefs.include_unused_tanks; } static bool display_default_tank_infos() { return prefs.display_default_tank_infos; } diff --git a/core/settings/qPrefLanguage.h b/core/settings/qPrefLanguage.h index 9eb17c36f..0d6f16278 100644 --- a/core/settings/qPrefLanguage.h +++ b/core/settings/qPrefLanguage.h @@ -25,12 +25,12 @@ public: static void sync() { loadSync(true); } public: - static const QString date_format() { return prefs.date_format; } + static const QString date_format() { return QString::fromStdString(prefs.date_format); } static bool date_format_override() { return prefs.date_format_override; } - static const QString date_format_short() { return prefs.date_format_short; } - static const QString language() { return prefs.locale.language; } - static const QString lang_locale() { return prefs.locale.lang_locale; } - static const QString time_format() { return prefs.time_format; } + static const QString date_format_short() { return QString::fromStdString(prefs.date_format_short); } + static const QString language() { return QString::fromStdString(prefs.locale.language); } + static const QString lang_locale() { return QString::fromStdString(prefs.locale.lang_locale); } + static const QString time_format() { return QString::fromStdString(prefs.time_format); } static bool time_format_override() { return prefs.time_format_override; } static bool use_system_language() { return prefs.locale.use_system_language; } diff --git a/core/settings/qPrefLog.cpp b/core/settings/qPrefLog.cpp index 97d400e3c..0c7b17613 100644 --- a/core/settings/qPrefLog.cpp +++ b/core/settings/qPrefLog.cpp @@ -28,7 +28,7 @@ void qPrefLog::set_default_file_behavior(enum def_file_behavior value) if (value == UNDEFINED_DEFAULT_FILE) { // undefined, so check if there's a filename set and // use that, otherwise go with no default file - prefs.default_file_behavior = QString(prefs.default_filename).isEmpty() ? NO_DEFAULT_FILE : LOCAL_DEFAULT_FILE; + prefs.default_file_behavior = prefs.default_filename.empty() ? NO_DEFAULT_FILE : LOCAL_DEFAULT_FILE; } else { prefs.default_file_behavior = value; } @@ -45,7 +45,7 @@ void qPrefLog::disk_default_file_behavior(bool doSync) if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) // undefined, so check if there's a filename set and // use that, otherwise go with no default file - prefs.default_file_behavior = QString(prefs.default_filename).isEmpty() ? NO_DEFAULT_FILE : LOCAL_DEFAULT_FILE; + prefs.default_file_behavior = prefs.default_filename.empty() ? NO_DEFAULT_FILE : LOCAL_DEFAULT_FILE; } } @@ -58,4 +58,3 @@ HANDLE_PREFERENCE_BOOL(Log, "use_default_file", use_default_file); HANDLE_PREFERENCE_BOOL(Log, "salinityEditDefault", salinityEditDefault); HANDLE_PREFERENCE_BOOL(Log, "show_average_depth", show_average_depth); - diff --git a/core/settings/qPrefLog.h b/core/settings/qPrefLog.h index 943e43e80..9ce49a719 100644 --- a/core/settings/qPrefLog.h +++ b/core/settings/qPrefLog.h @@ -23,7 +23,7 @@ public: static void sync() { return loadSync(true); } public: - static QString default_filename() { return prefs.default_filename; } + static QString default_filename() { return QString::fromStdString(prefs.default_filename); } static enum def_file_behavior default_file_behavior() { return prefs.default_file_behavior; } static bool use_default_file() { return prefs.use_default_file; } static bool extraEnvironmentalDefault() { return prefs.extraEnvironmentalDefault; } diff --git a/core/settings/qPrefMedia.cpp b/core/settings/qPrefMedia.cpp index 60e68f3d9..6d86c441c 100644 --- a/core/settings/qPrefMedia.cpp +++ b/core/settings/qPrefMedia.cpp @@ -23,4 +23,3 @@ HANDLE_PREFERENCE_BOOL(Media, "auto_recalculate_thumbnails", auto_recalculate_th HANDLE_PREFERENCE_BOOL(Media, "extract_video_thumbnails", extract_video_thumbnails); HANDLE_PREFERENCE_INT(Media, "extract_video_thumbnails_position", extract_video_thumbnails_position); HANDLE_PREFERENCE_TXT(Media, "ffmpeg_executable", ffmpeg_executable); - diff --git a/core/settings/qPrefMedia.h b/core/settings/qPrefMedia.h index fab87f250..66d538cbd 100644 --- a/core/settings/qPrefMedia.h +++ b/core/settings/qPrefMedia.h @@ -24,7 +24,7 @@ public: static bool auto_recalculate_thumbnails() { return prefs.auto_recalculate_thumbnails; } static bool extract_video_thumbnails() { return prefs.extract_video_thumbnails; } static int extract_video_thumbnails_position() { return prefs.extract_video_thumbnails_position; } - static QString ffmpeg_executable() { return prefs.ffmpeg_executable; } + static QString ffmpeg_executable() { return QString::fromStdString(prefs.ffmpeg_executable); } public slots: static void set_auto_recalculate_thumbnails(bool value); diff --git a/core/settings/qPrefPrivate.cpp b/core/settings/qPrefPrivate.cpp index a5a67be0d..b7a0bd6cc 100644 --- a/core/settings/qPrefPrivate.cpp +++ b/core/settings/qPrefPrivate.cpp @@ -4,12 +4,6 @@ #include -void qPrefPrivate::copy_txt(const char **name, const QString &string) -{ - free((void *)*name); - *name = copy_qstring(string); -} - QString keyFromGroupAndName(QString group, QString name) { QString slash = (group.endsWith('/') || name.startsWith('/')) ? "" : "/"; @@ -35,8 +29,19 @@ void qPrefPrivate::propSetValue(const QString &key, const QVariant &value, const s.remove(key); } +void qPrefPrivate::propSetValue(const QString &key, const std::string &value, const std::string &defaultValue) +{ + propSetValue(key, QString::fromStdString(value), QString::fromStdString(defaultValue)); +} + QVariant qPrefPrivate::propValue(const QString &key, const QVariant &defaultValue) { QSettings s; return s.value(key, defaultValue); } + +QVariant qPrefPrivate::propValue(const QString &key, const std::string &defaultValue) +{ + QSettings s; + return s.value(key, QString::fromStdString(defaultValue)); +} diff --git a/core/settings/qPrefPrivate.h b/core/settings/qPrefPrivate.h index e78ca0c72..3798c717b 100644 --- a/core/settings/qPrefPrivate.h +++ b/core/settings/qPrefPrivate.h @@ -16,10 +16,10 @@ class qPrefPrivate { public: // Helper functions - static void copy_txt(const char **name, const QString &string); - static void propSetValue(const QString &key, const QVariant &value, const QVariant &defaultValue); + static void propSetValue(const QString &key, const std::string &value, const std::string &defaultValue); static QVariant propValue(const QString &key, const QVariant &defaultValue); + static QVariant propValue(const QString &key, const std::string &defaultValue); private: qPrefPrivate() {} @@ -134,29 +134,29 @@ extern QString keyFromGroupAndName(QString group, QString name); #define DISK_LOADSYNC_TXT_EXT(usegroup, name, field, usestruct) \ void qPref##usegroup::disk_##field(bool doSync) \ { \ - static QString current_state; \ + static std::string current_state; \ if (doSync) { \ - if (current_state != QString(prefs.usestruct field)) { \ - current_state = QString(prefs.usestruct field); \ - qPrefPrivate::propSetValue(keyFromGroupAndName(group, name), prefs.usestruct field, default_prefs.usestruct field); \ + if (current_state != prefs.usestruct field) { \ + current_state = prefs.usestruct field; \ + qPrefPrivate::propSetValue(keyFromGroupAndName(group, name), QString::fromStdString(prefs.usestruct field), QString::fromStdString(default_prefs.usestruct field)); \ } \ } else { \ - prefs.usestruct field = copy_qstring(qPrefPrivate::propValue(keyFromGroupAndName(group, name), default_prefs.usestruct field).toString()); \ - current_state = QString(prefs.usestruct field); \ + prefs.usestruct field = qPrefPrivate::propValue(keyFromGroupAndName(group, name), QString::fromStdString(default_prefs.usestruct field)).toString().toStdString(); \ + current_state = prefs.usestruct field; \ } \ } #define DISK_LOADSYNC_TXT_EXT_ALT(usegroup, name, field, usestruct, alt) \ void qPref##usegroup::disk_##field##alt(bool doSync) \ { \ - static QString current_state; \ + static std::string current_state; \ if (doSync) { \ - if (current_state != QString(prefs.usestruct ## alt .field)) { \ - current_state = QString(prefs.usestruct ## alt .field); \ - qPrefPrivate::propSetValue(keyFromGroupAndName(group, name), prefs.usestruct ##alt .field, default_prefs.usestruct ##alt .field); \ + if (current_state != prefs.usestruct ## alt .field) { \ + current_state = prefs.usestruct ## alt .field; \ + qPrefPrivate::propSetValue(keyFromGroupAndName(group, name), QString::fromStdString(prefs.usestruct ##alt .field), QString::fromStdString(default_prefs.usestruct ##alt .field)); \ } \ } else { \ - prefs.usestruct ##alt .field = copy_qstring(qPrefPrivate::propValue(keyFromGroupAndName(group, name), default_prefs.usestruct ##alt .field).toString()); \ - current_state = QString(prefs.usestruct ##alt .field); \ + prefs.usestruct ##alt .field = qPrefPrivate::propValue(keyFromGroupAndName(group, name), QString::fromStdString(default_prefs.usestruct ##alt .field)).toString().toStdString(); \ + current_state = prefs.usestruct ##alt .field; \ } \ } #define DISK_LOADSYNC_TXT(usegroup, name, field) \ @@ -226,8 +226,8 @@ extern QString keyFromGroupAndName(QString group, QString name); #define SET_PREFERENCE_TXT_EXT(usegroup, field, usestruct) \ void qPref##usegroup::set_##field(const QString &value) \ { \ - if (value != prefs.usestruct field) { \ - qPrefPrivate::copy_txt(&prefs.usestruct field, value); \ + if (value.toStdString() != prefs.usestruct field) { \ + prefs.usestruct field = value.toStdString(); \ disk_##field(true); \ emit instance()->field##Changed(value); \ } \ @@ -236,8 +236,8 @@ extern QString keyFromGroupAndName(QString group, QString name); #define SET_PREFERENCE_TXT_EXT_ALT(usegroup, field, usestruct, alt) \ void qPref##usegroup::set_##field##alt(const QString &value) \ { \ - if (value != prefs.usestruct ##alt .field) { \ - qPrefPrivate::copy_txt(&prefs.usestruct ##alt .field, value); \ + if (value.toStdString() != prefs.usestruct ##alt .field) { \ + prefs.usestruct ##alt .field = value.toStdString(); \ disk_##field##alt(true); \ emit instance()->field##alt##Changed(value); \ } \ diff --git a/core/settings/qPrefProxy.h b/core/settings/qPrefProxy.h index d5328d0d5..628cdbbc1 100644 --- a/core/settings/qPrefProxy.h +++ b/core/settings/qPrefProxy.h @@ -25,11 +25,11 @@ public: public: static bool proxy_auth() { return prefs.proxy_auth; } - static QString proxy_host() { return prefs.proxy_host; } - static QString proxy_pass() { return prefs.proxy_pass; } + static QString proxy_host() { return QString::fromStdString(prefs.proxy_host); } + static QString proxy_pass() { return QString::fromStdString(prefs.proxy_pass); } static int proxy_port() { return prefs.proxy_port; } static int proxy_type() { return prefs.proxy_type; } - static QString proxy_user() { return prefs.proxy_user; } + static QString proxy_user() { return QString::fromStdString(prefs.proxy_user); } public slots: static void set_proxy_auth(bool value); diff --git a/core/settings/qPrefTechnicalDetails.cpp b/core/settings/qPrefTechnicalDetails.cpp index 1379b1f5d..9bccd2a82 100644 --- a/core/settings/qPrefTechnicalDetails.cpp +++ b/core/settings/qPrefTechnicalDetails.cpp @@ -14,6 +14,7 @@ qPrefTechnicalDetails *qPrefTechnicalDetails::instance() void qPrefTechnicalDetails::loadSync(bool doSync) { + disk_allowOcGasAsDiluent(doSync); disk_calcalltissues(doSync); disk_calcceiling(doSync); disk_calcceiling3m(doSync); @@ -100,6 +101,8 @@ void qPrefTechnicalDetails::disk_gflow(bool doSync) } } +HANDLE_PREFERENCE_BOOL(TechnicalDetails, "allowOcGasAsDiluent", allowOcGasAsDiluent); + HANDLE_PREFERENCE_BOOL(TechnicalDetails, "gf_low_at_maxdepth", gf_low_at_maxdepth); HANDLE_PREFERENCE_BOOL(TechnicalDetails, "InfoBox", infobox); diff --git a/core/settings/qPrefTechnicalDetails.h b/core/settings/qPrefTechnicalDetails.h index 63da6499d..3a3864c79 100644 --- a/core/settings/qPrefTechnicalDetails.h +++ b/core/settings/qPrefTechnicalDetails.h @@ -7,6 +7,7 @@ class qPrefTechnicalDetails : public QObject { Q_OBJECT + Q_PROPERTY(bool allowOcGasAsDiluent READ allowOcGasAsDiluent WRITE set_allowOcGasAsDiluent NOTIFY allowOcGasAsDiluentChanged) Q_PROPERTY(bool calcalltissues READ calcalltissues WRITE set_calcalltissues NOTIFY calcalltissuesChanged) Q_PROPERTY(bool calcceiling READ calcceiling WRITE set_calcceiling NOTIFY calcceilingChanged) Q_PROPERTY(bool calcceiling3m READ calcceiling3m WRITE set_calcceiling3m NOTIFY calcceiling3mChanged) @@ -44,6 +45,7 @@ public: static void sync() { loadSync(true); } public: + static bool allowOcGasAsDiluent() { return prefs.allowOcGasAsDiluent; } static bool calcalltissues() { return prefs.calcalltissues; } static bool calcceiling() { return prefs.calcceiling; } static bool calcceiling3m() { return prefs.calcceiling3m; } @@ -73,6 +75,7 @@ public: static bool infobox() { return prefs.infobox; } public slots: + static void set_allowOcGasAsDiluent(bool value); static void set_calcalltissues(bool value); static void set_calcceiling(bool value); static void set_calcceiling3m(bool value); @@ -102,6 +105,7 @@ public slots: static void set_infobox(bool value); signals: + void allowOcGasAsDiluentChanged(bool value); void calcalltissuesChanged(bool value); void calcceilingChanged(bool value); void calcceiling3mChanged(bool value); @@ -133,6 +137,7 @@ signals: private: qPrefTechnicalDetails() {} + static void disk_allowOcGasAsDiluent(bool doSync); static void disk_calcalltissues(bool doSync); static void disk_calcceiling(bool doSync); static void disk_calcceiling3m(bool doSync); diff --git a/core/settings/qPrefUpdateManager.cpp b/core/settings/qPrefUpdateManager.cpp index deee1c890..1989ffc86 100644 --- a/core/settings/qPrefUpdateManager.cpp +++ b/core/settings/qPrefUpdateManager.cpp @@ -41,7 +41,7 @@ void qPrefUpdateManager::disk_next_check(bool doSync) if (doSync) qPrefPrivate::propSetValue(keyFromGroupAndName(group, "NextCheck"), prefs.update_manager.next_check, default_prefs.update_manager.next_check); else - prefs.update_manager.next_check = qPrefPrivate::propValue(keyFromGroupAndName(group, "NextCheck"), 0).toInt(); + prefs.update_manager.next_check = qPrefPrivate::propValue(keyFromGroupAndName(group, "NextCheck"), QVariant(0)).toInt(); } HANDLE_PROP_QSTRING(UpdateManager, "UpdateManager/UUID", uuidString); diff --git a/core/settings/qPrefUpdateManager.h b/core/settings/qPrefUpdateManager.h index 15bcd3c6e..aa8d07b87 100644 --- a/core/settings/qPrefUpdateManager.h +++ b/core/settings/qPrefUpdateManager.h @@ -23,7 +23,7 @@ public: public: static bool dont_check_for_updates() { return prefs.update_manager.dont_check_for_updates; } - static const QString last_version_used() { return prefs.update_manager.last_version_used; } + static const QString last_version_used() { return QString::fromStdString(prefs.update_manager.last_version_used); } static const QDate next_check() { return QDate::fromJulianDay(prefs.update_manager.next_check); } static const QString uuidString() { return st_uuidString; } diff --git a/core/sha1.c b/core/sha1.cpp similarity index 87% rename from core/sha1.c rename to core/sha1.cpp index acf8c5d9f..04bf13b6e 100644 --- a/core/sha1.c +++ b/core/sha1.cpp @@ -8,7 +8,6 @@ /* this is only to get definitions for memcpy(), ntohl() and htonl() */ #include -#include #ifdef WIN32 #include #else @@ -132,16 +131,16 @@ #define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B &C) + (D &(B ^ C))), 0x8f1bbcdc, A, B, C, D, E) #define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B ^ C ^ D), 0xca62c1d6, A, B, C, D, E) -static void blk_SHA1_Block(blk_SHA_CTX *ctx, const void *block) +static void blk_SHA1_Block(unsigned int H[], const void *block) { unsigned int A, B, C, D, E; unsigned int array[16]; - A = ctx->H[0]; - B = ctx->H[1]; - C = ctx->H[2]; - D = ctx->H[3]; - E = ctx->H[4]; + A = H[0]; + B = H[1]; + C = H[2]; + D = H[3]; + E = H[4]; /* Round 1 - iterations 0-16 take their input from 'block' */ T_0_15(0, A, B, C, D, E); @@ -233,68 +232,84 @@ static void blk_SHA1_Block(blk_SHA_CTX *ctx, const void *block) T_60_79(78, C, D, E, A, B); T_60_79(79, B, C, D, E, A); - ctx->H[0] += A; - ctx->H[1] += B; - ctx->H[2] += C; - ctx->H[3] += D; - ctx->H[4] += E; + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; } -void blk_SHA1_Init(blk_SHA_CTX *ctx) -{ - ctx->size = 0; - +SHA1::SHA1() : + size(0), /* Initialize H with the magic constants (see FIPS180 for constants) */ - ctx->H[0] = 0x67452301; - ctx->H[1] = 0xefcdab89; - ctx->H[2] = 0x98badcfe; - ctx->H[3] = 0x10325476; - ctx->H[4] = 0xc3d2e1f0; + H { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 } +{ } -void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) +void SHA1::update(const void *data, unsigned long len) { - unsigned int lenW = ctx->size & 63; + unsigned int lenW = size & 63; - ctx->size += len; + size += len; /* Read the data into W and process blocks as they get full */ if (lenW) { unsigned int left = 64 - lenW; if (len < left) left = len; - memcpy(lenW + (char *)ctx->W, data, left); + memcpy(lenW + (char *)W, data, left); lenW = (lenW + left) & 63; len -= left; data = ((const char *)data + left); if (lenW) return; - blk_SHA1_Block(ctx, ctx->W); + blk_SHA1_Block(H, W); } while (len >= 64) { - blk_SHA1_Block(ctx, data); + blk_SHA1_Block(H, data); data = ((const char *)data + 64); len -= 64; } if (len) - memcpy(ctx->W, data, len); + memcpy(W, data, len); } -void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx) +void SHA1::update(const std::string &s) { + update(s.data(), s.size()); +} + +std::array SHA1::hash() +{ + std::array hashout; static const unsigned char pad[64] = { 0x80 }; unsigned int padlen[2]; int i; /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ - padlen[0] = htonl((uint32_t)(ctx->size >> 29)); - padlen[1] = htonl((uint32_t)(ctx->size << 3)); + padlen[0] = htonl((uint32_t)(size >> 29)); + padlen[1] = htonl((uint32_t)(size << 3)); - i = ctx->size & 63; - blk_SHA1_Update(ctx, pad, 1 + (63 & (55 - i))); - blk_SHA1_Update(ctx, padlen, 8); + i = size & 63; + update(pad, 1 + (63 & (55 - i))); + update(padlen, 8); /* Output hash */ for (i = 0; i < 5; i++) - put_be32(hashout + i * 4, ctx->H[i]); + put_be32(&hashout[i * 4], H[i]); + return hashout; +} + +uint32_t SHA1::hash_uint32() +{ + auto hashout = hash(); + return (hashout[0] << 0) | (hashout[1] << 8) | + (hashout[2] << 16) | (hashout[3] << 24); +} + +uint32_t SHA1_uint32(const void *dataIn, unsigned long len) +{ + SHA1 sha; + sha.update(dataIn, len); + return sha.hash_uint32(); } diff --git a/core/sha1.h b/core/sha1.h index 8a176f5e5..f6411fd44 100644 --- a/core/sha1.h +++ b/core/sha1.h @@ -8,39 +8,25 @@ #ifndef SHA1_H #define SHA1_H -#ifdef __cplusplus -extern "C" { -#endif +#include +#include -typedef struct +struct SHA1 { + SHA1(); + void update(const void *dataIn, unsigned long len); + void update(const std::string &s); + // Note: the hash() functions change state. Call only once. + std::array hash(); + uint32_t hash_uint32(); // Return first 4 bytes of hash interpreted + // as little-endian unsigned integer. +private: unsigned long long size; unsigned int H[5]; unsigned int W[16]; -} blk_SHA_CTX; +}; -void blk_SHA1_Init(blk_SHA_CTX *ctx); -void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); -void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); - -/* Make us use the standard names */ -#define SHA_CTX blk_SHA_CTX -#define SHA1_Init blk_SHA1_Init -#define SHA1_Update blk_SHA1_Update -#define SHA1_Final blk_SHA1_Final - -/* Trivial helper function */ -static inline void SHA1(const void *dataIn, unsigned long len, unsigned char hashout[20]) -{ - SHA_CTX ctx; - - SHA1_Init(&ctx); - SHA1_Update(&ctx, dataIn, len); - SHA1_Final(hashout, &ctx); -} - -#ifdef __cplusplus -} -#endif +/* Helper function that calculates an SHA1 has and returns the first 4 bytes as uint32_t */ +uint32_t SHA1_uint32(const void *dataIn, unsigned long len); #endif // SHA1_H diff --git a/core/ssrf.h b/core/ssrf.h deleted file mode 100644 index 7f65f2dc5..000000000 --- a/core/ssrf.h +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef SSRF_H -#define SSRF_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __clang__ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#endif - -#ifdef __cplusplus -} -#else - -// Macro to be used for silencing unused parameters -#define UNUSED(x) (void)x -#endif - -#endif // SSRF_H diff --git a/core/statistics.c b/core/statistics.c deleted file mode 100644 index aa9d27c96..000000000 --- a/core/statistics.c +++ /dev/null @@ -1,415 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* statistics.c - * - * core logic for the Info & Stats page - - * void calculate_stats_summary(struct stats_summary *out, bool selected_only); - * void calculate_stats_selected(stats_t *stats_selection); - */ - -#include "statistics.h" -#include "dive.h" -#include "divelog.h" -#include "event.h" -#include "gettext.h" -#include "sample.h" -#include "subsurface-time.h" -#include "trip.h" -#include "units.h" - -#include -#include -#include - -static void process_temperatures(struct dive *dp, stats_t *stats) -{ - temperature_t min_temp, mean_temp, max_temp = {.mkelvin = 0}; - - max_temp.mkelvin = dp->maxtemp.mkelvin; - if (max_temp.mkelvin && (!stats->max_temp.mkelvin || max_temp.mkelvin > stats->max_temp.mkelvin)) - stats->max_temp.mkelvin = max_temp.mkelvin; - - min_temp.mkelvin = dp->mintemp.mkelvin; - if (min_temp.mkelvin && (!stats->min_temp.mkelvin || min_temp.mkelvin < stats->min_temp.mkelvin)) - stats->min_temp.mkelvin = min_temp.mkelvin; - - if (min_temp.mkelvin || max_temp.mkelvin) { - mean_temp.mkelvin = min_temp.mkelvin; - if (mean_temp.mkelvin) - mean_temp.mkelvin = (mean_temp.mkelvin + max_temp.mkelvin) / 2; - else - mean_temp.mkelvin = max_temp.mkelvin; - stats->combined_temp.mkelvin += mean_temp.mkelvin; - stats->combined_count++; - } -} - -static void process_dive(struct dive *dive, stats_t *stats) -{ - int old_tadt, sac_time = 0; - int32_t duration = dive->duration.seconds; - - old_tadt = stats->total_average_depth_time.seconds; - stats->total_time.seconds += duration; - if (duration > stats->longest_time.seconds) - stats->longest_time.seconds = duration; - if (stats->shortest_time.seconds == 0 || duration < stats->shortest_time.seconds) - stats->shortest_time.seconds = duration; - if (dive->maxdepth.mm > stats->max_depth.mm) - stats->max_depth.mm = dive->maxdepth.mm; - if (stats->min_depth.mm == 0 || dive->maxdepth.mm < stats->min_depth.mm) - stats->min_depth.mm = dive->maxdepth.mm; - stats->combined_max_depth.mm += dive->maxdepth.mm; - - process_temperatures(dive, stats); - - /* Maybe we should drop zero-duration dives */ - if (!duration) - return; - if (dive->meandepth.mm) { - stats->total_average_depth_time.seconds += duration; - stats->avg_depth.mm = lrint((1.0 * old_tadt * stats->avg_depth.mm + - duration * dive->meandepth.mm) / - stats->total_average_depth_time.seconds); - } - if (dive->sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */ - sac_time = stats->total_sac_time.seconds + duration; - stats->avg_sac.mliter = lrint((1.0 * stats->total_sac_time.seconds * stats->avg_sac.mliter + - duration * dive->sac) / - sac_time); - if (dive->sac > stats->max_sac.mliter) - stats->max_sac.mliter = dive->sac; - if (stats->min_sac.mliter == 0 || dive->sac < stats->min_sac.mliter) - stats->min_sac.mliter = dive->sac; - stats->total_sac_time.seconds = sac_time; - } -} - -/* - * Calculate a summary of the statistics and put in the stats_summary - * structure provided in the first parameter. - * Before first use, it should be initialized with init_stats_summary(). - * After use, memory must be released with free_stats_summary(). - */ -void calculate_stats_summary(struct stats_summary *out, bool selected_only) -{ - int idx; - int t_idx, d_idx, r; - struct dive *dp; - struct tm tm; - int current_year = 0; - int current_month = 0; - int year_iter = 0; - int month_iter = 0; - int prev_month = 0, prev_year = 0; - int trip_iter = 0; - dive_trip_t *trip_ptr = 0; - size_t size, tsize, dsize, tmsize; - stats_t stats = { 0 }; - - if (divelog.dives->nr > 0) { - stats.shortest_time.seconds = divelog.dives->dives[0]->duration.seconds; - stats.min_depth.mm = divelog.dives->dives[0]->maxdepth.mm; - stats.selection_size = divelog.dives->nr; - } - - /* allocate sufficient space to hold the worst - * case (one dive per year or all dives during - * one month) for yearly and monthly statistics*/ - - size = sizeof(stats_t) * (divelog.dives->nr + 1); - tsize = sizeof(stats_t) * (NUM_DIVEMODE + 1); - dsize = sizeof(stats_t) * ((STATS_MAX_DEPTH / STATS_DEPTH_BUCKET) + 1); - tmsize = sizeof(stats_t) * ((STATS_MAX_TEMP / STATS_TEMP_BUCKET) + 1); - free_stats_summary(out); - out->stats_yearly = malloc(size); - out->stats_monthly = malloc(size); - out->stats_by_trip = malloc(size); - out->stats_by_type = malloc(tsize); - out->stats_by_depth = malloc(dsize); - out->stats_by_temp = malloc(tmsize); - if (!out->stats_yearly || !out->stats_monthly || !out->stats_by_trip || - !out->stats_by_type || !out->stats_by_depth || !out->stats_by_temp) - return; - memset(out->stats_yearly, 0, size); - memset(out->stats_monthly, 0, size); - memset(out->stats_by_trip, 0, size); - memset(out->stats_by_type, 0, tsize); - memset(out->stats_by_depth, 0, dsize); - memset(out->stats_by_temp, 0, tmsize); - out->stats_yearly[0].is_year = true; - - /* Setting the is_trip to true to show the location as first - * field in the statistics window */ - out->stats_by_type[0].location = strdup(translate("gettextFromC", "All (by type stats)")); - out->stats_by_type[0].is_trip = true; - out->stats_by_type[1].location = strdup(translate("gettextFromC", divemode_text_ui[OC])); - out->stats_by_type[1].is_trip = true; - out->stats_by_type[2].location = strdup(translate("gettextFromC", divemode_text_ui[CCR])); - out->stats_by_type[2].is_trip = true; - out->stats_by_type[3].location = strdup(translate("gettextFromC", divemode_text_ui[PSCR])); - out->stats_by_type[3].is_trip = true; - out->stats_by_type[4].location = strdup(translate("gettextFromC", divemode_text_ui[FREEDIVE])); - out->stats_by_type[4].is_trip = true; - - out->stats_by_depth[0].location = strdup(translate("gettextFromC", "All (by max depth stats)")); - out->stats_by_depth[0].is_trip = true; - - out->stats_by_temp[0].location = strdup(translate("gettextFromC", "All (by min. temp stats)")); - out->stats_by_temp[0].is_trip = true; - - /* this relies on the fact that the dives in the dive_table - * are in chronological order */ - for_each_dive (idx, dp) { - if (selected_only && !dp->selected) - continue; - if (dp->invalid) - continue; - process_dive(dp, &stats); - - /* yearly statistics */ - utc_mkdate(dp->when, &tm); - if (current_year == 0) - current_year = tm.tm_year; - - if (current_year != tm.tm_year) { - current_year = tm.tm_year; - process_dive(dp, &(out->stats_yearly[++year_iter])); - out->stats_yearly[year_iter].is_year = true; - } else { - process_dive(dp, &(out->stats_yearly[year_iter])); - } - out->stats_yearly[year_iter].selection_size++; - out->stats_yearly[year_iter].period = current_year; - - /* stats_by_type[0] is all the dives combined */ - out->stats_by_type[0].selection_size++; - process_dive(dp, &(out->stats_by_type[0])); - - process_dive(dp, &(out->stats_by_type[dp->dc.divemode + 1])); - out->stats_by_type[dp->dc.divemode + 1].selection_size++; - - /* stats_by_depth[0] is all the dives combined */ - out->stats_by_depth[0].selection_size++; - process_dive(dp, &(out->stats_by_depth[0])); - - d_idx = dp->maxdepth.mm / (STATS_DEPTH_BUCKET * 1000); - if (d_idx < 0) - d_idx = 0; - if (d_idx >= STATS_MAX_DEPTH / STATS_DEPTH_BUCKET) - d_idx = STATS_MAX_DEPTH / STATS_DEPTH_BUCKET - 1; - process_dive(dp, &(out->stats_by_depth[d_idx + 1])); - out->stats_by_depth[d_idx + 1].selection_size++; - - /* stats_by_temp[0] is all the dives combined */ - out->stats_by_temp[0].selection_size++; - process_dive(dp, &(out->stats_by_temp[0])); - - t_idx = ((int)mkelvin_to_C(dp->mintemp.mkelvin)) / STATS_TEMP_BUCKET; - if (t_idx < 0) - t_idx = 0; - if (t_idx >= STATS_MAX_TEMP / STATS_TEMP_BUCKET) - t_idx = STATS_MAX_TEMP / STATS_TEMP_BUCKET - 1; - process_dive(dp, &(out->stats_by_temp[t_idx + 1])); - out->stats_by_temp[t_idx + 1].selection_size++; - - if (dp->divetrip != NULL) { - if (trip_ptr != dp->divetrip) { - trip_ptr = dp->divetrip; - trip_iter++; - } - - /* stats_by_trip[0] is all the dives combined */ - out->stats_by_trip[0].selection_size++; - process_dive(dp, &(out->stats_by_trip[0])); - out->stats_by_trip[0].is_trip = true; - out->stats_by_trip[0].location = strdup(translate("gettextFromC", "All (by trip stats)")); - - process_dive(dp, &(out->stats_by_trip[trip_iter])); - out->stats_by_trip[trip_iter].selection_size++; - out->stats_by_trip[trip_iter].is_trip = true; - out->stats_by_trip[trip_iter].location = dp->divetrip->location; - } - - /* monthly statistics */ - if (current_month == 0) { - current_month = tm.tm_mon + 1; - } else { - if (current_month != tm.tm_mon + 1) - current_month = tm.tm_mon + 1; - if (prev_month != current_month || prev_year != current_year) - month_iter++; - } - process_dive(dp, &(out->stats_monthly[month_iter])); - out->stats_monthly[month_iter].selection_size++; - out->stats_monthly[month_iter].period = current_month; - prev_month = current_month; - prev_year = current_year; - } - - /* add labels for depth ranges up to maximum depth seen */ - if (out->stats_by_depth[0].selection_size) { - d_idx = out->stats_by_depth[0].max_depth.mm; - if (d_idx > STATS_MAX_DEPTH * 1000) - d_idx = STATS_MAX_DEPTH * 1000; - for (r = 0; r * (STATS_DEPTH_BUCKET * 1000) < d_idx; ++r) - out->stats_by_depth[r+1].is_trip = true; - } - - /* add labels for depth ranges up to maximum temperature seen */ - if (out->stats_by_temp[0].selection_size) { - t_idx = (int)mkelvin_to_C(out->stats_by_temp[0].max_temp.mkelvin); - if (t_idx > STATS_MAX_TEMP) - t_idx = STATS_MAX_TEMP; - for (r = 0; r * STATS_TEMP_BUCKET < t_idx; ++r) - out->stats_by_temp[r+1].is_trip = true; - } -} - -void free_stats_summary(struct stats_summary *stats) -{ - free(stats->stats_yearly); - free(stats->stats_monthly); - free(stats->stats_by_trip); - free(stats->stats_by_type); - free(stats->stats_by_depth); - free(stats->stats_by_temp); -} - -void init_stats_summary(struct stats_summary *stats) -{ - stats->stats_yearly = NULL; - stats->stats_monthly = NULL; - stats->stats_by_trip = NULL; - stats->stats_by_type = NULL; - stats->stats_by_depth = NULL; - stats->stats_by_temp = NULL; -} - -/* make sure we skip the selected summary entries */ -void calculate_stats_selected(stats_t *stats_selection) -{ - struct dive *dive; - unsigned int i, nr; - - memset(stats_selection, 0, sizeof(*stats_selection)); - - nr = 0; - for_each_dive(i, dive) { - if (dive->selected && !dive->invalid) { - process_dive(dive, stats_selection); - nr++; - } - } - stats_selection->selection_size = nr; -} - -#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used - -bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx) -{ - bool first_gas_explicit = false; - const struct event *event = get_next_event(dc->events, "gaschange"); - while (event) { - if (dc->sample && (event->time.seconds == 0 || - (dc->samples && dc->sample[0].time.seconds == event->time.seconds))) - first_gas_explicit = true; - if (get_cylinder_index(dive, event) == idx) - return true; - event = get_next_event(event->next, "gaschange"); - } - return !first_gas_explicit && idx == 0; -} - -bool is_cylinder_used(const struct dive *dive, int idx) -{ - const struct divecomputer *dc; - cylinder_t *cyl; - if (idx < 0 || idx >= dive->cylinders.nr) - return false; - - cyl = get_cylinder(dive, idx); - if ((cyl->start.mbar - cyl->end.mbar) > SOME_GAS) - return true; - - if ((cyl->sample_start.mbar - cyl->sample_end.mbar) > SOME_GAS) - return true; - - for_each_dc(dive, dc) { - if (has_gaschange_event(dive, dc, idx)) - return true; - else if (dc->divemode == CCR && idx == get_cylinder_idx_by_use(dive, OXYGEN)) - return true; - } - return false; -} - -bool is_cylinder_prot(const struct dive *dive, int idx) -{ - const struct divecomputer *dc; - if (idx < 0 || idx >= dive->cylinders.nr) - return false; - - for_each_dc(dive, dc) { - if (has_gaschange_event(dive, dc, idx)) - return true; - } - return false; -} - -/* Returns a dynamically allocated array with dive->cylinders.nr entries, - * which has to be freed by the caller */ -volume_t *get_gas_used(struct dive *dive) -{ - int idx; - - volume_t *gases = malloc(dive->cylinders.nr * sizeof(volume_t)); - for (idx = 0; idx < dive->cylinders.nr; idx++) { - cylinder_t *cyl = get_cylinder(dive, idx); - pressure_t start, end; - - start = cyl->start.mbar ? cyl->start : cyl->sample_start; - end = cyl->end.mbar ? cyl->end : cyl->sample_end; - if (end.mbar && start.mbar > end.mbar) - gases[idx].mliter = gas_volume(cyl, start) - gas_volume(cyl, end); - else - gases[idx].mliter = 0; - } - - return gases; -} - -/* Quite crude reverse-blender-function, but it produces a approx result */ -static void get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup, volume_t *o2, volume_t *he) -{ - volume_t air = {}; - - if (gasmix_is_air(mix)) { - o2->mliter = 0; - he->mliter = 0; - return; - } - - air.mliter = lrint(((double)vol.mliter * get_n2(mix)) / (1000 - o2_in_topup)); - he->mliter = lrint(((double)vol.mliter * get_he(mix)) / 1000.0); - o2->mliter += vol.mliter - he->mliter - air.mliter; -} - -void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot) -{ - int i, j; - struct dive *d; - for_each_dive (i, d) { - if (!d->selected || d->invalid) - continue; - volume_t *diveGases = get_gas_used(d); - for (j = 0; j < d->cylinders.nr; j++) { - if (diveGases[j].mliter) { - volume_t o2 = {}, he = {}; - get_gas_parts(get_cylinder(d, j)->gasmix, diveGases[j], O2_IN_AIR, &o2, &he); - o2_tot->mliter += o2.mliter; - he_tot->mliter += he.mliter; - } - } - free(diveGases); - } -} diff --git a/core/statistics.cpp b/core/statistics.cpp new file mode 100644 index 000000000..d2932d442 --- /dev/null +++ b/core/statistics.cpp @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* statistics.cpp + * + * core logic for the Info & Stats page + */ + +#include "statistics.h" +#include "dive.h" +#include "divelog.h" +#include "event.h" +#include "gettext.h" +#include "range.h" +#include "sample.h" +#include "subsurface-time.h" +#include "trip.h" +#include "units.h" + +#include +#include +#include + +static void process_temperatures(const struct dive &dp, stats_t &stats) +{ + temperature_t min_temp, mean_temp, max_temp; + + max_temp.mkelvin = dp.maxtemp.mkelvin; + if (max_temp.mkelvin && (!stats.max_temp.mkelvin || max_temp.mkelvin > stats.max_temp.mkelvin)) + stats.max_temp.mkelvin = max_temp.mkelvin; + + min_temp.mkelvin = dp.mintemp.mkelvin; + if (min_temp.mkelvin && (!stats.min_temp.mkelvin || min_temp.mkelvin < stats.min_temp.mkelvin)) + stats.min_temp.mkelvin = min_temp.mkelvin; + + if (min_temp.mkelvin || max_temp.mkelvin) { + mean_temp.mkelvin = min_temp.mkelvin; + if (mean_temp.mkelvin) + mean_temp.mkelvin = (mean_temp.mkelvin + max_temp.mkelvin) / 2; + else + mean_temp.mkelvin = max_temp.mkelvin; + stats.combined_temp.mkelvin += mean_temp.mkelvin; + stats.combined_count++; + } +} + +static void process_dive(const struct dive &dive, stats_t &stats) +{ + int old_tadt, sac_time = 0; + int32_t duration = dive.duration.seconds; + + old_tadt = stats.total_average_depth_time.seconds; + stats.total_time.seconds += duration; + if (duration > stats.longest_time.seconds) + stats.longest_time.seconds = duration; + if (stats.shortest_time.seconds == 0 || duration < stats.shortest_time.seconds) + stats.shortest_time.seconds = duration; + if (dive.maxdepth.mm > stats.max_depth.mm) + stats.max_depth.mm = dive.maxdepth.mm; + if (stats.min_depth.mm == 0 || dive.maxdepth.mm < stats.min_depth.mm) + stats.min_depth.mm = dive.maxdepth.mm; + stats.combined_max_depth.mm += dive.maxdepth.mm; + + process_temperatures(dive, stats); + + /* Maybe we should drop zero-duration dives */ + if (!duration) + return; + if (dive.meandepth.mm) { + stats.total_average_depth_time.seconds += duration; + stats.avg_depth.mm = lrint((1.0 * old_tadt * stats.avg_depth.mm + + duration * dive.meandepth.mm) / + stats.total_average_depth_time.seconds); + } + if (dive.sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */ + sac_time = stats.total_sac_time.seconds + duration; + stats.avg_sac.mliter = lrint((1.0 * stats.total_sac_time.seconds * stats.avg_sac.mliter + + duration * dive.sac) / + sac_time); + if (dive.sac > stats.max_sac.mliter) + stats.max_sac.mliter = dive.sac; + if (stats.min_sac.mliter == 0 || dive.sac < stats.min_sac.mliter) + stats.min_sac.mliter = dive.sac; + stats.total_sac_time.seconds = sac_time; + } +} + +/* + * Calculate a summary of the statistics and put in the stats_summary + * structure provided in the first parameter. + * Before first use, it should be initialized with init_stats_summary(). + * After use, memory must be released with free_stats_summary(). + */ +stats_summary calculate_stats_summary(bool selected_only) +{ + struct tm tm; + int current_year = -1; + int current_month = 0; + int prev_month = 0, prev_year = 0; + dive_trip *trip_ptr = nullptr; + + stats_summary out; + + /* stats_by_trip[0] is all the dives combined */ + out.stats_by_trip.emplace_back(); + + /* Setting the is_trip to true to show the location as first + * field in the statistics window */ + out.stats_by_type.resize(NUM_DIVEMODE + 1); + out.stats_by_type[0].location = translate("gettextFromC", "All (by type stats)"); + out.stats_by_type[0].is_trip = true; + out.stats_by_type[1].location = translate("gettextFromC", divemode_text_ui[OC]); + out.stats_by_type[1].is_trip = true; + out.stats_by_type[2].location = translate("gettextFromC", divemode_text_ui[CCR]); + out.stats_by_type[2].is_trip = true; + out.stats_by_type[3].location = translate("gettextFromC", divemode_text_ui[PSCR]); + out.stats_by_type[3].is_trip = true; + out.stats_by_type[4].location = translate("gettextFromC", divemode_text_ui[FREEDIVE]); + out.stats_by_type[4].is_trip = true; + + out.stats_by_depth.resize((STATS_MAX_DEPTH / STATS_DEPTH_BUCKET) + 1); + out.stats_by_depth[0].location = translate("gettextFromC", "All (by max depth stats)"); + out.stats_by_depth[0].is_trip = true; + + out.stats_by_temp.resize((STATS_MAX_TEMP / STATS_TEMP_BUCKET) + 1); + out.stats_by_temp[0].location = translate("gettextFromC", "All (by min. temp stats)"); + out.stats_by_temp[0].is_trip = true; + + /* this relies on the fact that the dives in the dive_table + * are in chronological order */ + for (auto &dp: divelog.dives) { + if (selected_only && !dp->selected) + continue; + if (dp->invalid) + continue; + //process_dive(dp, &stats); + + /* yearly statistics */ + utc_mkdate(dp->when, &tm); + + if (current_year != tm.tm_year || out.stats_yearly.empty()) { + current_year = tm.tm_year; + out.stats_yearly.emplace_back(); + out.stats_yearly.back().is_year = true; + } + process_dive(*dp, out.stats_yearly.back()); + + out.stats_yearly.back().selection_size++; + out.stats_yearly.back().period = current_year; + + /* stats_by_type[0] is all the dives combined */ + out.stats_by_type[0].selection_size++; + process_dive(*dp, out.stats_by_type[0]); + + process_dive(*dp, out.stats_by_type[dp->dcs[0].divemode + 1]); + out.stats_by_type[dp->dcs[0].divemode + 1].selection_size++; + + /* stats_by_depth[0] is all the dives combined */ + out.stats_by_depth[0].selection_size++; + process_dive(*dp, out.stats_by_depth[0]); + + int d_idx = dp->maxdepth.mm / (STATS_DEPTH_BUCKET * 1000); + d_idx = std::clamp(d_idx, 0, STATS_MAX_DEPTH / STATS_DEPTH_BUCKET); + process_dive(*dp, out.stats_by_depth[d_idx + 1]); + out.stats_by_depth[d_idx + 1].selection_size++; + + /* stats_by_temp[0] is all the dives combined */ + out.stats_by_temp[0].selection_size++; + process_dive(*dp, out.stats_by_temp[0]); + + int t_idx = ((int)mkelvin_to_C(dp->mintemp.mkelvin)) / STATS_TEMP_BUCKET; + t_idx = std::clamp(t_idx, 0, STATS_MAX_TEMP / STATS_TEMP_BUCKET); + process_dive(*dp, out.stats_by_temp[t_idx + 1]); + out.stats_by_temp[t_idx + 1].selection_size++; + + if (dp->divetrip != NULL) { + if (trip_ptr != dp->divetrip) { + trip_ptr = dp->divetrip; + out.stats_by_trip.emplace_back(); + } + + /* stats_by_trip[0] is all the dives combined */ + /* TODO: yet, this doesn't seem to consider dives outside of trips !? */ + out.stats_by_trip[0].selection_size++; + process_dive(*dp, out.stats_by_trip[0]); + out.stats_by_trip[0].is_trip = true; + out.stats_by_trip[0].location = translate("gettextFromC", "All (by trip stats)"); + + process_dive(*dp, out.stats_by_trip.back()); + out.stats_by_trip.back().selection_size++; + out.stats_by_trip.back().is_trip = true; + out.stats_by_trip.back().location = dp->divetrip->location; + } + + /* monthly statistics */ + if (current_month == 0 || out.stats_monthly.empty()) { + current_month = tm.tm_mon + 1; + out.stats_monthly.emplace_back(); + } else { + if (current_month != tm.tm_mon + 1) + current_month = tm.tm_mon + 1; + if (prev_month != current_month || prev_year != current_year) + out.stats_monthly.emplace_back(); + } + process_dive(*dp, out.stats_monthly.back()); + out.stats_monthly.back().selection_size++; + out.stats_monthly.back().period = current_month; + prev_month = current_month; + prev_year = current_year; + } + + /* add labels for depth ranges up to maximum depth seen */ + if (out.stats_by_depth[0].selection_size) { + int d_idx = out.stats_by_depth[0].max_depth.mm; + if (d_idx > STATS_MAX_DEPTH * 1000) + d_idx = STATS_MAX_DEPTH * 1000; + for (int r = 0; r * (STATS_DEPTH_BUCKET * 1000) < d_idx; ++r) + out.stats_by_depth[r+1].is_trip = true; + } + + /* add labels for depth ranges up to maximum temperature seen */ + if (out.stats_by_temp[0].selection_size) { + int t_idx = (int)mkelvin_to_C(out.stats_by_temp[0].max_temp.mkelvin); + if (t_idx > STATS_MAX_TEMP) + t_idx = STATS_MAX_TEMP; + for (int r = 0; r * STATS_TEMP_BUCKET < t_idx; ++r) + out.stats_by_temp[r+1].is_trip = true; + } + + return out; +} + +stats_summary::stats_summary() = default; +stats_summary::~stats_summary() = default; + +/* make sure we skip the selected summary entries */ +stats_t calculate_stats_selected() +{ + stats_t stats_selection; + unsigned int nr = 0; + + for (auto &dive: divelog.dives) { + if (dive->selected && !dive->invalid) { + process_dive(*dive, stats_selection); + nr++; + } + } + stats_selection.selection_size = nr; + return stats_selection; +} + +#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used + +/* Returns a vector with dive->cylinders.size() entries */ +std::vector get_gas_used(struct dive *dive) +{ + std::vector gases(dive->cylinders.size()); + for (auto [idx, cyl]: enumerated_range(dive->cylinders)) { + pressure_t start, end; + + start = cyl.start.mbar ? cyl.start : cyl.sample_start; + end = cyl.end.mbar ? cyl.end : cyl.sample_end; + // TODO: Implement addition/subtraction on units.h types + if (end.mbar && start.mbar > end.mbar) + gases[idx] = cyl.gas_volume(start) - cyl.gas_volume(end); + else + gases[idx] = 0_l; + } + + return gases; +} + +/* Quite crude reverse-blender-function, but it produces an approx result. + * Returns an (O2, He) pair. */ +static std::pair get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup) +{ + if (gasmix_is_air(mix)) + return { volume_t() , volume_t() }; + + volume_t air { .mliter = int_cast(((double)vol.mliter * get_n2(mix)) / (1000 - o2_in_topup)) }; + volume_t he { .mliter = int_cast(((double)vol.mliter * get_he(mix)) / 1000.0) }; + volume_t o2 = vol - he - air; + return std::make_pair(o2, he); +} + +std::pair selected_dives_gas_parts() +{ + volume_t o2_tot, he_tot; + for (auto &d: divelog.dives) { + if (!d->selected || d->invalid) + continue; + int j = 0; + for (auto &gas: get_gas_used(d.get())) { + if (gas.mliter) { + auto [o2, he] = get_gas_parts(d->get_cylinder(j)->gasmix, gas, O2_IN_AIR); + o2_tot += o2; + he_tot += he; + } + j++; + } + } + return std::make_pair(o2_tot, he_tot); +} diff --git a/core/statistics.h b/core/statistics.h index 4b2b0b1bb..2ff463153 100644 --- a/core/statistics.h +++ b/core/statistics.h @@ -13,15 +13,18 @@ #define STATS_MAX_DEPTH 300 /* Max depth for stats bucket is 300m */ #define STATS_DEPTH_BUCKET 10 /* Size of buckets for depth range */ -#define STATS_MAX_TEMP 40 /* Max temp for stats bucket is 40C */ -#define STATS_TEMP_BUCKET 5 /* Size of buckets for temp range */ +#define STATS_MAX_TEMP 40 /* Max temp for stats bucket is 40C */ +#define STATS_TEMP_BUCKET 5 /* Size of buckets for temp range */ struct dive; -typedef struct +#include +#include + +struct stats_t { - int period; - duration_t total_time; + int period = 0; + duration_t total_time ; /* total time of dives with non-zero average depth */ duration_t total_average_depth_time; /* avg_time is simply total_time / nr -- let's not keep this */ @@ -37,57 +40,28 @@ typedef struct temperature_t max_temp; temperature_t min_temp; temperature_sum_t combined_temp; - unsigned int combined_count; - unsigned int selection_size; + unsigned int combined_count = 0; + unsigned int selection_size = 0; duration_t total_sac_time; - bool is_year; - bool is_trip; - char *location; -} stats_t; + bool is_year = false; + bool is_trip = false; + std::string location; +}; struct stats_summary { - stats_t *stats_yearly; - stats_t *stats_monthly; - stats_t *stats_by_trip; - stats_t *stats_by_type; - stats_t *stats_by_depth; - stats_t *stats_by_temp; + stats_summary(); + ~stats_summary(); + std::vector stats_yearly; + std::vector stats_monthly; + std::vector stats_by_trip; + std::vector stats_by_type; + std::vector stats_by_depth; + std::vector stats_by_temp; }; -#ifdef __cplusplus -extern "C" { -#endif - -extern void init_stats_summary(struct stats_summary *stats); -extern void free_stats_summary(struct stats_summary *stats); -extern void calculate_stats_summary(struct stats_summary *stats, bool selected_only); -extern void calculate_stats_selected(stats_t *stats_selection); -extern volume_t *get_gas_used(struct dive *dive); -extern void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot); - -#ifdef __cplusplus -} -#endif - -/* - * For C++ code, provide a convenience version of stats_summary - * that initializes the structure on construction and frees - * resources when it goes out of scope. Apart from that, it - * can be used as a stats_summary replacement. - */ -#ifdef __cplusplus -struct stats_summary_auto_free : public stats_summary { - stats_summary_auto_free(); - ~stats_summary_auto_free(); -}; -inline stats_summary_auto_free::stats_summary_auto_free() -{ - init_stats_summary(this); -} -inline stats_summary_auto_free::~stats_summary_auto_free() -{ - free_stats_summary(this); -} -#endif +extern stats_summary calculate_stats_summary(bool selected_only); +extern stats_t calculate_stats_selected(); +extern std::vector get_gas_used(struct dive *dive); +extern std::pair selected_dives_gas_parts(); // returns (O2, He) tuple #endif // STATISTICS_H diff --git a/core/string-format.cpp b/core/string-format.cpp index 4e10c38bf..07bb45f26 100644 --- a/core/string-format.cpp +++ b/core/string-format.cpp @@ -1,9 +1,12 @@ #include "string-format.h" #include "dive.h" +#include "divelist.h" +#include "divelog.h" #include "divesite.h" #include "event.h" #include "format.h" #include "qthelper.h" +#include "range.h" #include "subsurface-string.h" #include "trip.h" #include @@ -13,23 +16,21 @@ enum returnPressureSelector { START_PRESSURE, END_PRESSURE }; static QLocale loc; -static QString getPressures(const struct dive *dive, int i, enum returnPressureSelector ret) +static QString getPressures(const cylinder_t &cyl, enum returnPressureSelector ret) { - const cylinder_t *cyl = get_cylinder(dive, i); - QString fmt; if (ret == START_PRESSURE) { - if (cyl->start.mbar) - fmt = get_pressure_string(cyl->start, true); - else if (cyl->sample_start.mbar) - fmt = get_pressure_string(cyl->sample_start, true); + if (cyl.start.mbar) + return get_pressure_string(cyl.start, true); + else if (cyl.sample_start.mbar) + return get_pressure_string(cyl.sample_start, true); } if (ret == END_PRESSURE) { - if (cyl->end.mbar) - fmt = get_pressure_string(cyl->end, true); - else if(cyl->sample_end.mbar) - fmt = get_pressure_string(cyl->sample_end, true); + if (cyl.end.mbar) + return get_pressure_string(cyl.end, true); + else if (cyl.sample_end.mbar) + return get_pressure_string(cyl.sample_end, true); } - return fmt; + return QString(); } QString formatSac(const dive *d) @@ -44,8 +45,8 @@ QString formatSac(const dive *d) QString formatNotes(const dive *d) { - QString tmp = d->notes ? QString::fromUtf8(d->notes) : QString(); - if (is_dc_planner(&d->dc)) { + QString tmp = QString::fromStdString(d->notes); + if (is_dc_planner(&d->dcs[0])) { QTextDocument notes; #define _NOTES_BR "\n" tmp.replace("", "" _NOTES_BR) @@ -77,9 +78,9 @@ QString format_gps_decimal(const dive *d) QStringList formatGetCylinder(const dive *d) { QStringList getCylinder; - for (int i = 0; i < d->cylinders.nr; i++) { - if (is_cylinder_used(d, i)) - getCylinder << get_cylinder(d, i)->type.description; + for (auto [i, cyl]: enumerated_range(d->cylinders)) { + if (d->is_cylinder_used(i)) + getCylinder << QString::fromStdString(cyl.type.description); } return getCylinder; } @@ -87,9 +88,9 @@ QStringList formatGetCylinder(const dive *d) QStringList formatStartPressure(const dive *d) { QStringList startPressure; - for (int i = 0; i < d->cylinders.nr; i++) { - if (is_cylinder_used(d, i)) - startPressure << getPressures(d, i, START_PRESSURE); + for (auto [i, cyl]: enumerated_range(d->cylinders)) { + if (d->is_cylinder_used(i)) + startPressure << getPressures(cyl, START_PRESSURE); } return startPressure; } @@ -97,9 +98,9 @@ QStringList formatStartPressure(const dive *d) QStringList formatEndPressure(const dive *d) { QStringList endPressure; - for (int i = 0; i < d->cylinders.nr; i++) { - if (is_cylinder_used(d, i)) - endPressure << getPressures(d, i, END_PRESSURE); + for (auto [i, cyl]: enumerated_range(d->cylinders)) { + if (d->is_cylinder_used(i)) + endPressure << getPressures(cyl, END_PRESSURE); } return endPressure; } @@ -107,67 +108,62 @@ QStringList formatEndPressure(const dive *d) QStringList formatFirstGas(const dive *d) { QStringList gas; - for (int i = 0; i < d->cylinders.nr; i++) { - if (is_cylinder_used(d, i)) - gas << get_gas_string(get_cylinder(d, i)->gasmix); + for (auto [i, cyl]: enumerated_range(d->cylinders)) { + if (d->is_cylinder_used(i)) + gas << get_gas_string(cyl.gasmix); } return gas; } // Add string to sorted QStringList, if it doesn't already exist and // it isn't the empty string. -static void addStringToSortedList(QStringList &l, const char *s) +static void addStringToSortedList(QStringList &l, const std::string &s) { - if (empty_string(s)) + if (s.empty()) return; // Do a binary search for the string. lower_bound() returns an iterator // to either the searched-for element or the next higher element if it // doesn't exist. - QString qs(s); + QString qs = QString::fromStdString(s); auto it = std::lower_bound(l.begin(), l.end(), qs); // TODO: use locale-aware sorting - if (it != l.end() && *it == s) + if (it != l.end() && *it == qs) return; // Add new string at sorted position - l.insert(it, s); + l.insert(it, qs); } QStringList formatFullCylinderList() { QStringList cylinders; - struct dive *d; - int i = 0; - for_each_dive (i, d) { - for (int j = 0; j < d->cylinders.nr; j++) - addStringToSortedList(cylinders, get_cylinder(d, j)->type.description); + for (auto &d: divelog.dives) { + for (const cylinder_t &cyl: d->cylinders) + addStringToSortedList(cylinders, cyl.type.description); } - for (int ti = 0; ti < tank_info_table.nr; ti++) - addStringToSortedList(cylinders, tank_info_table.infos[ti].name); + for (const auto &ti: tank_info_table) + addStringToSortedList(cylinders, ti.name); return cylinders; } -static QString formattedCylinder(const struct dive *dive, int idx) +static QString formattedCylinder(const cylinder_t &cyl) { - const cylinder_t *cyl = get_cylinder(dive, idx); - const char *desc = cyl->type.description; - QString fmt = desc ? QString(desc) : gettextFromC::tr("unknown"); - fmt += ", " + get_volume_string(cyl->type.size, true); - fmt += ", " + get_pressure_string(cyl->type.workingpressure, true); - fmt += ", " + get_pressure_string(cyl->start, false) + " - " + get_pressure_string(cyl->end, true); - fmt += ", " + get_gas_string(cyl->gasmix); + const std::string &desc = cyl.type.description; + QString fmt = !desc.empty() ? QString::fromStdString(desc) : gettextFromC::tr("unknown"); + fmt += ", " + get_volume_string(cyl.type.size, true); + fmt += ", " + get_pressure_string(cyl.type.workingpressure, true); + fmt += ", " + get_pressure_string(cyl.start, false) + " - " + get_pressure_string(cyl.end, true); + fmt += ", " + get_gas_string(cyl.gasmix); return fmt; } QStringList formatCylinders(const dive *d) { QStringList cylinders; - for (int i = 0; i < d->cylinders.nr; i++) { - QString cyl = formattedCylinder(d, i); - cylinders << cyl; - } + for (const cylinder_t &cyl: d->cylinders) + cylinders << formattedCylinder(cyl); return cylinders; } @@ -176,14 +172,14 @@ QString formatGas(const dive *d) /*WARNING: here should be the gastlist, returned * from the get_gas_string function or this is correct? */ - QString gas, gases; - for (int i = 0; i < d->cylinders.nr; i++) { - if (!is_cylinder_used(d, i)) + QString gases; + for (auto [i, cyl]: enumerated_range(d->cylinders)) { + if (!d->is_cylinder_used(i)) continue; - gas = get_cylinder(d, i)->type.description; + QString gas = QString::fromStdString(cyl.type.description); if (!gas.isEmpty()) gas += QChar(' '); - gas += gasname(get_cylinder(d, i)->gasmix); + gas += QString::fromStdString(cyl.gasmix.name()); // if has a description and if such gas is not already present if (!gas.isEmpty() && gases.indexOf(gas) == -1) { if (!gases.isEmpty()) @@ -196,24 +192,22 @@ QString formatGas(const dive *d) QString formatSumWeight(const dive *d) { - return get_weight_string(weight_t { total_weight(d) }, true); + return get_weight_string(d->total_weight(), true); } -static QString getFormattedWeight(const struct dive *dive, int idx) +static QString getFormattedWeight(const weightsystem_t &weight) { - const weightsystem_t *weight = &dive->weightsystems.weightsystems[idx]; - if (!weight->description) + if (weight.description.empty()) return QString(); - QString fmt = QString(weight->description); - fmt += ", " + get_weight_string(weight->weight, true); - return fmt; + return QString::fromStdString(weight.description) + + ", " + get_weight_string(weight.weight, true); } QString formatWeightList(const dive *d) { QString weights; - for (int i = 0; i < d->weightsystems.nr; i++) { - QString w = getFormattedWeight(d, i); + for (auto &ws: d->weightsystems) { + QString w = getFormattedWeight(ws); if (w.isEmpty()) continue; weights += w + "; "; @@ -224,8 +218,8 @@ QString formatWeightList(const dive *d) QStringList formatWeights(const dive *d) { QStringList weights; - for (int i = 0; i < d->weightsystems.nr; i++) { - QString w = getFormattedWeight(d, i); + for (auto &ws: d->weightsystems) { + QString w = getFormattedWeight(ws); if (w.isEmpty()) continue; weights << w; @@ -247,26 +241,25 @@ QString formatDiveGPS(const dive *d) QString formatDiveDate(const dive *d) { QDateTime localTime = timestampToDateTime(d->when); - return localTime.date().toString(prefs.date_format_short); + return localTime.date().toString(QString::fromStdString(prefs.date_format_short)); } QString formatDiveTime(const dive *d) { QDateTime localTime = timestampToDateTime(d->when); - return localTime.time().toString(prefs.time_format); + return localTime.time().toString(QString::fromStdString(prefs.time_format)); } QString formatDiveDateTime(const dive *d) { QDateTime localTime = timestampToDateTime(d->when); - return QStringLiteral("%1 %2").arg(localTime.date().toString(prefs.date_format_short), - localTime.time().toString(prefs.time_format)); + return QStringLiteral("%1 %2").arg(localTime.date().toString(QString::fromStdString(prefs.date_format_short)), + localTime.time().toString(QString::fromStdString(prefs.time_format))); } QString formatDiveGasString(const dive *d) { - int o2, he, o2max; - get_dive_gas(d, &o2, &he, &o2max); + auto [o2, he, o2max ] = d->get_maximal_gas(); o2 = (o2 + 5) / 10; he = (he + 5) / 10; o2max = (o2max + 5) / 10; @@ -305,26 +298,23 @@ QString formatMinutes(int seconds) return QString::asprintf("%d:%.2d", FRACTION_TUPLE(seconds, 60)); } -QString formatTripTitle(const dive_trip *trip) +QString formatTripTitle(const dive_trip &trip) { - if (!trip) - return QString(); - - timestamp_t when = trip_date(trip); - bool getday = trip_is_single_day(trip); + timestamp_t when = trip.date(); + bool getday = trip.is_single_day(); QDateTime localTime = timestampToDateTime(when); - QString prefix = !empty_string(trip->location) ? QString(trip->location) + ", " : QString(); + QString prefix = !trip.location.empty() ? QString::fromStdString(trip.location) + ", " : QString(); if (getday) - return prefix + loc.toString(localTime, prefs.date_format); + return prefix + loc.toString(localTime, QString::fromStdString(prefs.date_format)); else return prefix + loc.toString(localTime, "MMM yyyy"); } -QString formatTripTitleWithDives(const dive_trip *trip) +QString formatTripTitleWithDives(const dive_trip &trip) { - int nr = trip->dives.nr; + int nr = static_cast(trip.dives.size()); return formatTripTitle(trip) + " " + gettextFromC::tr("(%n dive(s))", "", nr); } diff --git a/core/string-format.h b/core/string-format.h index c83ff8e70..1956db1cc 100644 --- a/core/string-format.h +++ b/core/string-format.h @@ -30,7 +30,7 @@ QString formatDiveDateTime(const dive *d); QString formatDiveGasString(const dive *d); QString formatDayOfWeek(int day); QString formatMinutes(int seconds); -QString formatTripTitle(const dive_trip *trip); -QString formatTripTitleWithDives(const dive_trip *trip); +QString formatTripTitle(const dive_trip &trip); +QString formatTripTitleWithDives(const dive_trip &trip); #endif diff --git a/core/strtod.c b/core/strtod.cpp similarity index 52% rename from core/strtod.c rename to core/strtod.cpp index 74720783c..39abfb7a7 100644 --- a/core/strtod.c +++ b/core/strtod.cpp @@ -3,68 +3,51 @@ * Sane helper for 'strtod()'. * * Sad that we even need this, but the C library version has - * insane locale behavior, and while the Qt "doDouble()" routines + * insane locale behavior, and while the Qt "toDouble()" routines * are better in that regard, they don't have an end pointer * (having replaced it with the completely idiotic "ok" boolean * pointer instead). * - * I wonder what drugs people are on sometimes. - * - * Right now we support the following flags to limit the - * parsing some ways: - * - * STRTOD_NO_SIGN - don't accept signs - * STRTOD_NO_DOT - no decimal dots, I'm European - * STRTOD_NO_COMMA - no comma, please, I'm C locale - * STRTOD_NO_EXPONENT - no exponent parsing, I'm human - * - * The "negative" flags are so that the common case can just - * use a flag value of 0, and only if you have some special - * requirements do you need to state those with explicit flags. - * - * So if you want the C locale kind of parsing, you'd use the - * STRTOD_NO_COMMA flag to disallow a decimal comma. But if you - * want a more relaxed "Hey, Europeans are people too, even if - * they have locales with commas", just pass in a zero flag. + * So if you want the C locale kind of parsing, use the + * ascii_strtod() function. But if you want a more relaxed + * "Hey, Europeans are people too, even if they have locales + * with commas", use general_strtod() instead. */ #include #include "subsurface-string.h" -double strtod_flags(const char *str, const char **ptr, unsigned int flags) +static double strtod_flags(const char *str, const char **ptr, bool no_comma) { char c; const char *p = str, *ep; double val = 0.0; double decimal = 1.0; - int sign = 0, esign = 0; - int numbers = 0, dot = 0; + bool sign = false, esign = false; + bool numbers = false, dot = false; /* skip spaces */ while (isspace(c = *p++)) /* */; /* optional sign */ - if (!(flags & STRTOD_NO_SIGN)) { - switch (c) { - case '-': - sign = 1; - /* fallthrough */ - case '+': - c = *p++; - } + switch (c) { + case '-': + sign = true; + /* fallthrough */ + case '+': + c = *p++; } /* Mantissa */ for (;; c = *p++) { - if ((c == '.' && !(flags & STRTOD_NO_DOT)) || - (c == ',' && !(flags & STRTOD_NO_COMMA))) { + if (c == '.' || (c == ',' && !no_comma)) { if (dot) goto done; - dot = 1; + dot = true; continue; } if (c >= '0' && c <= '9') { - numbers++; + numbers = true; val = (val * 10) + (c - '0'); if (dot) decimal *= 10; @@ -72,8 +55,6 @@ double strtod_flags(const char *str, const char **ptr, unsigned int flags) } if (c != 'e' && c != 'E') goto done; - if (flags & STRTOD_NO_EXPONENT) - goto done; break; } @@ -85,7 +66,7 @@ double strtod_flags(const char *str, const char **ptr, unsigned int flags) c = *ep++; switch (c) { case '-': - esign = 1; + esign = true; /* fallthrough */ case '+': c = *ep++; @@ -127,3 +108,13 @@ no_conversion: *ptr = str; return 0.0; } + +double permissive_strtod(const char *str, const char **ptr) +{ + return strtod_flags(str, ptr, false); +} + +double ascii_strtod(const char *str, const char **ptr) +{ + return strtod_flags(str, ptr, true); +} diff --git a/core/structured_list.h b/core/structured_list.h deleted file mode 100644 index c392b52c4..000000000 --- a/core/structured_list.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef STRUCTURED_LIST_H -#define STRUCTURED_LIST_H - -/* Clear whole list; this works for taglist and dive computers */ -#define STRUCTURED_LIST_FREE(_type, _start, _free) \ - { \ - _type *_ptr = _start; \ - while (_ptr) { \ - _type *_next = _ptr->next; \ - _free(_ptr); \ - _ptr = _next; \ - } \ - } - -#define STRUCTURED_LIST_COPY(_type, _first, _dest, _cpy) \ - { \ - _type *_sptr = _first; \ - _type **_dptr = &_dest; \ - while (_sptr) { \ - *_dptr = (_type *)malloc(sizeof(_type)); \ - _cpy(_sptr, *_dptr); \ - _sptr = _sptr->next; \ - _dptr = &(*_dptr)->next; \ - } \ - *_dptr = 0; \ - } - -#endif diff --git a/core/subsurface-float.h b/core/subsurface-float.h index 4dc225728..9f3b1eb27 100644 --- a/core/subsurface-float.h +++ b/core/subsurface-float.h @@ -4,10 +4,6 @@ #include -#ifdef __cplusplus -extern "C" { -#endif - static inline bool nearly_equal(double a, double b) { return fabs(a - b) <= 1e-6 * fmax(fabs(a), fabs(b)); @@ -18,7 +14,4 @@ static inline bool nearly_0(double fp) return fabs(fp) <= 1e-6; } -#ifdef __cplusplus -} -#endif #endif // SUBSURFACE_FLOAT_H diff --git a/core/subsurface-qt/divelistnotifier.h b/core/subsurface-qt/divelistnotifier.h index 98339e339..53ea260e5 100644 --- a/core/subsurface-qt/divelistnotifier.h +++ b/core/subsurface-qt/divelistnotifier.h @@ -6,7 +6,6 @@ #define DIVELISTNOTIFIER_H #include "core/dive.h" -#include "core/pictureobj.h" #include @@ -133,7 +132,7 @@ signals: // Picture (media) related signals void pictureOffsetChanged(dive *d, QString filename, offset_t offset); void picturesRemoved(dive *d, QVector filenames); - void picturesAdded(dive *d, QVector pics); + void picturesAdded(dive *d, QVector pics); // Devices related signals void deviceEdited(); diff --git a/core/subsurface-string.h b/core/subsurface-string.h index 59a50d8c0..e24ff5097 100644 --- a/core/subsurface-string.h +++ b/core/subsurface-string.h @@ -2,33 +2,13 @@ #ifndef SUBSURFACE_STRING_H #define SUBSURFACE_STRING_H +#include #include #include #include - -// shared generic definitions and macros -// mostly about strings, but a couple of math macros are here as well - -/* Windows has no MIN/MAX macros - so let's just roll our own */ -#ifndef MIN -#define MIN(x, y) ({ \ - __typeof__(x) _min1 = (x); \ - __typeof__(y) _min2 = (y); \ - (void) (&_min1 == &_min2); \ - _min1 < _min2 ? _min1 : _min2; }) -#endif - -#ifndef MAX -#define MAX(x, y) ({ \ - __typeof__(x) _max1 = (x); \ - __typeof__(y) _max2 = (y); \ - (void) (&_max1 == &_max2); \ - _max1 > _max2 ? _max1 : _max2; }) -#endif - -#ifdef __cplusplus -extern "C" { -#endif +#include +#include +#include // string handling @@ -47,41 +27,31 @@ static inline bool empty_string(const char *s) return !s || !*s; } -static inline char *copy_string(const char *s) -{ - return (s && *s) ? strdup(s) : NULL; -} - -#define STRTOD_NO_SIGN 0x01 -#define STRTOD_NO_DOT 0x02 -#define STRTOD_NO_COMMA 0x04 -#define STRTOD_NO_EXPONENT 0x08 -extern double strtod_flags(const char *str, const char **ptr, unsigned int flags); - -#define STRTOD_ASCII (STRTOD_NO_COMMA) - -#define ascii_strtod(str, ptr) strtod_flags(str, ptr, STRTOD_ASCII) - -#ifdef __cplusplus -} - -#include -#include +extern double permissive_strtod(const char *str, const char **ptr); +extern double ascii_strtod(const char *str, const char **ptr); // Sadly, starts_with only with C++20! -inline bool starts_with(const std::string &s, const char *s2) +inline bool starts_with(std::string_view s, const char *s2) { return s.rfind(s2, 0) == 0; } // Sadly, std::string::contains only with C++23! -inline bool contains(const std::string &s, char c) +inline bool contains(std::string_view s, char c) { return s.find(c) != std::string::npos; } +inline bool contains(std::string_view haystack, const char *needle) +{ + return haystack.find(needle) != std::string::npos; +} + +inline bool contains(std::string_view haystack, const std::string &needle) +{ + return haystack.find(needle) != std::string::npos; +} + std::string join(const std::vector &l, const std::string &separator, bool skip_empty = false); -#endif - #endif // SUBSURFACE_STRING_H diff --git a/core/subsurface-time.h b/core/subsurface-time.h index 10dc92e51..554fdcc6f 100644 --- a/core/subsurface-time.h +++ b/core/subsurface-time.h @@ -3,28 +3,19 @@ #define TIME_H #include "units.h" +#include #include -#ifdef __cplusplus -extern "C" { -#endif - extern timestamp_t utc_mktime(const struct tm *tm); extern void utc_mkdate(timestamp_t, struct tm *tm); extern int utc_year(timestamp_t timestamp); extern int utc_weekday(timestamp_t timestamp); +extern int gettimezoneoffset(); /* parse and format date times of the form YYYY-MM-DD hh:mm:ss */ extern timestamp_t parse_datetime(const char *s); /* returns 0 on error */ - extern const char *monthname(int mon); -#ifdef __cplusplus -} - -#include std::string format_datetime(timestamp_t timestamp); /* ownership of string passed to caller */ #endif - -#endif diff --git a/core/subsurfacestartup.cpp b/core/subsurfacestartup.cpp index e91a340bc..502bc5010 100644 --- a/core/subsurfacestartup.cpp +++ b/core/subsurfacestartup.cpp @@ -25,7 +25,7 @@ std::string testqml; */ bool imported = false; -extern "C" void print_version() +void print_version() { static bool version_printed = false; if (version_printed) @@ -43,14 +43,14 @@ extern "C" void print_version() version_printed = true; } -extern "C" void print_files() +void print_files() { struct git_info info; std::optional filename; printf("\nFile locations:\n\n"); - printf("Cloud email:%s\n", prefs.cloud_storage_email); - if (!empty_string(prefs.cloud_storage_email) && !empty_string(prefs.cloud_storage_password)) { + printf("Cloud email:%s\n", prefs.cloud_storage_email.c_str()); + if (!prefs.cloud_storage_email.empty() && !prefs.cloud_storage_password.empty()) { filename = getCloudURL(); if (filename) is_git_repository(filename->c_str(), &info); @@ -63,9 +63,7 @@ extern "C" void print_files() printf("Unable to get local git directory\n"); } printf("Cloud URL: %s\n", filename->c_str()); - char *tmp = hashfile_name_string(); - printf("Image filename table: %s\n", tmp); - free(tmp); + printf("Image filename table: %s\n", hashfile_name().c_str()); } static void print_help() @@ -89,7 +87,7 @@ static void print_help() printf("\n --cloud-timeout= Set timeout for cloud connection (0 < timeout < 60)\n\n"); } -extern "C" void parse_argument(const char *arg) +void parse_argument(const char *arg) { const char *p = arg + 1; @@ -109,7 +107,7 @@ extern "C" void parse_argument(const char *arg) /* long options with -- */ /* first test for --user=bla which allows the use of user specific settings */ if (strncmp(arg, "--user=", sizeof("--user=") - 1) == 0) { - settings_suffix = strdup(arg + sizeof("--user=") - 1); + settings_suffix = arg + sizeof("--user=") - 1; return; } if (strncmp(arg, "--cloud-timeout=", sizeof("--cloud-timeout=") - 1) == 0) { @@ -146,15 +144,15 @@ extern "C" void parse_argument(const char *arg) } #if SUBSURFACE_DOWNLOADER if (strncmp(arg, "--dc-vendor=", sizeof("--dc-vendor=") - 1) == 0) { - prefs.dive_computer.vendor = strdup(arg + sizeof("--dc-vendor=") - 1); + prefs.dive_computer.vendor = arg + sizeof("--dc-vendor=") - 1; return; } if (strncmp(arg, "--dc-product=", sizeof("--dc-product=") - 1) == 0) { - prefs.dive_computer.product = strdup(arg + sizeof("--dc-product=") - 1); + prefs.dive_computer.product = arg + sizeof("--dc-product=") - 1; return; } if (strncmp(arg, "--device=", sizeof("--device=") - 1) == 0) { - prefs.dive_computer.device = strdup(arg + sizeof("--device=") - 1); + prefs.dive_computer.device = arg + sizeof("--device=") - 1; return; } if (strncmp(arg, "--list-dc", sizeof("--list-dc") - 1) == 0) { @@ -191,17 +189,17 @@ extern "C" void parse_argument(const char *arg) * I guess Burma and Liberia should trigger this too. I'm too * lazy to look up the territory names, though. */ -extern "C" void setup_system_prefs(void) +void setup_system_prefs() { const char *env; subsurface_OS_pref_setup(); - default_prefs.divelist_font = strdup(system_divelist_default_font); + default_prefs.divelist_font = system_divelist_default_font; default_prefs.font_size = system_divelist_default_font_size; - default_prefs.ffmpeg_executable = strdup("ffmpeg"); + default_prefs.ffmpeg_executable = "ffmpeg"; #if !defined(SUBSURFACE_MOBILE) - default_prefs.default_filename = copy_string(system_default_filename()); + default_prefs.default_filename = system_default_filename(); #endif env = getenv("LC_MEASUREMENT"); if (!env) diff --git a/core/subsurfacestartup.h b/core/subsurfacestartup.h index 6f40f7abd..93c666a69 100644 --- a/core/subsurfacestartup.h +++ b/core/subsurfacestartup.h @@ -2,31 +2,24 @@ #ifndef SUBSURFACESTARTUP_H #define SUBSURFACESTARTUP_H -#ifdef __cplusplus -extern "C" { -#else -#include -#endif +#include extern bool imported; extern int quit, force_root, ignore_bt; -void setup_system_prefs(void); +void setup_system_prefs(); void parse_argument(const char *arg); -void free_prefs(void); -void print_files(void); -void print_version(void); +void print_files(); +void print_version(); -extern char *settings_suffix; +void subsurface_console_init(); +void subsurface_console_exit(); +bool subsurface_user_is_root(); -#ifdef __cplusplus -} +extern std::string settings_suffix; #ifdef SUBSURFACE_MOBILE_DESKTOP -#include extern std::string testqml; #endif -#endif - #endif // SUBSURFACESTARTUP_H diff --git a/core/subsurfacesysinfo.cpp b/core/subsurfacesysinfo.cpp index 7cad562b8..966c528a0 100644 --- a/core/subsurfacesysinfo.cpp +++ b/core/subsurfacesysinfo.cpp @@ -3,7 +3,7 @@ #include #ifdef Q_OS_WIN -extern "C" bool isWin7Or8() +bool isWin7Or8() { return (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based) >= QSysInfo::WV_WINDOWS7; } diff --git a/core/subsurfacesysinfo.h b/core/subsurfacesysinfo.h index a608239b3..a5354478a 100644 --- a/core/subsurfacesysinfo.h +++ b/core/subsurfacesysinfo.h @@ -1,13 +1,7 @@ #ifndef SUBSURFACESYSINFO_H #define SUBSURFACESYSINFO_H -#include - #ifdef Q_OS_WIN -#ifdef __cplusplus -extern "C" -#endif - bool isWin7Or8(); #endif diff --git a/core/table.h b/core/table.h deleted file mode 100644 index 1f241e5cb..000000000 --- a/core/table.h +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* This header defines a number of macros that generate table-manipulation functions. - * There is no design behind these macros - the functions were take as-is and transformed - * into macros. Thus, this whole header could do with some redesign. */ -#ifndef CORE_TABLE_H -#define CORE_TABLE_H - -#define MAKE_GROW_TABLE(table_type, item_type, array_name) \ - item_type *grow_##table_type(struct table_type *table) \ - { \ - int nr = table->nr, allocated = table->allocated; \ - item_type *items = table->array_name; \ - \ - if (nr >= allocated) { \ - allocated = (nr + 32) * 3 / 2; \ - items = realloc(items, allocated * sizeof(item_type)); \ - if (!items) \ - exit(1); \ - table->array_name = items; \ - table->allocated = allocated; \ - } \ - return items; \ - } - -/* get the index where we want to insert an object so that everything stays - * ordered according to a comparison function() */ -#define MAKE_GET_INSERTION_INDEX(table_type, item_type, array_name, fun) \ - int table_type##_get_insertion_index(struct table_type *table, item_type item) \ - { \ - /* we might want to use binary search here */ \ - for (int i = 0; i < table->nr; i++) { \ - if (fun(item, table->array_name[i])) \ - return i; \ - } \ - return table->nr; \ - } - -/* add object at the given index to a table. */ -#define MAKE_ADD_TO(table_type, item_type, array_name) \ - void add_to_##table_type(struct table_type *table, int idx, item_type item) \ - { \ - int i; \ - grow_##table_type(table); \ - table->nr++; \ - \ - for (i = idx; i < table->nr; i++) { \ - item_type tmp = table->array_name[i]; \ - table->array_name[i] = item; \ - item = tmp; \ - } \ - } - -#define MAKE_REMOVE_FROM(table_type, array_name) \ - void remove_from_##table_type(struct table_type *table, int idx) \ - { \ - int i; \ - for (i = idx; i < table->nr - 1; i++) \ - table->array_name[i] = table->array_name[i + 1]; \ - memset(&table->array_name[--table->nr], 0, sizeof(table->array_name[0])); \ - } - -#define MAKE_GET_IDX(table_type, item_type, array_name) \ - int get_idx_in_##table_type(const struct table_type *table, const item_type item) \ - { \ - for (int i = 0; i < table->nr; ++i) { \ - if (table->array_name[i] == item) \ - return i; \ - } \ - return -1; \ - } - -#define MAKE_SORT(table_type, item_type, array_name, fun) \ - static int sortfn_##table_type(const void *_a, const void *_b) \ - { \ - const item_type a = *(const item_type *)_a; \ - const item_type b = *(const item_type *)_b; \ - return fun(a, b); \ - } \ - \ - void sort_##table_type(struct table_type *table) \ - { \ - qsort(table->array_name, table->nr, sizeof(item_type), sortfn_##table_type); \ - } - -#define MAKE_REMOVE(table_type, item_type, item_name) \ - int remove_##item_name(const item_type item, struct table_type *table) \ - { \ - int idx = get_idx_in_##table_type(table, item); \ - if (idx >= 0) \ - remove_from_##table_type(table, idx); \ - return idx; \ - } - -#define MAKE_CLEAR_TABLE(table_type, array_name, item_name) \ - void clear_##table_type(struct table_type *table) \ - { \ - for (int i = 0; i < table->nr; i++) \ - free_##item_name(table->array_name[i]); \ - free(table->array_name); \ - table->array_name = NULL; \ - table->allocated = 0; \ - table->nr = 0; \ - } - -/* Move data of one table to the other - source table is empty after call. */ -#define MAKE_MOVE_TABLE(table_type, array_name) \ - void move_##table_type(struct table_type *src, struct table_type *dst) \ - { \ - clear_##table_type(dst); \ - free(dst->array_name); \ - *dst = *src; \ - src->nr = src->allocated = 0; \ - src->array_name = NULL; \ - } - -#endif diff --git a/core/tag.cpp b/core/tag.cpp index 3dca51b2e..5884cfd5b 100644 --- a/core/tag.cpp +++ b/core/tag.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include "tag.h" -#include "structured_list.h" #include "subsurface-string.h" #include "membuffer.h" #include "gettext.h" @@ -22,127 +21,90 @@ static const char *default_tags[] = { QT_TRANSLATE_NOOP("gettextFromC", "deco") }; -/* copy an element in a list of tags */ -static void copy_tl(struct tag_entry *st, struct tag_entry *dt) +divetag::divetag(std::string name, std::string source) : + name(std::move(name)), source(std::move(source)) { - dt->tag = st->tag; } -static bool tag_seen_before(struct tag_entry *start, struct tag_entry *before) +/* remove duplicates and empty tags */ +void taglist_cleanup(tag_list &list) { - while (start && start != before) { - if (start->tag->name == before->tag->name) - return true; - start = start->next; - } - return false; + // Remove empty tags + list.erase(std::remove_if(list.begin(), list.end(), [](const divetag *tag) { return tag->name.empty(); }), + list.end()); + + // Sort (should be a NOP, because we add in a sorted way, but let's make sure) + std::sort(list.begin(), list.end()); + + // Remove duplicates + list.erase(std::unique(list.begin(), list.end(), + [](const divetag *tag1, const divetag *tag2) { return tag1->name == tag2->name; }), + list.end()); } -/* remove duplicates and empty nodes */ -extern "C" void taglist_cleanup(struct tag_entry **tag_list) +std::string taglist_get_tagstring(const tag_list &list) { - struct tag_entry **tl = tag_list; - while (*tl) { - /* skip tags that are empty or that we have seen before */ - if ((*tl)->tag->name.empty() || tag_seen_before(*tag_list, *tl)) { - *tl = (*tl)->next; - continue; - } - tl = &(*tl)->next; - } -} - -std::string taglist_get_tagstring(struct tag_entry *tag_list) -{ - bool first_tag = true; std::string res; - for (struct tag_entry *tmp = tag_list; tmp != NULL; tmp = tmp->next) { - if (tmp->tag->name.empty()) + for (const divetag *tag: list) { + if (tag->name.empty()) continue; - if (!first_tag) + if (!res.empty()) res += ", "; - res += tmp->tag->name; - first_tag = false; + res += tag->name; } return res; } /* Add a tag to the tag_list, keep the list sorted */ -static void taglist_add_divetag(struct tag_entry **tag_list, const struct divetag *tag) +static void taglist_add_divetag(tag_list &list, const struct divetag *tag) { - struct tag_entry *next, *entry; - - while ((next = *tag_list) != NULL) { - int cmp = next->tag->name.compare(tag->name); - - /* Already have it? */ - if (!cmp) - return; - /* Is the entry larger? If so, insert here */ - if (cmp > 0) - break; - /* Continue traversing the list */ - tag_list = &next->next; - } - - /* Insert in front of it */ - entry = (tag_entry *)malloc(sizeof(struct tag_entry)); - entry->next = next; - entry->tag = tag; - *tag_list = entry; + // Use binary search to enter at sorted position + auto it = std::lower_bound(list.begin(), list.end(), tag, + [](const struct divetag *tag1, const struct divetag *tag2) + { return tag1->name < tag2->name; }); + // Don't add if it already exists + if (it == list.end() || (*it)->name != tag->name) + list.insert(it, tag); } -static const divetag *register_tag(const char *s, const char *source) +static const divetag *register_tag(std::string s, std::string source) { // binary search auto it = std::lower_bound(g_tag_list.begin(), g_tag_list.end(), s, - [](const std::unique_ptr &tag, const char *s) + [](const std::unique_ptr &tag, const std::string &s) { return tag->name < s; }); - if (it == g_tag_list.end() || (*it)->name != s) { - std::string source_s = empty_string(source) ? std::string() : std::string(source); - it = g_tag_list.insert(it, std::make_unique(s, source)); - } + if (it == g_tag_list.end() || (*it)->name != s) + it = g_tag_list.insert(it, std::make_unique(std::move(s), std::move(source))); return it->get(); } -extern "C" void taglist_add_tag(struct tag_entry **tag_list, const char *tag) +void taglist_add_tag(tag_list &list, const std::string &tag) { bool is_default_tag = std::find_if(std::begin(default_tags), std::end(default_tags), [&tag] (const char *default_tag) { return tag == default_tag; }); /* Only translate default tags */ /* TODO: Do we really want to translate user-supplied tags if they happen to be known!? */ - const char *translation = is_default_tag ? translate("gettextFromC", tag) : tag; - const char *source = is_default_tag ? tag : nullptr; - const struct divetag *d_tag = register_tag(translation, source); + std::string translation = is_default_tag ? translate("gettextFromC", tag.c_str()) : tag; + std::string source = is_default_tag ? tag : std::string(); + const struct divetag *d_tag = register_tag(std::move(translation), std::move(source)); - taglist_add_divetag(tag_list, d_tag); -} - -extern "C" void taglist_free(struct tag_entry *entry) -{ - STRUCTURED_LIST_FREE(struct tag_entry, entry, free) -} - -extern "C" struct tag_entry *taglist_copy(struct tag_entry *s) -{ - struct tag_entry *res; - STRUCTURED_LIST_COPY(struct tag_entry, s, res, copy_tl); - return res; + taglist_add_divetag(list, d_tag); } /* Merge src1 and src2, write to *dst */ -extern "C" void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2) +tag_list taglist_merge(const tag_list &src1, const tag_list &src2) { - struct tag_entry *entry; + tag_list dst; - for (entry = src1; entry; entry = entry->next) - taglist_add_divetag(dst, entry->tag); - for (entry = src2; entry; entry = entry->next) - taglist_add_divetag(dst, entry->tag); + for (const divetag *t: src1) + taglist_add_divetag(dst, t); + for (const divetag *t: src2) + taglist_add_divetag(dst, t); + return dst; } -extern "C" void taglist_init_global() +void taglist_init_global() { for (const char *s: default_tags) register_tag(translate("gettextFromC", s), s); diff --git a/core/tag.h b/core/tag.h index d7288804d..ada4d2700 100644 --- a/core/tag.h +++ b/core/tag.h @@ -3,18 +3,12 @@ #ifndef TAG_H #define TAG_H -#include - -#ifdef __cplusplus #include #include #include - -extern "C" { -#endif +#include struct divetag { -#ifdef __cplusplus /* * The name of the divetag. If a translation is available, name contains * the translated tag @@ -25,28 +19,18 @@ struct divetag { * This enables us to write a non-localized tag to the xml file. */ std::string source; - divetag(const char *n, const char *s) : name(n), source(s) - { - } -#endif + divetag(std::string name, std::string source); }; -struct tag_entry { - const struct divetag *tag; - struct tag_entry *next; -}; +using tag_list = std::vector; -void taglist_add_tag(struct tag_entry **tag_list, const char *tag); +void taglist_add_tag(tag_list &list, const std::string &tag); /* cleans up a list: removes empty tags and duplicates */ -void taglist_cleanup(struct tag_entry **tag_list); +void taglist_cleanup(tag_list &list); void taglist_init_global(); -void taglist_free(struct tag_entry *tag_list); -struct tag_entry *taglist_copy(struct tag_entry *s); -void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2); - -#ifdef __cplusplus +tag_list taglist_merge(const tag_list &src1, const tag_list &src2); /* * divetags are only stored once, each dive only contains @@ -55,22 +39,7 @@ void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_en */ extern std::vector> g_tag_list; -/* - * Writes all divetags form tag_list into internally allocated buffer - * Function returns pointer to allocated buffer - * Buffer contains comma separated list of tags names or null terminated string - */ -extern std::string taglist_get_tagstring(struct tag_entry *tag_list); - -} - -// C++ only functions - -#include - -/* Comma separated list of tags names or null terminated string */ -std::string taglist_get_tagstring(struct tag_entry *tag_list); - -#endif +/* Comma separated list of tags names or empty string */ +std::string taglist_get_tagstring(const tag_list &tags); #endif diff --git a/core/taxonomy.c b/core/taxonomy.c deleted file mode 100644 index c21a2e644..000000000 --- a/core/taxonomy.c +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "taxonomy.h" -#include "gettext.h" -#include "subsurface-string.h" -#include -#include - -char *taxonomy_category_names[TC_NR_CATEGORIES] = { - QT_TRANSLATE_NOOP("gettextFromC", "None"), - QT_TRANSLATE_NOOP("gettextFromC", "Ocean"), - QT_TRANSLATE_NOOP("gettextFromC", "Country"), - QT_TRANSLATE_NOOP("gettextFromC", "State"), - QT_TRANSLATE_NOOP("gettextFromC", "County"), - QT_TRANSLATE_NOOP("gettextFromC", "Town"), - QT_TRANSLATE_NOOP("gettextFromC", "City") -}; - -// these are the names for geoname.org -char *taxonomy_api_names[TC_NR_CATEGORIES] = { - "none", - "name", - "countryName", - "adminName1", - "adminName2", - "toponymName", - "adminName3" -}; - -static void alloc_taxonomy_table(struct taxonomy_data *t) -{ - if (!t->category) - t->category = calloc(TC_NR_CATEGORIES, sizeof(struct taxonomy)); -} - -void free_taxonomy(struct taxonomy_data *t) -{ - if (t) { - for (int i = 0; i < t->nr; i++) - free((void *)t->category[i].value); - free(t->category); - t->category = NULL; - t->nr = 0; - } -} - -void copy_taxonomy(const struct taxonomy_data *orig, struct taxonomy_data *copy) -{ - if (orig->category == NULL) { - free_taxonomy(copy); - } else { - alloc_taxonomy_table(copy); - for (int i = 0; i < TC_NR_CATEGORIES; i++) { - if (i < copy->nr) { - free((void *)copy->category[i].value); - copy->category[i].value = NULL; - } - if (i < orig->nr) { - copy->category[i] = orig->category[i]; - copy->category[i].value = copy_string(orig->category[i].value); - } - } - copy->nr = orig->nr; - } -} - -static int taxonomy_index_for_category(const struct taxonomy_data *t, enum taxonomy_category cat) -{ - for (int i = 0; i < t->nr; i++) { - if (t->category[i].category == cat) - return i; - } - return -1; -} - -const char *taxonomy_get_value(const struct taxonomy_data *t, enum taxonomy_category cat) -{ - int idx = taxonomy_index_for_category(t, cat); - return idx >= 0 ? t->category[idx].value : NULL; -} - -const char *taxonomy_get_country(const struct taxonomy_data *t) -{ - return taxonomy_get_value(t, TC_COUNTRY); -} - -void taxonomy_set_category(struct taxonomy_data *t, enum taxonomy_category category, const char *value, enum taxonomy_origin origin) -{ - int idx = taxonomy_index_for_category(t, category); - - if (idx < 0) { - alloc_taxonomy_table(t); // make sure we have taxonomy data allocated - if (t->nr == TC_NR_CATEGORIES - 1) { - // can't add another one - fprintf(stderr, "Error adding taxonomy category\n"); - return; - } - idx = t->nr++; - } else { - free((void *)t->category[idx].value); - t->category[idx].value = NULL; - } - t->category[idx].value = strdup(value); - t->category[idx].origin = origin; - t->category[idx].category = category; -} - -void taxonomy_set_country(struct taxonomy_data *t, const char *country, enum taxonomy_origin origin) -{ - fprintf(stderr, "%s: set the taxonomy country to %s\n", __func__, country); - taxonomy_set_category(t, TC_COUNTRY, country, origin); -} diff --git a/core/taxonomy.cpp b/core/taxonomy.cpp new file mode 100644 index 000000000..562f1541d --- /dev/null +++ b/core/taxonomy.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "taxonomy.h" +#include "errorhelper.h" +#include "gettext.h" +#include "pref.h" +#include "subsurface-string.h" +#include "gettextfromc.h" +#include +#include +#include +#include // for QT_TRANSLATE_NOOP + +const char *taxonomy_category_names[TC_NR_CATEGORIES] = { + QT_TRANSLATE_NOOP("gettextFromC", "None"), + QT_TRANSLATE_NOOP("gettextFromC", "Ocean"), + QT_TRANSLATE_NOOP("gettextFromC", "Country"), + QT_TRANSLATE_NOOP("gettextFromC", "State"), + QT_TRANSLATE_NOOP("gettextFromC", "County"), + QT_TRANSLATE_NOOP("gettextFromC", "Town"), + QT_TRANSLATE_NOOP("gettextFromC", "City") +}; + +// these are the names for geoname.org +const char *taxonomy_api_names[TC_NR_CATEGORIES] = { + "none", + "name", + "countryName", + "adminName1", + "adminName2", + "toponymName", + "adminName3" +}; + +std::string taxonomy_get_value(const taxonomy_data &t, enum taxonomy_category cat) +{ + auto it = std::find_if(t.begin(), t.end(), [cat] (const taxonomy &tax) { return tax.category == cat; }); + return it != t.end() ? it->value : std::string(); +} + +std::string taxonomy_get_country(const taxonomy_data &t) +{ + return taxonomy_get_value(t, TC_COUNTRY); +} + +void taxonomy_set_category(taxonomy_data &t, enum taxonomy_category cat, const std::string &value, enum taxonomy_origin origin) +{ + auto it = std::find_if(t.begin(), t.end(), [cat] (const taxonomy &tax) { return tax.category == cat; }); + if (it == t.end()) { + t.emplace_back(); + it = std::prev(t.end()); + } + it->value = value; + it->origin = origin; + it->category = cat; +} + +void taxonomy_set_country(taxonomy_data &t, const std::string &country, enum taxonomy_origin origin) +{ + report_info("%s: set the taxonomy country to %s\n", __func__, country.c_str()); + taxonomy_set_category(t, TC_COUNTRY, country, origin); +} + +std::string taxonomy_get_location_tags(const taxonomy_data &taxonomy, bool for_maintab) +{ + using namespace std::string_literals; + std::string locationTag; + + if (taxonomy.empty()) + 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 = "" + gettextFromC::tr("No dive site layout categories set in preferences!").toStdString() + + ""s; + return locationTag; + } + else if (!prefs_set) + return locationTag; + + if (for_maintab) + locationTag = "("s + gettextFromC::tr("Tags").toStdString() + ": "s; + else + locationTag = ""s; + std::string connector; + for (int i = 0; i < 3; i++) { + if (prefs.geocoding.category[i] == TC_NONE) + continue; + for (auto const &t: taxonomy) { + if (t.category == prefs.geocoding.category[i]) { + if (!t.value.empty()) { + locationTag += connector + t.value; + connector = " / "s; + } + break; + } + } + } + + if (for_maintab) + locationTag += ")"s; + else + locationTag += ""s; + return locationTag; +} diff --git a/core/taxonomy.h b/core/taxonomy.h index a2c03220c..b10673b1b 100644 --- a/core/taxonomy.h +++ b/core/taxonomy.h @@ -2,9 +2,8 @@ #ifndef TAXONOMY_H #define TAXONOMY_H -#ifdef __cplusplus -extern "C" { -#endif +#include +#include enum taxonomy_category { TC_NONE, @@ -24,29 +23,22 @@ enum taxonomy_origin { GEOCOPIED }; -extern char *taxonomy_category_names[TC_NR_CATEGORIES]; -extern char *taxonomy_api_names[TC_NR_CATEGORIES]; +extern const char *taxonomy_category_names[TC_NR_CATEGORIES]; +extern const char *taxonomy_api_names[TC_NR_CATEGORIES]; struct taxonomy { - int category; /* the category for this tag: ocean, country, admin_l1, admin_l2, localname, etc */ - const char *value; /* the value returned, parsed, or manually entered for that category */ - enum taxonomy_origin origin; + taxonomy_category category = TC_NONE; /* the category for this tag: ocean, country, admin_l1, admin_l2, localname, etc */ + std::string value; /* the value returned, parsed, or manually entered for that category */ + taxonomy_origin origin = GEOCODED; }; -/* the data block contains 3 taxonomy structures - unused ones have a tag value of NONE */ -struct taxonomy_data { - int nr; - struct taxonomy *category; -}; +/* the data block contains taxonomy structures - unused ones have a tag value of NONE */ +using taxonomy_data = std::vector; -void free_taxonomy(struct taxonomy_data *t); -void copy_taxonomy(const struct taxonomy_data *orig, struct taxonomy_data *copy); -const char *taxonomy_get_value(const struct taxonomy_data *t, enum taxonomy_category cat); -const char *taxonomy_get_country(const struct taxonomy_data *t); -void taxonomy_set_category(struct taxonomy_data *t, enum taxonomy_category category, const char *value, enum taxonomy_origin origin); -void taxonomy_set_country(struct taxonomy_data *t, const char *country, enum taxonomy_origin origin); +std::string taxonomy_get_value(const taxonomy_data &t, enum taxonomy_category cat); +std::string taxonomy_get_country(const taxonomy_data &t); +std::string taxonomy_get_location_tags(const taxonomy_data &taxonomy, bool for_maintab); +void taxonomy_set_category(taxonomy_data &t, enum taxonomy_category category, const std::string &value, enum taxonomy_origin origin); +void taxonomy_set_country(taxonomy_data &t, const std::string &country, enum taxonomy_origin origin); -#ifdef __cplusplus -} -#endif #endif // TAXONOMY_H diff --git a/core/time.cpp b/core/time.cpp index bf42554a6..c1e3c1ff2 100644 --- a/core/time.cpp +++ b/core/time.cpp @@ -33,7 +33,7 @@ * are unnecessary once you're counting minutes (32-bit minutes: * 8000+ years). */ -extern "C" void utc_mkdate(timestamp_t timestamp, struct tm *tm) +void utc_mkdate(timestamp_t timestamp, struct tm *tm) { static const unsigned int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, @@ -100,7 +100,7 @@ extern "C" void utc_mkdate(timestamp_t timestamp, struct tm *tm) tm->tm_mon = m; } -extern "C" timestamp_t utc_mktime(const struct tm *tm) +timestamp_t utc_mktime(const struct tm *tm) { static const int mdays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 @@ -147,7 +147,7 @@ extern "C" timestamp_t utc_mktime(const struct tm *tm) * out unused calculations. If it turns out to be a bottle neck * we will have to cache a struct tm per dive. */ -extern "C" int utc_year(timestamp_t timestamp) +int utc_year(timestamp_t timestamp) { struct tm tm; utc_mkdate(timestamp, &tm); @@ -162,7 +162,7 @@ extern "C" int utc_year(timestamp_t timestamp) * at throwing out unused calculations, so this is more efficient * than it looks. */ -extern "C" int utc_weekday(timestamp_t timestamp) +int utc_weekday(timestamp_t timestamp) { struct tm tm; utc_mkdate(timestamp, &tm); @@ -174,7 +174,7 @@ extern "C" int utc_weekday(timestamp_t timestamp) * an 64-bit decimal and return 64-bit timestamp. On failure or * if passed an empty string, return 0. */ -extern "C" timestamp_t parse_datetime(const char *s) +timestamp_t parse_datetime(const char *s) { int y, m, d; int hr, min, sec; @@ -225,3 +225,18 @@ const char *monthname(int mon) }; return translate("gettextFromC", month_array[mon]); } + +int gettimezoneoffset() +{ +#ifdef WIN32 + // Somewhat surprisingly, Windows doesn't have localtime_r (I thought this was POSIX?). + // Let's use the global timezone variable. + // Ultimately, use the portable C++20 API. + return static_cast(-timezone); +#else + time_t now = time(nullptr); + struct tm local; + localtime_r(&now, &local); + return local.tm_gmtoff; +#endif +} diff --git a/core/timer.c b/core/timer.c deleted file mode 100644 index 14ccd4666..000000000 --- a/core/timer.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2018 Jef Driesen - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include - -#ifdef _WIN32 -#define NOGDI -#include -#else -#include -#include -#ifdef HAVE_MACH_MACH_TIME_H -#include -#endif -#endif - -#include "timer.h" - -struct dc_timer_t { -#if defined (_WIN32) - LARGE_INTEGER timestamp; - LARGE_INTEGER frequency; -#elif defined (HAVE_CLOCK_GETTIME) - struct timespec timestamp; -#elif defined (HAVE_MACH_ABSOLUTE_TIME) - uint64_t timestamp; - mach_timebase_info_data_t info; -#else - struct timeval timestamp; -#endif -}; - -dc_status_t -dc_timer_new (dc_timer_t **out) -{ - dc_timer_t *timer = NULL; - - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - timer = (dc_timer_t *) malloc (sizeof (dc_timer_t)); - if (timer == NULL) { - return DC_STATUS_NOMEMORY; - } - -#if defined (_WIN32) - if (!QueryPerformanceFrequency(&timer->frequency) || - !QueryPerformanceCounter(&timer->timestamp)) { - free(timer); - return DC_STATUS_IO; - } -#elif defined (HAVE_CLOCK_GETTIME) - if (clock_gettime(CLOCK_MONOTONIC, &timer->timestamp) != 0) { - free(timer); - return DC_STATUS_IO; - } -#elif defined (HAVE_MACH_ABSOLUTE_TIME) - if (mach_timebase_info(&timer->info) != KERN_SUCCESS) { - free(timer); - return DC_STATUS_IO; - } - - timer->timestamp = mach_absolute_time(); -#else - if (gettimeofday (&timer->timestamp, NULL) != 0) { - free(timer); - return DC_STATUS_IO; - } -#endif - - *out = timer; - - return DC_STATUS_SUCCESS; -} - -dc_status_t -dc_timer_now (dc_timer_t *timer, dc_usecs_t *usecs) -{ - dc_status_t status = DC_STATUS_SUCCESS; - dc_usecs_t value = 0; - - if (timer == NULL) { - status = DC_STATUS_INVALIDARGS; - goto out; - } - -#if defined (_WIN32) - LARGE_INTEGER now; - if (!QueryPerformanceCounter(&now)) { - status = DC_STATUS_IO; - goto out; - } - - value = (now.QuadPart - timer->timestamp.QuadPart) * 1000000 / timer->frequency.QuadPart; -#elif defined (HAVE_CLOCK_GETTIME) - struct timespec now, delta; - if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { - status = DC_STATUS_IO; - goto out; - } - - if (now.tv_nsec < timer->timestamp.tv_nsec) { - delta.tv_nsec = 1000000000 + now.tv_nsec - timer->timestamp.tv_nsec; - delta.tv_sec = now.tv_sec - timer->timestamp.tv_sec - 1; - } else { - delta.tv_nsec = now.tv_nsec - timer->timestamp.tv_nsec; - delta.tv_sec = now.tv_sec - timer->timestamp.tv_sec; - } - - value = (dc_usecs_t) delta.tv_sec * 1000000 + delta.tv_nsec / 1000; -#elif defined (HAVE_MACH_ABSOLUTE_TIME) - uint64_t now = mach_absolute_time(); - value = (now - timer->timestamp) * timer->info.numer / (timer->info.denom * 1000); -#else - struct timeval now, delta; - if (gettimeofday (&now, NULL) != 0) { - status = DC_STATUS_IO; - goto out; - } - - timersub (&now, &timer->timestamp, &delta); - - value = (dc_usecs_t) delta.tv_sec * 1000000 + delta.tv_usec; -#endif - -out: - if (usecs) - *usecs = value; - - return status; -} - -dc_status_t -dc_timer_free (dc_timer_t *timer) -{ - free (timer); - - return DC_STATUS_SUCCESS; -} diff --git a/core/timer.h b/core/timer.h deleted file mode 100644 index 651991ff9..000000000 --- a/core/timer.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2018 Jef Driesen - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#ifndef DC_TIMER_H -#define DC_TIMER_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#if defined (_WIN32) && !defined (__GNUC__) -typedef unsigned __int64 dc_usecs_t; -#else -typedef unsigned long long dc_usecs_t; -#endif - -typedef struct dc_timer_t dc_timer_t; - -dc_status_t -dc_timer_new (dc_timer_t **timer); - -dc_status_t -dc_timer_now (dc_timer_t *timer, dc_usecs_t *usecs); - -dc_status_t -dc_timer_free (dc_timer_t *timer); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* DC_TIMER_H */ diff --git a/core/trip.c b/core/trip.c deleted file mode 100644 index 53c6a4b15..000000000 --- a/core/trip.c +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include "trip.h" -#include "dive.h" -#include "divelog.h" -#include "subsurface-time.h" -#include "subsurface-string.h" -#include "selection.h" -#include "table.h" -#include "core/errorhelper.h" - -#ifdef DEBUG_TRIP -void dump_trip_list(void) -{ - dive_trip_t *trip; - int i = 0; - timestamp_t last_time = 0; - - for (i = 0; i < divelog.trips->nr; ++i) { - struct tm tm; - trip = divelog.trips->trips[i]; - utc_mkdate(trip_date(trip), &tm); - if (trip_date(trip) < last_time) - printf("\n\ntrip_table OUT OF ORDER!!!\n\n\n"); - printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n", - trip->autogen ? "autogen " : "", - i + 1, trip->location, - tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, - trip->dives.nr, trip); - last_time = trip_date(trip); - } - printf("-----\n"); -} -#endif - -/* free resources associated with a trip structure */ -void free_trip(dive_trip_t *trip) -{ - if (trip) { - free(trip->location); - free(trip->notes); - free(trip->dives.dives); - free(trip); - } -} - -/* Trip table functions */ -static MAKE_GET_IDX(trip_table, struct dive_trip *, trips) -static MAKE_GROW_TABLE(trip_table, struct dive_trip *, trips) -static MAKE_GET_INSERTION_INDEX(trip_table, struct dive_trip *, trips, trip_less_than) -static MAKE_ADD_TO(trip_table, struct dive_trip *, trips) -static MAKE_REMOVE_FROM(trip_table, trips) -MAKE_SORT(trip_table, struct dive_trip *, trips, comp_trips) -MAKE_REMOVE(trip_table, struct dive_trip *, trip) -MAKE_CLEAR_TABLE(trip_table, trips, trip) -MAKE_MOVE_TABLE(trip_table, trips) - -timestamp_t trip_date(const struct dive_trip *trip) -{ - if (!trip || trip->dives.nr == 0) - return 0; - return trip->dives.dives[0]->when; -} - -timestamp_t trip_enddate(const struct dive_trip *trip) -{ - if (!trip || trip->dives.nr == 0) - return 0; - return dive_endtime(trip->dives.dives[trip->dives.nr - 1]); -} - -/* check if we have a trip right before / after this dive */ -bool is_trip_before_after(const struct dive *dive, bool before) -{ - int idx = get_idx_by_uniq_id(dive->id); - if (before) { - const struct dive *d = get_dive(idx - 1); - if (d && d->divetrip) - return true; - } else { - const struct dive *d = get_dive(idx + 1); - if (d && d->divetrip) - return true; - } - return false; -} - -/* Add dive to a trip. Caller is responsible for removing dive - * from trip beforehand. */ -void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) -{ - if (dive->divetrip == trip) - return; - if (dive->divetrip) - report_info("Warning: adding dive to trip that has trip set\n"); - insert_dive(&trip->dives, dive); - dive->divetrip = trip; -} - -/* remove a dive from the trip it's associated to, but don't delete the - * trip if this was the last dive in the trip. the caller is responsible - * for removing the trip, if the trip->dives.nr went to 0. - */ -struct dive_trip *unregister_dive_from_trip(struct dive *dive) -{ - dive_trip_t *trip = dive->divetrip; - - if (!trip) - return NULL; - - remove_dive(dive, &trip->dives); - dive->divetrip = NULL; - return trip; -} - -static void delete_trip(dive_trip_t *trip, struct trip_table *trip_table_arg) -{ - remove_trip(trip, trip_table_arg); - free_trip(trip); -} - -void remove_dive_from_trip(struct dive *dive, struct trip_table *trip_table_arg) -{ - struct dive_trip *trip = unregister_dive_from_trip(dive); - if (trip && trip->dives.nr == 0) - delete_trip(trip, trip_table_arg); -} - -dive_trip_t *alloc_trip(void) -{ - dive_trip_t *res = calloc(1, sizeof(dive_trip_t)); - res->id = dive_getUniqID(); - return res; -} - -/* insert the trip into the trip table */ -void insert_trip(dive_trip_t *dive_trip, struct trip_table *trip_table_arg) -{ - int idx = trip_table_get_insertion_index(trip_table_arg, dive_trip); - add_to_trip_table(trip_table_arg, idx, dive_trip); -#ifdef DEBUG_TRIP - dump_trip_list(); -#endif -} - -dive_trip_t *create_trip_from_dive(struct dive *dive) -{ - dive_trip_t *trip; - - trip = alloc_trip(); - trip->location = copy_string(get_dive_location(dive)); - - return trip; -} - -/* random threshold: three days without diving -> new trip - * this works very well for people who usually dive as part of a trip and don't - * regularly dive at a local facility; this is why trips are an optional feature */ -#define TRIP_THRESHOLD 3600 * 24 * 3 - -/* - * Find a trip a new dive should be autogrouped with. If no such trips - * exist, allocate a new trip. The bool "*allocated" is set to true - * if a new trip was allocated. - */ -dive_trip_t *get_trip_for_new_dive(struct dive *new_dive, bool *allocated) -{ - struct dive *d; - dive_trip_t *trip; - int i; - - /* Find dive that is within TRIP_THRESHOLD of current dive */ - for_each_dive(i, d) { - /* Check if we're past the range of possible dives */ - if (d->when >= new_dive->when + TRIP_THRESHOLD) - break; - - if (d->when + TRIP_THRESHOLD >= new_dive->when && d->divetrip) { - /* Found a dive with trip in the range */ - *allocated = false; - return d->divetrip; - } - } - - /* Didn't find a trip -> allocate a new one */ - trip = create_trip_from_dive(new_dive); - trip->autogen = true; - *allocated = true; - return trip; -} - -/* lookup of trip in main trip_table based on its id */ -dive_trip_t *get_trip_by_uniq_id(int tripId) -{ - for (int i = 0; i < divelog.trips->nr; i++) { - if (divelog.trips->trips[i]->id == tripId) - return divelog.trips->trips[i]; - } - return NULL; -} - -/* Check if two trips overlap time-wise up to trip threshold. */ -bool trips_overlap(const struct dive_trip *t1, const struct dive_trip *t2) -{ - /* First, handle the empty-trip cases. */ - if (t1->dives.nr == 0 || t2->dives.nr == 0) - return 0; - - if (trip_date(t1) < trip_date(t2)) - return trip_enddate(t1) + TRIP_THRESHOLD >= trip_date(t2); - else - return trip_enddate(t2) + TRIP_THRESHOLD >= trip_date(t1); -} - -/* - * Collect dives for auto-grouping. Pass in first dive which should be checked. - * Returns range of dives that should be autogrouped and trip it should be - * associated to. If the returned trip was newly allocated, the last bool - * is set to true. Caller still has to register it in the system. Note - * whereas this looks complicated - it is needed by the undo-system, which - * manually injects the new trips. If there are no dives to be autogrouped, - * return NULL. - */ -dive_trip_t *get_dives_to_autogroup(struct dive_table *table, int start, int *from, int *to, bool *allocated) -{ - int i; - struct dive *lastdive = NULL; - - /* Find first dive that should be merged and remember any previous - * dive that could be merged into. - */ - for (i = start; i < table->nr; i++) { - struct dive *dive = table->dives[i]; - dive_trip_t *trip; - - if (dive->divetrip) { - lastdive = dive; - continue; - } - - /* Only consider dives that have not been explicitly removed from - * a dive trip by the user. */ - if (dive->notrip) { - lastdive = NULL; - continue; - } - - /* We found a dive, let's see if we have to allocate a new trip */ - if (!lastdive || dive->when >= lastdive->when + TRIP_THRESHOLD) { - /* allocate new trip */ - trip = create_trip_from_dive(dive); - trip->autogen = true; - *allocated = true; - } else { - /* use trip of previous dive */ - trip = lastdive->divetrip; - *allocated = false; - } - - // Now, find all dives that will be added to this trip - lastdive = dive; - *from = i; - for (*to = *from + 1; *to < table->nr; (*to)++) { - dive = table->dives[*to]; - if (dive->divetrip || dive->notrip || - dive->when >= lastdive->when + TRIP_THRESHOLD) - break; - if (get_dive_location(dive) && !trip->location) - trip->location = copy_string(get_dive_location(dive)); - lastdive = dive; - } - return trip; - } - - /* Did not find anyhting - mark as end */ - return NULL; -} - -/* Out of two strings, copy the string that is not empty (if any). */ -static char *copy_non_empty_string(const char *a, const char *b) -{ - return copy_string(empty_string(b) ? a : b); -} - -/* This combines the information of two trips, generating a - * new trip. To support undo, we have to preserve the old trips. */ -dive_trip_t *combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b) -{ - dive_trip_t *trip; - - trip = alloc_trip(); - trip->location = copy_non_empty_string(trip_a->location, trip_b->location); - trip->notes = copy_non_empty_string(trip_a->notes, trip_b->notes); - - return trip; -} - -/* Trips are compared according to the first dive in the trip. */ -int comp_trips(const struct dive_trip *a, const struct dive_trip *b) -{ - /* This should never happen, nevertheless don't crash on trips - * with no (or worse a negative number of) dives. */ - if (a->dives.nr <= 0) - return b->dives.nr <= 0 ? 0 : -1; - if (b->dives.nr <= 0) - return 1; - return comp_dives(a->dives.dives[0], b->dives.dives[0]); -} - -bool trip_less_than(const struct dive_trip *a, const struct dive_trip *b) -{ - return comp_trips(a, b) < 0; -} - -static bool is_same_day(timestamp_t trip_when, timestamp_t dive_when) -{ - static timestamp_t twhen = (timestamp_t) 0; - static struct tm tmt; - struct tm tmd; - - utc_mkdate(dive_when, &tmd); - - if (twhen != trip_when) { - twhen = trip_when; - utc_mkdate(twhen, &tmt); - } - - return (tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year); -} - -bool trip_is_single_day(const struct dive_trip *trip) -{ - if (trip->dives.nr <= 1) - return true; - return is_same_day(trip->dives.dives[0]->when, - trip->dives.dives[trip->dives.nr - 1]->when); -} - -int trip_shown_dives(const struct dive_trip *trip) -{ - int res = 0; - for (int i = 0; i < trip->dives.nr; ++i) { - if (!trip->dives.dives[i]->hidden_by_filter) - res++; - } - return res; -} diff --git a/core/trip.cpp b/core/trip.cpp new file mode 100644 index 000000000..6bd9ad72a --- /dev/null +++ b/core/trip.cpp @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "trip.h" +#include "dive.h" +#include "divelog.h" +#include "errorhelper.h" +#include "range.h" +#include "subsurface-time.h" +#include "subsurface-string.h" +#include "selection.h" + +dive_trip::dive_trip() : id(dive_getUniqID()) +{ +} + +dive_trip::~dive_trip() = default; + +timestamp_t dive_trip::date() const +{ + if (dives.empty()) + return 0; + return dives[0]->when; +} + +static timestamp_t trip_enddate(const struct dive_trip &trip) +{ + if (trip.dives.empty()) + return 0; + return trip.dives.back()->endtime(); +} + +/* Add dive to a trip. Caller is responsible for removing dive + * from trip beforehand. */ +void dive_trip::add_dive(struct dive *dive) +{ + if (dive->divetrip == this) + return; + if (dive->divetrip) + report_info("Warning: adding dive to trip, which already has a trip set"); + range_insert_sorted(dives, dive, comp_dives_ptr); + dive->divetrip = this; +} + +/* remove a dive from the trip it's associated to, but don't delete the + * trip if this was the last dive in the trip. the caller is responsible + * for removing the trip, if the trip->dives.size() went to 0. + */ +struct dive_trip *unregister_dive_from_trip(struct dive *dive) +{ + dive_trip *trip = dive->divetrip; + + if (!trip) + return NULL; + + range_remove(trip->dives, dive); + dive->divetrip = NULL; + return trip; +} + +std::unique_ptr create_trip_from_dive(const struct dive *dive) +{ + auto trip = std::make_unique(); + trip->location = dive->get_location(); + + return trip; +} + +/* random threshold: three days without diving -> new trip + * this works very well for people who usually dive as part of a trip and don't + * regularly dive at a local facility; this is why trips are an optional feature */ +#define TRIP_THRESHOLD 3600 * 24 * 3 + +/* + * Find a trip a new dive should be autogrouped with. If no such trips + * exist, allocate a new trip. A unique_ptr is returned if a new trip + * was allocated. The caller has to store it. + */ +std::pair> get_trip_for_new_dive(const struct divelog &log, const struct dive *new_dive) +{ + /* Find dive that is within TRIP_THRESHOLD of current dive */ + for (auto &d: log.dives) { + /* Check if we're past the range of possible dives */ + if (d->when >= new_dive->when + TRIP_THRESHOLD) + break; + + if (d->when + TRIP_THRESHOLD >= new_dive->when && d->divetrip) + return { d->divetrip, nullptr }; /* Found a dive with trip in the range */ + } + + /* Didn't find a trip -> allocate a new one */ + auto trip = create_trip_from_dive(new_dive); + trip->autogen = true; + auto t = trip.get(); + return { t, std::move(trip) }; +} + +/* Check if two trips overlap time-wise up to trip threshold. */ +bool trips_overlap(const struct dive_trip &t1, const struct dive_trip &t2) +{ + /* First, handle the empty-trip cases. */ + if (t1.dives.empty() || t2.dives.empty()) + return 0; + + if (t1.date() < t2.date()) + return trip_enddate(t1) + TRIP_THRESHOLD >= t2.date(); + else + return trip_enddate(t2) + TRIP_THRESHOLD >= t1.date(); +} + +/* + * Collect dives for auto-grouping. Pass in first dive which should be checked. + * Returns range of dives that should be autogrouped and trip it should be + * associated to. If the returned trip was newly allocated, a std::unique_ptr<> + * to the trip is returned. + * is set to true. Caller still has to register it in the system. Note + * whereas this looks complicated - it is needed by the undo-system, which + * manually injects the new trips. If there are no dives to be autogrouped, + * return NULL. + */ +std::vector get_dives_to_autogroup(const struct dive_table &table) +{ + std::vector res; + struct dive *lastdive = NULL; + + /* Find first dive that should be merged and remember any previous + * dive that could be merged into. + */ + for (size_t i = 0; i < table.size(); ++i) { + auto &dive = table[i]; + + if (dive->divetrip) { + lastdive = dive.get(); + continue; + } + + /* Only consider dives that have not been explicitly removed from + * a dive trip by the user. */ + if (dive->notrip) { + lastdive = NULL; + continue; + } + + /* We found a dive, let's see if we have to allocate a new trip */ + std::unique_ptr allocated; + dive_trip *trip; + if (!lastdive || dive->when >= lastdive->when + TRIP_THRESHOLD) { + /* allocate new trip */ + allocated = create_trip_from_dive(dive.get()); + allocated->autogen = true; + trip = allocated.get(); + } else { + /* use trip of previous dive */ + trip = lastdive->divetrip; + } + + // Now, find all dives that will be added to this trip + lastdive = dive.get(); + size_t to; + for (to = i + 1; to < table.size(); to++) { + auto &dive = table[to]; + if (dive->divetrip || dive->notrip || + dive->when >= lastdive->when + TRIP_THRESHOLD) + break; + if (trip->location.empty()) + trip->location = dive->get_location(); + lastdive = dive.get(); + } + res.push_back({ i, to, trip, std::move(allocated) }); + i = to - 1; + } + + return res; +} + +/* Out of two strings, get the string that is not empty (if any). */ +static std::string non_empty_string(const std::string &a, const std::string &b) +{ + return b.empty() ? a : b; +} + +/* This combines the information of two trips, generating a + * new trip. To support undo, we have to preserve the old trips. */ +std::unique_ptr combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b) +{ + auto trip = std::make_unique(); + + trip->location = non_empty_string(trip_a->location, trip_b->location); + trip->notes = non_empty_string(trip_a->notes, trip_b->notes); + + return trip; +} + +/* Trips are compared according to the first dive in the trip. */ +int comp_trips(const struct dive_trip &a, const struct dive_trip &b) +{ + // To make sure that trips never compare equal, compare by + // address if both are empty. + if (&a == &b) + return 0; // reflexivity. shouldn't happen. + if (a.dives.empty() && b.dives.empty()) + return &a < &b ? -1 : 1; + if (a.dives.empty()) + return -1; + if (b.dives.empty()) + return 1; + return comp_dives(*a.dives[0], *b.dives[0]); +} + +static bool is_same_day(timestamp_t trip_when, timestamp_t dive_when) +{ + static timestamp_t twhen = (timestamp_t) 0; + static struct tm tmt; + struct tm tmd; + + utc_mkdate(dive_when, &tmd); + + if (twhen != trip_when) { + twhen = trip_when; + utc_mkdate(twhen, &tmt); + } + + return (tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year); +} + +bool dive_trip::is_single_day() const +{ + if (dives.size() <= 1) + return true; + return is_same_day(dives.front()->when, dives.back()->when); +} + +int dive_trip::shown_dives() const +{ + return std::count_if(dives.begin(), dives.end(), + [](const dive *d) { return !d->hidden_by_filter; }); +} + +void dive_trip::sort_dives() +{ + std::sort(dives.begin(), dives.end(), [] (dive *d1, dive *d2) { return comp_dives(*d1, *d2) < 0; }); +} diff --git a/core/trip.h b/core/trip.h index 249fa464c..2aa5f274b 100644 --- a/core/trip.h +++ b/core/trip.h @@ -4,71 +4,52 @@ #include "divelist.h" -#ifdef __cplusplus -extern "C" { -#endif +struct divelog; -typedef struct dive_trip +struct dive_trip { - char *location; - char *notes; - struct dive_table dives; + std::string location; + std::string notes; + std::vector dives; int id; /* unique ID for this trip: used to pass trips through QML. */ /* Used by the io-routines to mark trips that have already been written. */ - bool saved; - bool autogen; - bool selected; -} dive_trip_t; + bool saved = false; + bool autogen = false; + bool selected = false; -typedef struct trip_table { - int nr, allocated; - struct dive_trip **trips; -} trip_table_t; + dive_trip(); + ~dive_trip(); -static const trip_table_t empty_trip_table = { 0, 0, (struct dive_trip **)0 }; + void sort_dives(); + void add_dive(struct dive *); + timestamp_t date() const; + bool is_single_day() const; + int shown_dives() const; +}; + +int comp_trips(const dive_trip &t1, const dive_trip &t2); -extern void add_dive_to_trip(struct dive *, dive_trip_t *); extern struct dive_trip *unregister_dive_from_trip(struct dive *dive); -extern void remove_dive_from_trip(struct dive *dive, struct trip_table *trip_table_arg); -extern void insert_trip(dive_trip_t *trip, struct trip_table *trip_table_arg); -extern int remove_trip(const dive_trip_t *trip, struct trip_table *trip_table_arg); -extern void free_trip(dive_trip_t *trip); -extern timestamp_t trip_date(const struct dive_trip *trip); -extern timestamp_t trip_enddate(const struct dive_trip *trip); +extern std::unique_ptr create_trip_from_dive(const struct dive *dive); -extern bool trip_less_than(const struct dive_trip *a, const struct dive_trip *b); -extern int comp_trips(const struct dive_trip *a, const struct dive_trip *b); -extern void sort_trip_table(struct trip_table *table); +// Result item of get_dives_to_autogroup() +struct dives_to_autogroup_result { + size_t from, to; // Group dives in the range [from, to) + dive_trip *trip; // Pointer to trip + std::unique_ptr created_trip; + // Is set if the trip was newly created - caller has to store it. +}; -extern dive_trip_t *alloc_trip(void); -extern dive_trip_t *create_trip_from_dive(struct dive *dive); -extern dive_trip_t *get_dives_to_autogroup(struct dive_table *table, int start, int *from, int *to, bool *allocated); -extern dive_trip_t *get_trip_for_new_dive(struct dive *new_dive, bool *allocated); -extern dive_trip_t *get_trip_by_uniq_id(int tripId); -extern bool trips_overlap(const struct dive_trip *t1, const struct dive_trip *t2); +extern std::vector get_dives_to_autogroup(const struct dive_table &table); +extern std::pair> get_trip_for_new_dive(const struct divelog &log, const struct dive *new_dive); +extern bool trips_overlap(const struct dive_trip &t1, const struct dive_trip &t2); -extern dive_trip_t *combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b); -extern bool is_trip_before_after(const struct dive *dive, bool before); -extern bool trip_is_single_day(const struct dive_trip *trip); -extern int trip_shown_dives(const struct dive_trip *trip); +extern std::unique_ptr combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b); -void move_trip_table(struct trip_table *src, struct trip_table *dst); -void clear_trip_table(struct trip_table *table); - -#ifdef DEBUG_TRIP -extern void dump_trip_list(void); -#endif - -#ifdef __cplusplus -} - -/* Make pointers to dive_trip and trip_table "Qt metatypes" so that they can be +/* Make pointers to dive_trip "Qt metatypes" so that they can be * passed through QVariants and through QML. See comment in dive.h. */ #include Q_DECLARE_METATYPE(struct dive_trip *); -Q_DECLARE_METATYPE(trip_table_t *); - -#endif #endif diff --git a/core/triptable.cpp b/core/triptable.cpp new file mode 100644 index 000000000..841a6b609 --- /dev/null +++ b/core/triptable.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "triptable.h" +#include "trip.h" + +#ifdef DEBUG_TRIP +void dump_trip_list() +{ + timestamp_t last_time = 0; + + for (auto &trip: divelog.trips) { + struct tm tm; + utc_mkdate(trip->date(), &tm); + if (trip->date() < last_time) + printf("\n\ntrip_table OUT OF ORDER!!!\n\n\n"); + printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n", + trip->autogen ? "autogen " : "", + i + 1, trip->location.c_str(), + tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, + static_cast(trip->dives.size()), trip.get()); + last_time = trip->date(); + } + printf("-----\n"); +} +#endif + +/* lookup of trip in main trip_table based on its id */ +dive_trip *trip_table::get_by_uniq_id(int tripId) const +{ + auto it = std::find_if(begin(), end(), [tripId](auto &t) { return t->id == tripId; }); + return it != end() ? it->get() : nullptr; +} diff --git a/core/triptable.h b/core/triptable.h new file mode 100644 index 000000000..73cba9652 --- /dev/null +++ b/core/triptable.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef TRIPTABLE_H +#define TRIPTABLE_H + +#include "owning_table.h" + +struct dive_trip; + +int comp_trips(const dive_trip &t1, const dive_trip &t2); + +struct trip_table : public sorted_owning_table { + dive_trip *get_by_uniq_id(int tripId) const; +}; + +#ifdef DEBUG_TRIP +extern void dump_trip_list(); +#endif + +/* Make pointers to trip_table "Qt metatypes" so that they can be + * passed through QVariants and through QML. See comment in dive.h. */ +#include +Q_DECLARE_METATYPE(trip_table *); + +#endif + diff --git a/core/uemis-downloader.cpp b/core/uemis-downloader.cpp index d9d04d366..011a41dd5 100644 --- a/core/uemis-downloader.cpp +++ b/core/uemis-downloader.cpp @@ -21,18 +21,23 @@ #include #include #include +#include #include +#include +#include #include "gettext.h" #include "libdivecomputer.h" #include "uemis.h" +#include "dive.h" #include "divelist.h" #include "divelog.h" #include "divesite.h" #include "errorhelper.h" #include "file.h" -#include "tag.h" +#include "format.h" #include "subsurface-time.h" +#include "tag.h" #include "core/qthelper.h" #include "core/subsurface-string.h" @@ -41,183 +46,153 @@ #define ERR_FS_FULL QT_TRANSLATE_NOOP("gettextFromC", "Uemis Zurich: the file system is full.\nDisconnect/reconnect the dive computer\nand click Retry") #define ERR_FS_SHORT_WRITE QT_TRANSLATE_NOOP("gettextFromC", "Short write to req.txt file.\nIs the Uemis Zurich plugged in correctly?") #define ERR_NO_FILES QT_TRANSLATE_NOOP("gettextFromC", "No dives to download.") -#define BUFLEN 2048 -#define BUFLEN 2048 -#define NUM_PARAM_BUFS 10 +constexpr size_t num_param_bufs = 10; // debugging setup //#define UEMIS_DEBUG 1 + 2 + 4 + 8 + 16 + 32 -#define UEMIS_MAX_FILES 4000 -#define UEMIS_MEM_FULL 1 -#define UEMIS_MEM_OK 0 -#define UEMIS_SPOT_BLOCK_SIZE 1 -#define UEMIS_DIVE_DETAILS_SIZE 2 -#define UEMIS_LOG_BLOCK_SIZE 10 -#define UEMIS_CHECK_LOG 1 -#define UEMIS_CHECK_DETAILS 2 -#define UEMIS_CHECK_SINGLE_DIVE 3 +static constexpr int uemis_max_files = 4000; +static constexpr int uemis_spot_block_size = 1; +static constexpr int uemis_dive_details_size = 2; +static constexpr int uemis_log_block_size = 10; + +enum class uemis_mem_status { + ok, full +}; + +enum class uemis_checkpoint { + log, details, single_dive +}; #if UEMIS_DEBUG static std::string home, user, d_time; static int debug_round = 0; -#define debugfile stderr #endif #if UEMIS_DEBUG & 64 /* we are reading from a copy of the filesystem, not the device - no need to wait */ -#define UEMIS_TIMEOUT 50 /* 50ns */ -#define UEMIS_LONG_TIMEOUT 500 /* 500ns */ -#define UEMIS_MAX_TIMEOUT 2000 /* 2ms */ +static constexpr int uemis_timeout = 50; /* 50ns */ +static constexpr int uemis_long_timeout = 500; /* 500ns */ +static constexpr int uemis_max_timeout = 2000; /* 2ms */ #else -#define UEMIS_TIMEOUT 50000 /* 50ms */ -#define UEMIS_LONG_TIMEOUT 500000 /* 500ms */ -#define UEMIS_MAX_TIMEOUT 2000000 /* 2s */ +static constexpr int uemis_timeout = 50000; /* 50ms */ +static constexpr int uemis_long_timeout = 500000; /* 500ms */ +static constexpr int uemis_max_timeout = 2000000; /* 2s */ #endif -static const char *param_buff[NUM_PARAM_BUFS]; +static uemis uemis_obj; +static std::array param_buff; static std::string reqtxt_path; -static int reqtxt_file; static int filenr; static int number_of_files; -static char *mbuf = NULL; -static int mbuf_size = 0; static int max_mem_used = -1; -static int next_table_index = 0; static int dive_to_read = 0; -static uint32_t mindiveid; -/* Linked list to remember already executed divespot download requests */ -struct divespot_mapping { - int divespot_id; - struct dive_site *dive_site; - struct divespot_mapping *next; -}; -static struct divespot_mapping *divespot_mapping = NULL; - -static void erase_divespot_mapping() -{ - struct divespot_mapping *tmp; - while (divespot_mapping != NULL) { - tmp = divespot_mapping; - divespot_mapping = tmp->next; - free(tmp); - } - divespot_mapping = NULL; -} - -static void add_to_divespot_mapping(int divespot_id, struct dive_site *ds) -{ - struct divespot_mapping *ndm = (struct divespot_mapping*)calloc(1, sizeof(struct divespot_mapping)); - struct divespot_mapping **pdm = &divespot_mapping; - struct divespot_mapping *cdm = *pdm; - - while (cdm && cdm->next) - cdm = cdm->next; - - ndm->divespot_id = divespot_id; - ndm->dive_site = ds; - ndm->next = NULL; - if (cdm) - cdm->next = ndm; - else - cdm = *pdm = ndm; -} - -static bool is_divespot_mappable(int divespot_id) -{ - struct divespot_mapping *dm = divespot_mapping; - while (dm) { - if (dm->divespot_id == divespot_id) - return true; - dm = dm->next; - } - return false; -} - -static struct dive_site *get_dive_site_by_divespot_id(int divespot_id) -{ - struct divespot_mapping *dm = divespot_mapping; - while (dm) { - if (dm->divespot_id == divespot_id) - return dm->dive_site; - dm = dm->next; - } - return NULL; -} +/* Hash map to remember already executed divespot download requests */ +static std::unordered_map divespot_mapping; /* helper function to parse the Uemis data structures */ -static void uemis_ts(char *buffer, void *_when) +static timestamp_t uemis_ts(std::string_view buffer) { struct tm tm; - timestamp_t *when = (timestamp_t *)_when; memset(&tm, 0, sizeof(tm)); - sscanf(buffer, "%d-%d-%dT%d:%d:%d", + sscanf(buffer.begin(), "%d-%d-%dT%d:%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); tm.tm_mon -= 1; - *when = utc_mktime(&tm); + return utc_mktime(&tm); +} + +/* helper function to make the std::from_chars() interface more + * palatable. + * std::from_chars(s.begin(), s.end(), v) + * works for the compilers we use, but is not guaranteed to work + * as per standard. + * see: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2007r0.html + */ +template +auto from_chars(std::string_view s, T &v) +{ + return std::from_chars(s.data(), s.data() + s.size(), v); +} + +/* Sadly, a number of supported compilers do not support from_chars() + * to double. Therefore, implement our own version. + * Frustrating, but oh well. TODO: Try to find the proper check for the + * existence of this function. + */ +static std::from_chars_result from_chars(const std::string &s, double &v) +{ + const char *end; + double res = ascii_strtod(s.c_str(), &end); + if (end == s.c_str()) + return { end, std::errc::invalid_argument }; + v = res; + return { end, std::errc() }; +} + +template <> +auto from_chars(std::string_view sv, double &v) +{ + std::string s(sv); // Generate a null-terminated string to work on. + return from_chars(s, v); } /* float minutes */ -static void uemis_duration(char *buffer, duration_t *duration) +static void uemis_duration(std::string_view buffer, duration_t &duration) { - duration->seconds = lrint(ascii_strtod(buffer, NULL) * 60); + from_chars(buffer, duration.seconds); } /* int cm */ -static void uemis_depth(char *buffer, depth_t *depth) +static void uemis_depth(std::string_view buffer, depth_t &depth) { - depth->mm = atoi(buffer) * 10; + if (from_chars(buffer, depth.mm).ec != std::errc::invalid_argument) + depth.mm *= 10; } -static void uemis_get_index(char *buffer, int *idx) +static void uemis_get_index(std::string_view buffer, int &idx) { - *idx = atoi(buffer); + from_chars(buffer, idx); } /* space separated */ -static void uemis_add_string(const char *buffer, char **text, const char *delimit) +static void uemis_add_string(std::string_view buffer, std::string &text, const char *delimit) { /* do nothing if this is an empty buffer (Uemis sometimes returns a single * space for empty buffers) */ - if (empty_string(buffer) || (*buffer == ' ' && *(buffer + 1) == '\0')) + if (buffer.empty() || buffer == " ") return; - if (!*text) { - *text = strdup(buffer); - } else { - char *buf = (char *)malloc(strlen(buffer) + strlen(*text) + 2); - strcpy(buf, *text); - strcat(buf, delimit); - strcat(buf, buffer); - free(*text); - *text = buf; - } + if (!text.empty()) + text += delimit; + text += buffer; } /* still unclear if it ever reports lbs */ -static void uemis_get_weight(char *buffer, weightsystem_t *weight, int diveid) +static void uemis_get_weight(std::string_view buffer, weightsystem_t &weight, int diveid) { - weight->weight.grams = uemis_get_weight_unit(diveid) ? - lbs_to_grams(ascii_strtod(buffer, NULL)) : - lrint(ascii_strtod(buffer, NULL) * 1000); - weight->description = translate("gettextFromC", "unknown"); + double val; + if (from_chars(buffer, val).ec != std::errc::invalid_argument) { + weight.weight.grams = uemis_obj.get_weight_unit(diveid) ? + lbs_to_grams(val) : lrint(val * 1000); + } + weight.description = translate("gettextFromC", "unknown"); } -static struct dive *uemis_start_dive(uint32_t deviceid) +static std::unique_ptr uemis_start_dive(uint32_t deviceid) { - struct dive *dive = alloc_dive(); - dive->dc.model = strdup("Uemis Zurich"); - dive->dc.deviceid = deviceid; + auto dive = std::make_unique(); + dive->dcs[0].model = "Uemis Zurich"; + dive->dcs[0].deviceid = deviceid; return dive; } static struct dive *get_dive_by_uemis_diveid(device_data_t *devdata, uint32_t object_id) { - for (int i = 0; i < devdata->log->dives->nr; i++) { - if (object_id == devdata->log->dives->dives[i]->dc.diveid) - return devdata->log->dives->dives[i]; + for (auto &d: devdata->log->dives) { + if (object_id == d->dcs[0].diveid) + return d.get(); } return NULL; } @@ -225,15 +200,13 @@ static struct dive *get_dive_by_uemis_diveid(device_data_t *devdata, uint32_t ob /* send text to the importer progress bar */ static void uemis_info(const char *fmt, ...) { - static char buffer[256]; va_list ap; va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); + progress_bar_text = vformat_string_std(fmt, ap); va_end(ap); - progress_bar_text = buffer; if (verbose) - report_info("Uemis downloader: %s", buffer); + report_info("Uemis downloader: %s", progress_bar_text.c_str()); } static long bytes_available(int file) @@ -249,7 +222,7 @@ static long bytes_available(int file) return result; } -static int number_of_file(const std::string& path) +static int number_of_file(const std::string &path) { int count = 0; #ifdef WIN32 @@ -281,77 +254,71 @@ static int number_of_file(const std::string& path) return count; } -static std::string build_filename(const std::string& path, const std::string& name) +static std::string build_filename(const std::string &path, const std::string &name) { - std::string str; #if WIN32 - str = path + "\\" + name; + return path + "\\" + name; #else - str = path + "/" + name; + return path + "/" + name; #endif - return str; -} -static std::string build_filename(const std::string& path, const char* name) -{ - return build_filename(path, std::string(name)); -} -static std::string build_filename(const char* path, const char* name) -{ - return build_filename(std::string(path), std::string(name)); } /* Check if there's a req.txt file and get the starting filenr from it. * Test for the maximum number of ANS files (I believe this is always * 4000 but in case there are differences depending on firmware, this * code is easy enough */ -static bool uemis_init(const char *path) +static bool uemis_init(const std::string &path) { - std::string ans_path; - int i; - erase_divespot_mapping(); - if (!path) + using namespace std::string_literals; + + divespot_mapping.clear(); + if (path.empty()) return false; /* let's check if this is indeed a Uemis DC */ - reqtxt_path = build_filename(path, "req.txt"); - reqtxt_file = subsurface_open(reqtxt_path.c_str(), O_RDONLY | O_CREAT, 0666); + reqtxt_path = build_filename(path, "req.txt"s); + int reqtxt_file = subsurface_open(reqtxt_path.c_str(), O_RDONLY | O_CREAT, 0666); if (reqtxt_file < 0) { #if UEMIS_DEBUG & 1 - fprintf(debugfile, ":EE req.txt can't be opened\n"); + report_info(":EE req.txt can't be opened\n"); #endif return false; } if (bytes_available(reqtxt_file) > 5) { char tmp[6]; - if (read(reqtxt_file, tmp, 5) != 5) + if (read(reqtxt_file, tmp, 5) != 5) { + close(reqtxt_file); return false; + } tmp[5] = '\0'; #if UEMIS_DEBUG & 2 - fprintf(debugfile, "::r req.txt \"%s\"\n", tmp); + report_info("::r req.txt \"%s\"\n", tmp); #endif - if (sscanf(tmp + 1, "%d", &filenr) != 1) + if (sscanf(tmp + 1, "%d", &filenr) != 1) { + close(reqtxt_file); return false; + } } else { filenr = 0; #if UEMIS_DEBUG & 2 - fprintf(debugfile, "::r req.txt skipped as there were fewer than 5 bytes\n"); + report_info("::r req.txt skipped as there were fewer than 5 bytes\n"); #endif } close(reqtxt_file); /* It would be nice if we could simply go back to the first set of * ANS files. But with a FAT filesystem that isn't possible */ - ans_path = build_filename(path, "ANS"); + std::string ans_path = build_filename(path, "ANS"s); number_of_files = number_of_file(ans_path); /* initialize the array in which we collect the answers */ - for (i = 0; i < NUM_PARAM_BUFS; i++) - param_buff[i] = ""; + for (std::string &s: param_buff) + s.clear(); return true; } -static void str_append_with_delim(char *s, const char *t) +static void str_append_with_delim(std::string &s, const std::string &t) { - int len = strlen(s); - snprintf(s + len, BUFLEN - len, "%s{", t); + s += t; + s += '{'; } /* The communication protocol with the DC is truly funky. @@ -366,7 +333,7 @@ static void trigger_response(int file, const char *command, int nr, long tailpos snprintf(fl, 8, "%s%04d", command, nr); #if UEMIS_DEBUG & 4 - fprintf(debugfile, ":tr %s (after seeks)\n", fl); + report_info(":tr %s (after seeks)\n", fl); #endif if (lseek(file, 0, SEEK_SET) == -1) goto fs_error; @@ -383,67 +350,40 @@ fs_error: close(file); } -static char *next_token(char **buf) +static std::string_view next_token(std::string_view &buf) { - char *q, *p = strchr(*buf, '{'); - if (p) - *p = '\0'; - else - p = *buf + strlen(*buf) - 1; - q = *buf; - *buf = p + 1; - return q; + size_t pos = buf.find('{'); + std::string_view res; + if (pos == std::string::npos) { + res = buf; + buf = std::string_view(); + } else { + res = buf.substr(0, pos); + buf = buf.substr(pos + 1); + } + return res; } /* poor man's tokenizer that understands a quoted delimiter ('{') */ -static char *next_segment(char *buf, int *offset, int size) +static std::string next_segment(const std::string &buf, int &offset) { - int i = *offset; - int seg_size; - bool done = false; - char *segment; + size_t i = static_cast(offset); + std::string segment; - while (!done) { - if (i < size) { - if (i < size - 1 && buf[i] == '\\' && - (buf[i + 1] == '\\' || buf[i + 1] == '{')) - memcpy(buf + i, buf + i + 1, size - i - 1); - else if (buf[i] == '{') - done = true; + while (i < buf.size()) { + if (i + 1 < buf.size() && buf[i] == '\\' && + (buf[i + 1] == '\\' || buf[i + 1] == '{')) { i++; - } else { - done = true; + } else if (buf[i] == '{') { + i++; + break; } + segment += buf[i++]; } - seg_size = i - *offset - 1; - if (seg_size < 0) - seg_size = 0; - segment = (char *)malloc(seg_size + 1); - memcpy(segment, buf + *offset, seg_size); - segment[seg_size] = '\0'; - *offset = i; + offset = i; return segment; } -/* a dynamically growing buffer to store the potentially massive responses. - * The binary data block can be more than 100k in size (base64 encoded) */ -static void buffer_add(char **buffer, int *buffer_size, char *buf) -{ - if (!buf) - return; - if (!*buffer) { - *buffer = strdup(buf); - *buffer_size = strlen(*buffer) + 1; - } else { - *buffer_size += strlen(buf); - *buffer = (char *)realloc(*buffer, *buffer_size); - strcat(*buffer, buf); - } -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "added \"%s\" to buffer - new length %d\n", buf, *buffer_size); -#endif -} - /* are there more ANS files we can check? */ static bool next_file(int max) { @@ -453,47 +393,42 @@ static bool next_file(int max) return true; } -/* try and do a quick decode - without trying to get to fancy in case the data +/* try and do a quick decode - without trying to get too fancy in case the data * straddles a block boundary... * we are parsing something that looks like this: * object_id{int{2{date{ts{2011-04-05T12:38:04{duration{float{12.000... */ -static char *first_object_id_val(char *buf) +static std::string first_object_id_val(std::string_view buf) { - char *object, *bufend; - if (!buf) - return NULL; - bufend = buf + strlen(buf); - object = strstr(buf, "object_id"); - if (object && object + 14 < bufend) { + size_t object = buf.find( "object_id"); + if (object != std::string::npos && object + 14 < buf.size()) { /* get the value */ - char tmp[36]; - char *p = object + 14; - char *t = tmp; + size_t p = object + 14; #if UEMIS_DEBUG & 4 char debugbuf[50]; - strncpy(debugbuf, object, 49); + strncpy(debugbuf, buf.begin() + object, 49); debugbuf[49] = '\0'; - fprintf(debugfile, "buf |%s|\n", debugbuf); + report_info("buf |%s|\n", debugbuf); #endif - while (p < bufend && *p != '{' && t < tmp + 9) - *t++ = *p++; - if (*p == '{') { + std::string res; + while (p < buf.size() && buf[p] != '{' && res.size() < 9) + res += buf[p++]; + if (buf[p] == '{') { /* found the object_id - let's quickly look for the date */ - if (strncmp(p, "{date{ts{", 9) == 0 && strstr(p, "{duration{") != NULL) { + std::string_view buf2 = buf.substr(p); + if (starts_with(buf2, "{date{ts{") && buf2.find("{duration{") != std::string::npos) { /* cool - that was easy */ - *t++ = ','; - *t++ = ' '; + res += ','; + res += ' '; /* skip the 9 characters we just found and take the date, ignoring the seconds * and replace the silly 'T' in the middle with a space */ - strncpy(t, p + 9, 16); - if (*(t + 10) == 'T') - *(t + 10) = ' '; - t += 16; + res += buf2.substr(9, 16); + size_t pos = res.size() - 16 + 10; + if (res[pos] == 'T') + res[pos] = ' '; } - *t = '\0'; - return strdup(tmp); + return res; } } return NULL; @@ -502,43 +437,44 @@ static char *first_object_id_val(char *buf) /* ultra-simplistic; it doesn't deal with the case when the object_id is * split across two chunks. It also doesn't deal with the discrepancy between * object_id and dive number as understood by the dive computer */ -static void show_progress(char *buf, const char *what) +static void show_progress(const std::string &buf, const char *what) { - char *val = first_object_id_val(buf); - if (val) { + std::string val = first_object_id_val(buf); + if (!val.empty()) { /* let the user know what we are working on */ #if UEMIS_DEBUG & 8 - fprintf(debugfile, "reading %s\n %s\n %s\n", what, val, buf); + report_info("reading %s\n %s\n %s\n", what, val.c_str(), buf.c_str()); #endif - uemis_info(translate("gettextFromC", "%s %s"), what, val); - free(val); + uemis_info(translate("gettextFromC", "%s %s"), what, val.c_str()); } } static void uemis_increased_timeout(int *timeout) { - if (*timeout < UEMIS_MAX_TIMEOUT) - *timeout += UEMIS_LONG_TIMEOUT; + if (*timeout < uemis_max_timeout) + *timeout += uemis_long_timeout; usleep(*timeout); } -static std::string build_ans_path(const std::string& path, int filenumber) +static std::string build_ans_path(const std::string &path, int filenumber) { - std::string intermediate, ans_path; + using namespace std::string_literals; - std::string fl = std::string("ANS") + std::to_string(filenumber) + ".TXT"; - intermediate = build_filename(path, "ANS"); - ans_path = build_filename(intermediate, fl); - return ans_path; + /* Clamp filenumber into the 0..9999 range. This is never necessary, + * as filenumber can never go above uemis_max_files, but gcc doesn't + * recognize that and produces very noisy warnings. */ + filenumber = filenumber < 0 ? 0 : filenumber % 10000; + + std::string fl = "ANS"s + std::to_string(filenumber) + ".TXT"s; + std::string intermediate = build_filename(path, "ANS"s); + return build_filename(intermediate, fl); } /* send a request to the dive computer and collect the answer */ -static bool uemis_get_answer(const char *path, const char *request, int n_param_in, - int n_param_out, const char **error_text) +static std::string uemis_get_answer(const std::string &path, const std::string &request, int n_param_in, + int n_param_out, std::string &error_text) { - int i = 0, file_length; - char sb[BUFLEN]; - char fl[13]; + int i = 0; char tmp[101]; const char *what = translate("gettextFromC", "data"); bool searching = true; @@ -547,82 +483,80 @@ static bool uemis_get_answer(const char *path, const char *request, int n_param_ bool found_answer = false; bool more_files = true; bool answer_in_mbuf = false; - std::string ans_path; int ans_file; - int timeout = UEMIS_LONG_TIMEOUT; + int timeout = uemis_long_timeout; - reqtxt_file = subsurface_open(reqtxt_path.c_str(), O_RDWR | O_CREAT, 0666); + int reqtxt_file = subsurface_open(reqtxt_path.c_str(), O_RDWR | O_CREAT, 0666); if (reqtxt_file < 0) { - *error_text = "can't open req.txt"; + error_text = "can't open req.txt"; #ifdef UEMIS_DEBUG - fprintf(debugfile, "open %s failed with errno %d\n", reqtxt_path, errno); + report_info("open %s failed with errno %d\n", reqtxt_path.c_str(), errno); #endif - return false; + return std::string(); } - snprintf(sb, BUFLEN, "n%04d12345678", filenr); + std::string sb; str_append_with_delim(sb, request); for (i = 0; i < n_param_in; i++) str_append_with_delim(sb, param_buff[i]); - if (!strcmp(request, "getDivelogs") || !strcmp(request, "getDeviceData") || !strcmp(request, "getDirectory") || - !strcmp(request, "getDivespot") || !strcmp(request, "getDive")) { + if (request == "getDivelogs" || request == "getDeviceData" || request == "getDirectory" || + request == "getDivespot" || request == "getDive") { answer_in_mbuf = true; - str_append_with_delim(sb, ""); - if (!strcmp(request, "getDivelogs")) + str_append_with_delim(sb, std::string()); + if (request == "getDivelogs") what = translate("gettextFromC", "dive log #"); - else if (!strcmp(request, "getDivespot")) + else if (request == "getDivespot") what = translate("gettextFromC", "dive spot #"); - else if (!strcmp(request, "getDive")) + else if (request == "getDive") what = translate("gettextFromC", "details for #"); } - str_append_with_delim(sb, ""); - file_length = strlen(sb); - snprintf(fl, 10, "%08d", file_length - 13); - memcpy(sb + 5, fl, strlen(fl)); + str_append_with_delim(sb, std::string()); + size_t file_length = sb.size(); + std::string header = format_string_std("n%04d%08lu", filenr, (unsigned long)file_length); + sb = header + sb; #if UEMIS_DEBUG & 4 - fprintf(debugfile, "::w req.txt \"%s\"\n", sb); + report_info("::w req.txt \"%s\"\n", sb.c_str()); #endif - int written = write(reqtxt_file, sb, strlen(sb)); - if (written == -1 || (size_t)written != strlen(sb)) { - *error_text = translate("gettextFromC", ERR_FS_SHORT_WRITE); - return false; + int written = write(reqtxt_file, sb.c_str(), sb.size()); + if (written == -1 || (size_t)written != sb.size()) { + error_text = translate("gettextFromC", ERR_FS_SHORT_WRITE); + close(reqtxt_file); + return std::string(); } if (!next_file(number_of_files)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); + error_text = translate("gettextFromC", ERR_FS_FULL); more_files = false; } trigger_response(reqtxt_file, "n", filenr, file_length); usleep(timeout); - free(mbuf); - mbuf = NULL; - mbuf_size = 0; + std::string mbuf; while (searching || assembling_mbuf) { if (import_thread_cancelled) - return false; - progress_bar_fraction = filenr / (double)UEMIS_MAX_FILES; - ans_path = build_ans_path(path, filenr - 1); + return std::string(); + progress_bar_fraction = filenr / (double)uemis_max_files; + std::string ans_path = build_ans_path(path, filenr - 1); ans_file = subsurface_open(ans_path.c_str(), O_RDONLY, 0666); if (ans_file < 0) { - *error_text = "can't open Uemis response file"; + error_text = "can't open Uemis response file"; #ifdef UEMIS_DEBUG - fprintf(debugfile, "open %s failed with errno %d\n", ans_path, errno); + report_info("open %s failed with errno %d\n", ans_path.c_str(), errno); #endif - return false; + return std::string(); } if (read(ans_file, tmp, 100) < 3) { close(ans_file); - return false; + return std::string(); } close(ans_file); #if UEMIS_DEBUG & 8 tmp[100] = '\0'; - fprintf(debugfile, "::t %s \"%s\"\n", ans_path, tmp); + report_info("::t %s \"%s\"\n", ans_path.c_str(), tmp); #elif UEMIS_DEBUG & 4 char pbuf[4]; pbuf[0] = tmp[0]; pbuf[1] = tmp[1]; pbuf[2] = tmp[2]; pbuf[3] = 0; - fprintf(debugfile, "::t %s \"%s...\"\n", ans_path, pbuf); + report_info("::t %s \"%s...\"\n", ans_path.c_str(), pbuf); #endif if (tmp[0] == '1') { searching = false; @@ -634,163 +568,155 @@ static bool uemis_get_answer(const char *path, const char *request, int n_param_ assembling_mbuf = false; if (assembling_mbuf) { if (!next_file(number_of_files)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); + error_text = translate("gettextFromC", ERR_FS_FULL); more_files = false; assembling_mbuf = false; } reqtxt_file = subsurface_open(reqtxt_path.c_str(), O_RDWR | O_CREAT, 0666); if (reqtxt_file < 0) { - *error_text = "can't open req.txt"; + error_text = "can't open req.txt"; report_info("open %s failed with errno %d", reqtxt_path.c_str(), errno); - return false; + return std::string(); } trigger_response(reqtxt_file, "n", filenr, file_length); } } else { if (!next_file(number_of_files - 1)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); + error_text = translate("gettextFromC", ERR_FS_FULL); more_files = false; assembling_mbuf = false; searching = false; } reqtxt_file = subsurface_open(reqtxt_path.c_str(), O_RDWR | O_CREAT, 0666); if (reqtxt_file < 0) { - *error_text = "can't open req.txt"; + error_text = "can't open req.txt"; report_info("open %s failed with errno %d", reqtxt_path.c_str(), errno); - return false; + return std::string(); } trigger_response(reqtxt_file, "r", filenr, file_length); uemis_increased_timeout(&timeout); } if (ismulti && more_files && tmp[0] == '1') { int size; - ans_path = build_ans_path(path, assembling_mbuf ? filenr - 2 : filenr - 1); + std::string ans_path = build_ans_path(std::string(path), assembling_mbuf ? filenr - 2 : filenr - 1); ans_file = subsurface_open(ans_path.c_str(), O_RDONLY, 0666); if (ans_file < 0) { - *error_text = "can't open Uemis response file"; + error_text = "can't open Uemis response file"; #ifdef UEMIS_DEBUG - fprintf(debugfile, "open %s failed with errno %d\n", ans_path, errno); + report_info("open %s failed with errno %d\n", ans_path.c_str(), errno); #endif - return false; + return std::string(); } size = bytes_available(ans_file); if (size > 3) { - char *buf; int r; if (lseek(ans_file, 3, SEEK_CUR) == -1) goto fs_error; - buf = (char *)malloc(size - 2); - if ((r = read(ans_file, buf, size - 3)) != size - 3) { - free(buf); + std::string buf(size - 3, ' '); + if ((r = read(ans_file, buf.data(), size - 3)) != size - 3) goto fs_error; - } - buf[r] = '\0'; - buffer_add(&mbuf, &mbuf_size, buf); + mbuf += buf; show_progress(buf, what); - free(buf); - param_buff[3]++; + if (param_buff[3].size() > 1) + param_buff[3] = param_buff[3].substr(1); } close(ans_file); - timeout = UEMIS_TIMEOUT; - usleep(UEMIS_TIMEOUT); + timeout = uemis_timeout; + usleep(uemis_timeout); } } if (more_files) { - int size = 0, j = 0; - char *buf = NULL; + int j = 0; + std::string buf; if (!ismulti) { - ans_path = build_ans_path(path, filenr - 1); + std::string ans_path = build_ans_path(std::string(path), filenr - 1); ans_file = subsurface_open(ans_path.c_str(), O_RDONLY, 0666); if (ans_file < 0) { - *error_text = "can't open Uemis response file"; + error_text = "can't open Uemis response file"; #ifdef UEMIS_DEBUG - fprintf(debugfile, "open %s failed with errno %d\n", ans_path, errno); + report_info("open %s failed with errno %d\n", ans_path.c_str(), errno); #endif - return false; + return std::string(); } - size = bytes_available(ans_file); + int size = bytes_available(ans_file); if (size > 3) { int r; if (lseek(ans_file, 3, SEEK_CUR) == -1) goto fs_error; - buf = (char *)malloc(size - 2); - if ((r = read(ans_file, buf, size - 3)) != size - 3) { - free(buf); + buf = std::string(size - 3, ' '); + if ((r = read(ans_file, buf.data(), size - 3)) != size - 3) goto fs_error; - } - buf[r] = '\0'; - buffer_add(&mbuf, &mbuf_size, buf); + mbuf += buf; show_progress(buf, what); #if UEMIS_DEBUG & 8 - fprintf(debugfile, "::r %s \"%s\"\n", ans_path, buf); + report_info("::r %s \"%s\"\n", ans_path.c_str(), buf.c_str()); #endif } - size -= 3; close(ans_file); } else { ismulti = false; } #if UEMIS_DEBUG & 8 - fprintf(debugfile, ":r: %s\n", buf); + report_info(":r: %s\n", buf.c_str()); #endif if (!answer_in_mbuf) - for (i = 0; i < n_param_out && j < size; i++) - param_buff[i] = next_segment(buf, &j, size); + for (i = 0; i < n_param_out && (size_t)j < buf.size(); i++) + param_buff[i] = next_segment(buf, j); found_answer = true; - free(buf); } #if UEMIS_DEBUG & 1 for (i = 0; i < n_param_out; i++) - fprintf(debugfile, "::: %d: %s\n", i, param_buff[i]); + report_info("::: %d: %s\n", i, param_buff[i].c_str()); #endif - return found_answer; + return found_answer ? std::move(mbuf) : std::string(); fs_error: close(ans_file); - return false; + return std::string(); } -static bool parse_divespot(char *buf) +static bool parse_divespot(const std::string &buf) { - char *bp = buf + 1; - char *tp = next_token(&bp); - char *tag, *type, *val; - char locationstring[1024] = ""; - int divespot, len; + std::string_view bp = std::string_view(buf).substr(1); + std::string_view tp = next_token(bp); + std::string_view tag, type, val; + std::string locationstring; + int divespot; double latitude = 0.0, longitude = 0.0; // dive spot got deleted, so fail here - if (strstr(bp, "deleted{bool{true")) + if (bp.find("deleted{bool{true") != std::string::npos) return false; // not a dive spot, fail here - if (strcmp(tp, "divespot")) + if (tp != "divespot") return false; do - tag = next_token(&bp); - while (*tag && strcmp(tag, "object_id")); - if (!*tag) + tag = next_token(bp); + while (!bp.empty() && tag != "object_id"); + if (bp.empty()) + return false; + next_token(bp); + val = next_token(bp); + if (from_chars(val, divespot).ec == std::errc::invalid_argument) return false; - next_token(&bp); - val = next_token(&bp); - divespot = atoi(val); do { - tag = next_token(&bp); - type = next_token(&bp); - val = next_token(&bp); - if (!strcmp(type, "string") && *val && strcmp(val, " ")) { - len = strlen(locationstring); - snprintf(locationstring + len, sizeof(locationstring) - len, - "%s%s", len ? ", " : "", val); - } else if (!strcmp(type, "float")) { - if (!strcmp(tag, "longitude")) - longitude = ascii_strtod(val, NULL); - else if (!strcmp(tag, "latitude")) - latitude = ascii_strtod(val, NULL); + tag = next_token(bp); + type = next_token(bp); + val = next_token(bp); + if (type == "string" && !val.empty() && val != " ") { + if (!locationstring.empty()) + locationstring += ", "; + locationstring += val; + } else if (type == "float") { + if (tag == "longitude") + from_chars(val, longitude); + else if (tag == "latitude") + from_chars(val, latitude); } - } while (tag && *tag); + } while (!tag.empty()); - uemis_set_divelocation(divespot, locationstring, longitude, latitude); + uemis_obj.set_divelocation(divespot, locationstring, longitude, latitude); return true; } @@ -798,67 +724,59 @@ static const char *suit[] = {"", QT_TRANSLATE_NOOP("gettextFromC", "wetsuit"), Q static const char *suit_type[] = {"", QT_TRANSLATE_NOOP("gettextFromC", "shorty"), QT_TRANSLATE_NOOP("gettextFromC", "vest"), QT_TRANSLATE_NOOP("gettextFromC", "long john"), QT_TRANSLATE_NOOP("gettextFromC", "jacket"), QT_TRANSLATE_NOOP("gettextFromC", "full suit"), QT_TRANSLATE_NOOP("gettextFromC", "2 pcs full suit")}; static const char *suit_thickness[] = {"", "0.5-2mm", "2-3mm", "3-5mm", "5-7mm", "8mm+", QT_TRANSLATE_NOOP("gettextFromC", "membrane")}; -static void parse_tag(struct dive *dive, char *tag, char *val) +static void parse_tag(struct dive *dive, std::string_view tag, std::string_view val) { /* we can ignore computer_id, water and gas as those are redundant * with the binary data and would just get overwritten */ #if UEMIS_DEBUG & 4 - if (strcmp(tag, "file_content")) - fprintf(debugfile, "Adding to dive %d : %s = %s\n", dive->dc.diveid, tag, val); + if (tag == "file_content") + report_info("Adding to dive %d : %s = %s\n", dive->dcs[0].diveid, std::string(tag).c_str(), std::string(val).c_str()); #endif - if (!strcmp(tag, "date")) { - uemis_ts(val, &dive->when); - } else if (!strcmp(tag, "duration")) { - uemis_duration(val, &dive->dc.duration); - } else if (!strcmp(tag, "depth")) { - uemis_depth(val, &dive->dc.maxdepth); - } else if (!strcmp(tag, "file_content")) { - uemis_parse_divelog_binary(val, dive); - } else if (!strcmp(tag, "altitude")) { - uemis_get_index(val, &dive->dc.surface_pressure.mbar); - } else if (!strcmp(tag, "f32Weight")) { - weightsystem_t ws = empty_weightsystem; - uemis_get_weight(val, &ws, dive->dc.diveid); - add_cloned_weightsystem(&dive->weightsystems, ws); - } else if (!strcmp(tag, "notes")) { - uemis_add_string(val, &dive->notes, " "); - } else if (!strcmp(tag, "u8DiveSuit")) { - if (*suit[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "u8DiveSuitType")) { - if (*suit_type[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit_type[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "u8SuitThickness")) { - if (*suit_thickness[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit_thickness[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "nickname")) { - uemis_add_string(val, &dive->buddy, ","); + if (tag == "date") { + dive->when = uemis_ts(val); + } else if (tag == "duration") { + uemis_duration(val, dive->dcs[0].duration); + } else if (tag == "depth") { + uemis_depth(val, dive->dcs[0].maxdepth); + } else if (tag == "file_content") { + uemis_obj.parse_divelog_binary(val, dive); + } else if (tag == "altitude") { + uemis_get_index(val, dive->dcs[0].surface_pressure.mbar); + } else if (tag == "f32Weight") { + weightsystem_t ws; + uemis_get_weight(val, ws, dive->dcs[0].diveid); + dive->weightsystems.push_back(std::move(ws)); + } else if (tag == "notes") { + uemis_add_string(val, dive->notes, " "); + } else if (tag == "u8DiveSuit") { + int idx = 0; + uemis_get_index(val, idx); + if (idx > 0 && idx < (int)std::size(suit)) + uemis_add_string(translate("gettextFromC", suit[idx]), dive->suit, " "); + } else if (tag == "u8DiveSuitType") { + int idx = 0; + uemis_get_index(val, idx); + if (idx > 0 && idx < (int)std::size(suit_type)) + uemis_add_string(translate("gettextFromC", suit_type[idx]), dive->suit, " "); + } else if (tag == "u8SuitThickness") { + int idx = 0; + uemis_get_index(val, idx); + if (idx > 0 && idx < (int)std::size(suit_thickness)) + uemis_add_string(translate("gettextFromC", suit_thickness[idx]), dive->suit, " "); + } else if (tag == "nickname") { + uemis_add_string(val, dive->buddy, ","); } } static bool uemis_delete_dive(device_data_t *devdata, uint32_t diveid) { - struct dive *dive = NULL; + auto it = std::find_if(devdata->log->dives.begin(), devdata->log->dives.end(), + [diveid] (auto &d) { return d->dcs[0].diveid == diveid; }); + if (it == devdata->log->dives.end()) + return false; - if (devdata->log->dives->dives[devdata->log->dives->nr - 1]->dc.diveid == diveid) { - /* we hit the last one in the array */ - dive = devdata->log->dives->dives[devdata->log->dives->nr - 1]; - } else { - for (int i = 0; i < devdata->log->dives->nr - 1; i++) { - if (devdata->log->dives->dives[i]->dc.diveid == diveid) { - dive = devdata->log->dives->dives[i]; - for (int x = i; x < devdata->log->dives->nr - 1; x++) - devdata->log->dives->dives[i] = devdata->log->dives->dives[x + 1]; - } - } - } - if (dive) { - devdata->log->dives->dives[--devdata->log->dives->nr] = NULL; - free_dive(dive); - - return true; - } - return false; + devdata->log->dives.erase(it); + return true; } /* This function is called for both dive log and dive information that we get @@ -872,59 +790,50 @@ static bool uemis_delete_dive(device_data_t *devdata, uint32_t diveid) * index into yet another data store that we read out later. In order to * correctly populate the location and gps data from that we need to remember * the addresses of those fields for every dive that references the dive spot. */ -static bool process_raw_buffer(device_data_t *devdata, uint32_t deviceid, char *inbuf, char **max_divenr, int *for_dive) +static bool process_raw_buffer(device_data_t *devdata, uint32_t deviceid, std::string_view buf, int &max_divenr, int *for_dive) { - char *buf = strdup(inbuf); - char *tp, *bp, *tag, *type, *val; + using namespace std::string_literals; bool done = false; - int inbuflen = strlen(inbuf); - char *endptr = buf + inbuflen; - bool is_log = false, is_dive = false; - char *sections[10]; - size_t s, nr_sections = 0; - struct dive *dive = NULL; - char dive_no[10]; + bool is_log = false; + std::vector sections; + std::unique_ptr owned_dive; // in log mode + struct dive *non_owned_dive = nullptr; // in dive (non-log) mode + int dive_no = 0; #if UEMIS_DEBUG & 8 - fprintf(debugfile, "p_r_b %s\n", inbuf); + report_info("p_r_b %s\n", std::string(buf).c_str()); #endif if (for_dive) *for_dive = -1; - bp = buf + 1; - tp = next_token(&bp); - if (strcmp(tp, "divelog") == 0) { + std::string_view bp = buf.substr(1); + std::string_view tp = next_token(bp); + if (tp == "divelog") { /* this is a dive log */ is_log = true; - tp = next_token(&bp); + tp = next_token(bp); /* is it a valid entry or nothing ? */ - if (strcmp(tp, "1.0") != 0 || strstr(inbuf, "divelog{1.0{{{{")) { - free(buf); + if (tp != "1.0" || buf.find("divelog{1.0{{{{") != std::string::npos) return false; - } - } else if (strcmp(tp, "dive") == 0) { + } else if (tp == "dive") { /* this is dive detail */ - is_dive = true; - tp = next_token(&bp); - if (strcmp(tp, "1.0") != 0) { - free(buf); + is_log = false; + tp = next_token(bp); + if (tp != "1.0") return false; - } } else { /* don't understand the buffer */ - free(buf); return false; } if (is_log) { - dive = uemis_start_dive(deviceid); + owned_dive = uemis_start_dive(deviceid); } else { /* remember, we don't know if this is the right entry, * so first test if this is even a valid entry */ - if (strstr(inbuf, "deleted{bool{true")) { + if (buf.find("deleted{bool{true") != std::string::npos) { #if UEMIS_DEBUG & 2 - fprintf(debugfile, "p_r_b entry deleted\n"); + report_info("p_r_b entry deleted\n"); #endif /* oops, this one isn't valid, suggest to try the previous one */ - free(buf); return false; } /* quickhack and workaround to capture the original dive_no @@ -933,109 +842,112 @@ static bool process_raw_buffer(device_data_t *devdata, uint32_t deviceid, char * * at the time it's being read the *dive variable is not set because * the dive_no tag comes before the object_id in the uemis ans file */ - dive_no[0] = '\0'; - char *dive_no_buf = strdup(inbuf); - char *dive_no_ptr = strstr(dive_no_buf, "dive_no{int{") + 12; - if (dive_no_ptr) { - char *dive_no_end = strstr(dive_no_ptr, "{"); - if (dive_no_end) { - *dive_no_end = '\0'; - strncpy(dive_no, dive_no_ptr, 9); - dive_no[9] = '\0'; + size_t dive_no_pos = buf.find("dive_no{int{"); + if (dive_no_pos != std::string::npos) { + dive_no_pos += 12; + size_t dive_no_end = buf.find('{', dive_no_pos); + if (dive_no_end != std::string::npos) { + std::string_view dive_no_str = buf.substr(dive_no_pos, dive_no_end - dive_no_pos); + if (from_chars(dive_no_str, dive_no).ec == std::errc::invalid_argument) + dive_no = 0; } } - free(dive_no_buf); } while (!done) { /* the valid buffer ends with a series of delimiters */ - if (bp >= endptr - 2 || !strcmp(bp, "{{")) + if (bp.size() < 2 || starts_with(bp, "{{")) break; - tag = next_token(&bp); + std::string_view tag = next_token(bp); /* we also end if we get an empty tag */ - if (*tag == '\0') + if (tag.empty()) break; - for (s = 0; s < nr_sections; s++) - if (!strcmp(tag, sections[s])) { - tag = next_token(&bp); - break; - } - type = next_token(&bp); - if (!strcmp(type, "1.0")) { + if (std::find(sections.begin(), sections.end(), tag) != sections.end()) + tag = next_token(bp); + std::string_view type = next_token(bp); + if (type == "1.0") { /* this tells us the sections that will follow; the tag here * is of the format dive-
*/ - sections[nr_sections] = strchr(tag, '-') + 1; + size_t pos = tag.find('-'); + if (pos != std::string::npos) + sections.push_back(tag.substr(pos + 1)); #if UEMIS_DEBUG & 4 - fprintf(debugfile, "Expect to find section %s\n", sections[nr_sections]); + report_info("Expect to find section %s\n", std::string(sections.back()).c_str()); #endif - if (nr_sections < sizeof(sections) / sizeof(*sections) - 1) - nr_sections++; continue; } - val = next_token(&bp); + std::string_view val = next_token(bp); #if UEMIS_DEBUG & 8 - if (strlen(val) < 20) - fprintf(debugfile, "Parsed %s, %s, %s\n*************************\n", tag, type, val); + if (val.size() < 20) + report_info("Parsed %s, %s, %s\n*************************\n", std::string(tag).c_str(), + std::string(type).c_str(), + std::string(val).c_str()); #endif - if (is_log && strcmp(tag, "object_id") == 0) { - free(*max_divenr); - *max_divenr = strdup(val); - dive->dc.diveid = atoi(val); + if (is_log) { + // Is log + if (tag == "object_id") { + from_chars(val, max_divenr); + owned_dive->dcs[0].diveid = max_divenr; #if UEMIS_DEBUG % 2 - fprintf(debugfile, "Adding new dive from log with object_id %d.\n", atoi(val)); + report_info("Adding new dive from log with object_id %d.\n", max_divenr); #endif - } else if (is_dive && strcmp(tag, "logfilenr") == 0) { - /* this one tells us which dive we are adding data to */ - dive = get_dive_by_uemis_diveid(devdata, atoi(val)); - if (strcmp(dive_no, "0")) - dive->number = atoi(dive_no); - if (for_dive) - *for_dive = atoi(val); - } else if (!is_log && dive && !strcmp(tag, "divespot_id")) { - int divespot_id = atoi(val); - if (divespot_id != -1) { - struct dive_site *ds = create_dive_site("from Uemis", devdata->log->sites); - unregister_dive_from_dive_site(dive); - add_dive_to_dive_site(dive, ds); - uemis_mark_divelocation(dive->dc.diveid, divespot_id, ds); } + parse_tag(owned_dive.get(), tag, val); + + if (tag == "file_content") + done = true; + /* done with one dive (got the file_content tag), but there could be more: + * a '{' indicates the end of the record - but we need to see another "{{" + * later in the buffer to know that the next record is complete (it could + * be a short read because of some error */ + if (done && bp.size() > 3) { + bp = bp.substr(1); + if (bp[0] != '{' && bp.find("{{") != std::string::npos) { + done = false; + devdata->log->dives.record_dive(std::move(owned_dive)); + owned_dive = uemis_start_dive(deviceid); + } + } + } else { + // Is dive + if (tag == "logfilenr") { + /* this one tells us which dive we are adding data to */ + int diveid = 0; + from_chars(val, diveid); + non_owned_dive = get_dive_by_uemis_diveid(devdata, diveid); + if (dive_no != 0) + non_owned_dive->number = dive_no; + if (for_dive) + *for_dive = diveid; + } else if (non_owned_dive && tag == "divespot_id") { + int divespot_id; + if (from_chars(val, divespot_id).ec != std::errc::invalid_argument) { + struct dive_site *ds = devdata->log->sites.create("from Uemis"s); + unregister_dive_from_dive_site(non_owned_dive); + ds->add_dive(non_owned_dive); + uemis_obj.mark_divelocation(non_owned_dive->dcs[0].diveid, divespot_id, ds); + } #if UEMIS_DEBUG & 2 - fprintf(debugfile, "Created divesite %d for diveid : %d\n", dive->dive_site->uuid, dive->dc.diveid); + report_info("Created divesite %d for diveid : %d\n", non_owned_dive->dive_site->uuid, non_owned_dive->dcs[0].diveid); #endif - } else if (dive) { - parse_tag(dive, tag, val); - } - if (is_log && !strcmp(tag, "file_content")) - done = true; - /* done with one dive (got the file_content tag), but there could be more: - * a '{' indicates the end of the record - but we need to see another "{{" - * later in the buffer to know that the next record is complete (it could - * be a short read because of some error */ - if (done && ++bp < endptr && *bp != '{' && strstr(bp, "{{")) { - done = false; - record_dive_to_table(dive, devdata->log->dives); - dive = uemis_start_dive(deviceid); + } else if (non_owned_dive) { + parse_tag(non_owned_dive, tag, val); + } } } if (is_log) { - if (dive->dc.diveid) { - record_dive_to_table(dive, devdata->log->dives); - } else { /* partial dive */ - free_dive(dive); - free(buf); + if (owned_dive->dcs[0].diveid) + devdata->log->dives.record_dive(std::move(owned_dive)); + else /* partial dive */ return false; - } } - free(buf); return true; } -static char *uemis_get_divenr(char *deviceidstr, struct dive_table *table, int force) +// Returns (mindiveid, maxdiveid) +static std::pair uemis_get_divenr(uint32_t deviceid, struct dive_table *table, int force) { - uint32_t deviceid, maxdiveid = 0; - int i; - char divenr[10]; - deviceid = atoi(deviceidstr); - mindiveid = 0xFFFFFFFF; + uint32_t maxdiveid = 0; + uint32_t mindiveid = 0xFFFFFFFF; /* * If we are are retrying after a disconnect/reconnect, we @@ -1047,61 +959,57 @@ static char *uemis_get_divenr(char *deviceidstr, struct dive_table *table, int f * * Otherwise, use the global dive table. */ - if (!force && !table->nr) - table = divelog.dives; + if (!force && table->empty()) + table = &divelog.dives; - for (i = 0; i < table->nr; i++) { - struct dive *d = table->dives[i]; - struct divecomputer *dc; + for (auto &d: *table) { if (!d) continue; - for_each_dc (d, dc) { - if (dc->model && !strcmp(dc->model, "Uemis Zurich") && - (dc->deviceid == 0 || dc->deviceid == 0x7fffffff || dc->deviceid == deviceid)) { - if (dc->diveid > maxdiveid) - maxdiveid = dc->diveid; - if (dc->diveid < mindiveid) - mindiveid = dc->diveid; + for (auto &dc: d->dcs) { + if (dc.model == "Uemis Zurich" && + (dc.deviceid == 0 || dc.deviceid == 0x7fffffff || dc.deviceid == deviceid)) { + if (dc.diveid > maxdiveid) + maxdiveid = dc.diveid; + if (dc.diveid < mindiveid) + mindiveid = dc.diveid; } } } - snprintf(divenr, 10, "%d", maxdiveid); - return strdup(divenr); + return std::make_pair(mindiveid, maxdiveid); } #if UEMIS_DEBUG -static int bufCnt = 0; -static bool do_dump_buffer_to_file(char *buf, const char *prefix) +static bool do_dump_buffer_to_file(std::string_view buf, const char *prefix) { - char path[100]; - char date[40]; - char obid[40]; - bool success; - if (!buf) + using namespace std::string_literals; + static int bufCnt = 0; + if (buf.empty()) return false; - if (strstr(buf, "date{ts{")) - strncpy(date, strstr(buf, "date{ts{"), sizeof(date)); - else - strncpy(date, "date{ts{no-date{", sizeof(date)); + size_t datepos = buf.find("date{ts{"); + std::string pdate; + if (datepos != std::string::npos) { + std::string_view ptr1 = buf.substr(datepos); + next_token(ptr1); + next_token(ptr1); + pdate = next_token(ptr1); + } else { + pdate = "date{ts{no-date{"s; + } - if (!strstr(buf, "object_id{int{")) + size_t obidpos = buf.find("object_id{int{"); + if (obidpos == std::string::npos) return false; - strncpy(obid, strstr(buf, "object_id{int{"), sizeof(obid)); - char *ptr1 = strstr(date, "date{ts{"); - char *ptr2 = strstr(obid, "object_id{int{"); - char *pdate = next_token(&ptr1); - pdate = next_token(&ptr1); - pdate = next_token(&ptr1); - char *pobid = next_token(&ptr2); - pobid = next_token(&ptr2); - pobid = next_token(&ptr2); - snprintf(path, sizeof(path), "/%s/%s/UEMIS Dump/%s_%s_Uemis_dump_%s_in_round_%d_%d.txt", home.c_str(), user.c_str(), prefix, pdate, pobid, debug_round, bufCnt); - int dumpFile = subsurface_open(path, O_RDWR | O_CREAT, 0666); + std::string_view ptr2 = buf.substr(obidpos); + next_token(ptr2); + next_token(ptr2); + std::string pobid = std::string(next_token(ptr2)); + std::string path = format_string_std("/%s/%s/UEMIS Dump/%s_%s_Uemis_dump_%s_in_round_%d_%d.txt", home.c_str(), user.c_str(), prefix, pdate.c_str(), pobid.c_str(), debug_round, bufCnt); + int dumpFile = subsurface_open(path.c_str(), O_RDWR | O_CREAT, 0666); if (dumpFile == -1) return false; - success = (size_t)write(dumpFile, buf, strlen(buf)) == strlen(buf); + bool success = (size_t)write(dumpFile, buf.data(), buf.size()) == buf.size(); close(dumpFile); bufCnt++; return success; @@ -1113,58 +1021,56 @@ static bool do_dump_buffer_to_file(char *buf, const char *prefix) * filenr holds now the uemis filenr after having read several logs including the dive details, * fCapacity will five us the average number of files needed for all currently loaded data * remember the maximum file usage per dive - * return : UEMIS_MEM_OK if there is enough memory for a full round - * UEMIS_MEM_CRITICAL if the memory is good for reading the dive logs - * UEMIS_MEM_FULL if the memory is exhausted + * return : ok if there is enough memory for a full round + * full if the memory is exhausted */ -static int get_memory(struct dive_table *td, int checkpoint) +static uemis_mem_status get_memory(struct dive_table &td, uemis_checkpoint checkpoint) { - if (td->nr <= 0) - return UEMIS_MEM_OK; + if (td.empty()) + return uemis_mem_status::ok; switch (checkpoint) { - case UEMIS_CHECK_LOG: - if (filenr / td->nr > max_mem_used) - max_mem_used = filenr / td->nr; + case uemis_checkpoint::log: + if (filenr / static_cast(td.size()) > max_mem_used) + max_mem_used = filenr / static_cast(td.size()); /* check if a full block of dive logs + dive details and dive spot fit into the UEMIS buffer */ #if UEMIS_DEBUG & 4 - fprintf(debugfile, "max_mem_used %d (from td->nr %d) * block_size %d > max_files %d - filenr %d?\n", max_mem_used, td->nr, UEMIS_LOG_BLOCK_SIZE, UEMIS_MAX_FILES, filenr); + report_info("max_mem_used %d (from td.size() %d) * block_size %d > max_files %d - filenr %d?\n", max_mem_used, static_cast(td.size()), uemis_log_block_size, uemis_max_files, filenr); #endif - if (max_mem_used * UEMIS_LOG_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; + if (max_mem_used * uemis_log_block_size > uemis_max_files - filenr) + return uemis_mem_status::full; break; - case UEMIS_CHECK_DETAILS: + case uemis_checkpoint::details: /* check if the next set of dive details and dive spot fit into the UEMIS buffer */ - if ((UEMIS_DIVE_DETAILS_SIZE + UEMIS_SPOT_BLOCK_SIZE) * UEMIS_LOG_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; + if ((uemis_dive_details_size + uemis_spot_block_size) * uemis_log_block_size > uemis_max_files - filenr) + return uemis_mem_status::full; break; - case UEMIS_CHECK_SINGLE_DIVE: - if (UEMIS_DIVE_DETAILS_SIZE + UEMIS_SPOT_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; + case uemis_checkpoint::single_dive: + if (uemis_dive_details_size + uemis_spot_block_size > uemis_max_files - filenr) + return uemis_mem_status::full; break; } - return UEMIS_MEM_OK; + return uemis_mem_status::ok; } /* we misuse the hidden_by_filter flag to mark a dive as deleted. * this will be picked up by some cleaning statement later. */ -static void do_delete_dives(struct dive_table *td, int idx) +static void do_delete_dives(struct dive_table &td, size_t idx) { - for (int x = idx; x < td->nr; x++) - td->dives[x]->hidden_by_filter = true; + for (auto it = td.begin() + idx; it != td.end(); ++it) + (*it)->hidden_by_filter = true; } -static bool load_uemis_divespot(const char *mountpath, int divespot_id) +static bool load_uemis_divespot(const std::string &mountpath, int divespot_id) { - char divespotnr[32]; - snprintf(divespotnr, sizeof(divespotnr), "%d", divespot_id); - param_buff[2] = divespotnr; + param_buff[2] = std::to_string(divespot_id); #if UEMIS_DEBUG & 2 - fprintf(debugfile, "getDivespot %d\n", divespot_id); + report_info("getDivespot %d\n", divespot_id); #endif - bool success = uemis_get_answer(mountpath, "getDivespot", 3, 0, NULL); - if (mbuf && success) { + std::string error_text; // unused + std::string mbuf = uemis_get_answer(mountpath, "getDivespot", 3, 0, error_text); + if (!mbuf.empty()) { #if UEMIS_DEBUG & 16 do_dump_buffer_to_file(mbuf, "Spot"); #endif @@ -1173,112 +1079,109 @@ static bool load_uemis_divespot(const char *mountpath, int divespot_id) return false; } -static void get_uemis_divespot(device_data_t *devdata, const char *mountpath, int divespot_id, struct dive *dive) +static void get_uemis_divespot(device_data_t *devdata, const std::string &mountpath, int divespot_id, struct dive *dive) { struct dive_site *nds = dive->dive_site; - if (is_divespot_mappable(divespot_id)) { - struct dive_site *ds = get_dive_site_by_divespot_id(divespot_id); + auto it = divespot_mapping.find(divespot_id); + if (it != divespot_mapping.end()) { + struct dive_site *ds = it->second; unregister_dive_from_dive_site(dive); - add_dive_to_dive_site(dive, ds); - } else if (nds && nds->name && strstr(nds->name,"from Uemis")) { + ds->add_dive(dive); + } else if (nds && !nds->name.empty() && nds->name.find("from Uemis") != std::string::npos) { if (load_uemis_divespot(mountpath, divespot_id)) { /* get the divesite based on the diveid, this should give us * the newly created site */ - struct dive_site *ods; /* with the divesite name we got from parse_dive, that is called on load_uemis_divespot * we search all existing divesites if we have one with the same name already. The function * returns the first found which is luckily not the newly created. */ - ods = get_dive_site_by_name(nds->name, devdata->log->sites); - if (ods) { + struct dive_site *ods = devdata->log->sites.get_by_name(nds->name); + if (ods && nds->uuid != ods->uuid) { /* if the uuid's are the same, the new site is a duplicate and can be deleted */ - if (nds->uuid != ods->uuid) { - delete_dive_site(nds, devdata->log->sites); - unregister_dive_from_dive_site(dive); - add_dive_to_dive_site(dive, ods); - } + unregister_dive_from_dive_site(dive); + ods->add_dive(dive); + devdata->log->sites.pull(nds); } - add_to_divespot_mapping(divespot_id, dive->dive_site); + divespot_mapping[divespot_id] = dive->dive_site; } else { /* if we can't load the dive site details, delete the site we * created in process_raw_buffer */ - delete_dive_site(dive->dive_site, devdata->log->sites); + devdata->log->sites.pull(dive->dive_site); + dive->dive_site = nullptr; } } } -static bool get_matching_dive(int idx, char *newmax, int *uemis_mem_status, device_data_t *data, const char *mountpath, const char deviceidnr) +static bool get_matching_dive(size_t idx, int &newmax, uemis_mem_status &mem_status, device_data_t *data, const std::string &mountpath, const char deviceidnr) { - struct dive *dive = data->log->dives->dives[idx]; + auto &dive = data->log->dives[idx]; char log_file_no_to_find[20]; - char dive_to_read_buf[10]; bool found = false; bool found_below = false; bool found_above = false; int deleted_files = 0; int fail_count = 0; - snprintf(log_file_no_to_find, sizeof(log_file_no_to_find), "logfilenr{int{%d", dive->dc.diveid); + snprintf(log_file_no_to_find, sizeof(log_file_no_to_find), "logfilenr{int{%d", dive->dcs[0].diveid); #if UEMIS_DEBUG & 2 - fprintf(debugfile, "Looking for dive details to go with dive log id %d\n", dive->dc.diveid); + report_info("Looking for dive details to go with dive log id %d\n", dive->dcs[0].diveid); #endif while (!found) { if (import_thread_cancelled) break; - snprintf(dive_to_read_buf, sizeof(dive_to_read_buf), "%d", dive_to_read); - param_buff[2] = dive_to_read_buf; - (void)uemis_get_answer(mountpath, "getDive", 3, 0, NULL); + param_buff[2] = std::to_string(dive_to_read); + std::string error_text; // unused + std::string mbuf = uemis_get_answer(mountpath, "getDive", 3, 0, error_text); #if UEMIS_DEBUG & 16 do_dump_buffer_to_file(mbuf, "Dive"); #endif - *uemis_mem_status = get_memory(data->log->dives, UEMIS_CHECK_SINGLE_DIVE); - if (*uemis_mem_status == UEMIS_MEM_OK) { + mem_status = get_memory(data->log->dives, uemis_checkpoint::single_dive); + if (mem_status == uemis_mem_status::ok) { /* if the memory isn's completely full we can try to read more dive log vs. dive details - * UEMIS_MEM_CRITICAL means not enough space for a full round but the dive details * and the dive spots should fit into the UEMIS memory * The match we do here is to map the object_id to the logfilenr, we do this * by iterating through the last set of loaded dive logs and then find the corresponding * dive with the matching logfilenr */ - if (mbuf) { - if (strstr(mbuf, log_file_no_to_find)) { + if (!mbuf.empty()) { + if (strstr(mbuf.c_str(), log_file_no_to_find)) { /* we found the logfilenr that matches our object_id from the dive log we were looking for * we mark the search successful even if the dive has been deleted. */ found = true; - if (strstr(mbuf, "deleted{bool{true") == NULL) { - process_raw_buffer(data, deviceidnr, mbuf, &newmax, NULL); + if (strstr(mbuf.c_str(), "deleted{bool{true") == NULL) { + process_raw_buffer(data, deviceidnr, mbuf, newmax, NULL); /* remember the last log file number as it is very likely that subsequent dives * have the same or higher logfile number. * UEMIS unfortunately deletes dives by deleting the dive details and not the logs. */ #if UEMIS_DEBUG & 2 d_time = get_dive_date_c_string(dive->when); - fprintf(debugfile, "Matching dive log id %d from %s with dive details %d\n", dive->dc.diveid, d_time.c_str(), dive_to_read); + report_info("Matching dive log id %d from %s with dive details %d\n", dive->dcs[0].diveid, d_time.c_str(), dive_to_read); #endif - int divespot_id = uemis_get_divespot_id_by_diveid(dive->dc.diveid); + int divespot_id = uemis_obj.get_divespot_id_by_diveid(dive->dcs[0].diveid); if (divespot_id >= 0) - get_uemis_divespot(data, mountpath, divespot_id, dive); + get_uemis_divespot(data, mountpath, divespot_id, dive.get()); } else { /* in this case we found a deleted file, so let's increment */ #if UEMIS_DEBUG & 2 d_time = get_dive_date_c_string(dive->when); - fprintf(debugfile, "TRY matching dive log id %d from %s with dive details %d but details are deleted\n", dive->dc.diveid, d_time.c_str(), dive_to_read); + report_info("TRY matching dive log id %d from %s with dive details %d but details are deleted\n", dive->dcs[0].diveid, d_time.c_str(), dive_to_read); #endif deleted_files++; /* mark this log entry as deleted and cleanup later, otherwise we mess up our array */ dive->hidden_by_filter = true; #if UEMIS_DEBUG & 2 - fprintf(debugfile, "Deleted dive from %s, with id %d from table -- newmax is %s\n", d_time.c_str(), dive->dc.diveid, newmax); + report_info("Deleted dive from %s, with id %d from table -- newmax is %d\n", d_time.c_str(), dive->dcs[0].diveid, newmax); #endif } } else { uint32_t nr_found = 0; - char *logfilenr = strstr(mbuf, "logfilenr"); - if (logfilenr && strstr(mbuf, "act{")) { - sscanf(logfilenr, "logfilenr{int{%u", &nr_found); - if (nr_found >= dive->dc.diveid || nr_found == 0) { + size_t pos = mbuf.find("logfilenr"); + if (pos != std::string::npos && mbuf.find("act{") != std::string::npos) { + sscanf(mbuf.c_str() + pos, "logfilenr{int{%u", &nr_found); + if (nr_found >= dive->dcs[0].diveid || nr_found == 0) { found_above = true; dive_to_read = dive_to_read - 2; } else { @@ -1286,7 +1189,7 @@ static bool get_matching_dive(int idx, char *newmax, int *uemis_mem_status, devi } if (dive_to_read < -1) dive_to_read = -1; - } else if (!strstr(mbuf, "act{") && ++fail_count == 10) { + } else if (mbuf.find("act{") == std::string::npos && ++fail_count == 10) { if (verbose) report_info("Uemis downloader: Cannot access dive details - searching from start"); dive_to_read = -1; @@ -1310,25 +1213,23 @@ static bool get_matching_dive(int idx, char *newmax, int *uemis_mem_status, devi return true; } -const char *do_uemis_import(device_data_t *data) +std::string do_uemis_import(device_data_t *data) { - const char *mountpath = data->devname; + const std::string &mountpath = data->devname; short force_download = data->force_download; - char *newmax = NULL; + int newmax = -1; int first, start, end = -2; uint32_t deviceidnr; - char *deviceid = NULL; - const char *result = NULL; - char *endptr; - bool success, once = true; - int match_dive_and_log = 0; + std::string deviceid; + std::string result; + bool once = true; int dive_offset = 0; - int uemis_mem_status = UEMIS_MEM_OK; + uemis_mem_status mem_status = uemis_mem_status::ok; // To speed up sync you can skip downloading old dives by defining UEMIS_DIVE_OFFSET if (getenv("UEMIS_DIVE_OFFSET")) { dive_offset = atoi(getenv("UEMIS_DIVE_OFFSET")); - printf("Uemis: Using dive # offset %d\n", dive_offset); + report_info("Uemis: Using dive # offset %d\n", dive_offset); } #if UEMIS_DEBUG @@ -1340,17 +1241,17 @@ const char *do_uemis_import(device_data_t *data) return translate("gettextFromC", "Uemis init failed"); } - if (!uemis_get_answer(mountpath, "getDeviceId", 0, 1, &result)) + if (uemis_get_answer(mountpath, "getDeviceId", 0, 1, result).empty()) goto bail; - deviceid = strdup(param_buff[0]); - deviceidnr = atoi(deviceid); + deviceid = param_buff[0]; + deviceidnr = atoi(deviceid.c_str()); /* param_buff[0] is still valid */ - if (!uemis_get_answer(mountpath, "initSession", 1, 6, &result)) + if (uemis_get_answer(mountpath, "initSession", 1, 6, result).empty()) goto bail; uemis_info(translate("gettextFromC", "Start download")); - if (!uemis_get_answer(mountpath, "processSync", 0, 2, &result)) + if (uemis_get_answer(mountpath, "processSync", 0, 2, result).empty()) goto bail; /* before starting the long download, check if user pressed cancel */ @@ -1358,12 +1259,15 @@ const char *do_uemis_import(device_data_t *data) goto bail; param_buff[1] = "notempty"; - newmax = uemis_get_divenr(deviceid, data->log->dives, force_download); - if (verbose) - report_info("Uemis downloader: start looking at dive nr %s", newmax); + { + auto [mindiveid, maxdiveid] = uemis_get_divenr(deviceidnr, &data->log->dives, force_download); + newmax = maxdiveid; + if (verbose) + report_info("Uemis downloader: start looking at dive nr %d", newmax); - first = start = atoi(newmax); - dive_to_read = (int)mindiveid < first ? first - mindiveid : first; + first = start = newmax; + dive_to_read = (int)mindiveid < first ? first - mindiveid : first; + } if (dive_offset > 0) start += dive_offset; for (;;) { @@ -1371,45 +1275,53 @@ const char *do_uemis_import(device_data_t *data) debug_round++; #endif #if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i inner loop start %d end %d newmax %s\n", start, end, newmax); + report_info("d_u_i inner loop start %d end %d newmax %d\n", start, end, newmax); #endif /* start at the last filled download table index */ - match_dive_and_log = data->log->dives->nr; - sprintf(newmax, "%d", start); - param_buff[2] = newmax; - param_buff[3] = 0; - success = uemis_get_answer(mountpath, "getDivelogs", 3, 0, &result); - uemis_mem_status = get_memory(data->log->dives, UEMIS_CHECK_DETAILS); + size_t match_dive_and_log = data->log->dives.size(); + newmax = start; + std::string newmax_str = std::to_string(newmax); + param_buff[2] = newmax_str.c_str(); + param_buff[3].clear(); + std::string mbuf = uemis_get_answer(mountpath, "getDivelogs", 3, 0, result); + mem_status = get_memory(data->log->dives, uemis_checkpoint::details); /* first, remove any leading garbage... this needs to start with a '{' */ - char *realmbuf = mbuf; - if (mbuf) - realmbuf = strchr(mbuf, '{'); - if (success && realmbuf && uemis_mem_status != UEMIS_MEM_FULL) { + std::string_view realmbuf = mbuf; + size_t pos = realmbuf.find('{'); + if (pos != std::string::npos) + realmbuf = realmbuf.substr(pos); + if (!realmbuf.empty() && mem_status != uemis_mem_status::full) { #if UEMIS_DEBUG & 16 do_dump_buffer_to_file(realmbuf, "Dive logs"); #endif + bool success = true; /* process the buffer we have assembled */ - if (!process_raw_buffer(data, deviceidnr, realmbuf, &newmax, NULL)) { + if (!process_raw_buffer(data, deviceidnr, realmbuf, newmax, NULL)) { /* if no dives were downloaded, mark end appropriately */ - if (end == -2) - end = start - 1; + /* Might be related to the "clean up mbuf" below. + * Disable for now, since end will be overwritten anyway */ + //if (end == -2) + //end = start - 1; success = false; } if (once) { - char *t = first_object_id_val(realmbuf); - if (t && atoi(t) > start) - start = atoi(t); - free(t); + std::string t = first_object_id_val(realmbuf); + int val; + if (from_chars(t, val).ec != std::errc::invalid_argument) { + start = std::max(val, start); + } once = false; } /* clean up mbuf */ - endptr = strstr(realmbuf, "{{{"); + /* reason unclear: + const char *endptr = strstr(realmbuf, "{{{"); if (endptr) *(endptr + 2) = '\0'; + */ /* last object_id we parsed */ - sscanf(newmax, "%d", &end); + end = newmax; #if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i after download and parse start %d end %d newmax %s progress %4.2f\n", start, end, newmax, progress_bar_fraction); + report_info("d_u_i after download and parse start %d end %d newmax %d progress %4.2f\n", start, end, newmax, progress_bar_fraction); #endif /* The way this works is that I am reading the current dive from what has been loaded during the getDiveLogs call to the UEMIS. * As the object_id of the dive log entry and the object_id of the dive details are not necessarily the same, the match needs @@ -1417,9 +1329,8 @@ const char *do_uemis_import(device_data_t *data) * What the following part does is to optimize the mapping by using * dive_to_read = the dive details entry that need to be read using the object_id * logFileNoToFind = map the logfilenr of the dive details with the object_id = diveid from the get dive logs */ - for (int i = match_dive_and_log; i < data->log->dives->nr; i++) { - bool success = get_matching_dive(i, newmax, &uemis_mem_status, data, mountpath, deviceidnr); - if (!success) + for (size_t i = match_dive_and_log; i < data->log->dives.size(); i++) { + if (!get_matching_dive(i, newmax, mem_status, data, mountpath, deviceidnr)) break; if (import_thread_cancelled) break; @@ -1428,12 +1339,12 @@ const char *do_uemis_import(device_data_t *data) start = end; /* Do some memory checking here */ - uemis_mem_status = get_memory(data->log->dives, UEMIS_CHECK_LOG); - if (uemis_mem_status != UEMIS_MEM_OK) { + mem_status = get_memory(data->log->dives, uemis_checkpoint::log); + if (mem_status != uemis_mem_status::ok) { #if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i out of memory, bailing\n"); + report_info("d_u_i out of memory, bailing\n"); #endif - (void)uemis_get_answer(mountpath, "terminateSync", 0, 3, &result); + mbuf = uemis_get_answer(mountpath, "terminateSync", 0, 3, result); const char *errormsg = translate("gettextFromC", ACTION_RECONNECT); for (int wait=60; wait >=0; wait--){ uemis_info("%s %ds", errormsg, wait); @@ -1442,39 +1353,39 @@ const char *do_uemis_import(device_data_t *data) // Resetting to original state filenr = 0; max_mem_used = -1; - uemis_mem_status = get_memory(data->log->dives, UEMIS_CHECK_DETAILS); - if (!uemis_get_answer(mountpath, "getDeviceId", 0, 1, &result)) + mem_status = get_memory(data->log->dives, uemis_checkpoint::details); + if (uemis_get_answer(mountpath, "getDeviceId", 0, 1, result).empty()) goto bail; - if (strcmp(deviceid, param_buff[0]) != 0) { + if (deviceid != param_buff[0]) { report_info("Uemis: Device id has changed after reconnect!"); goto bail; } - param_buff[0] = strdup(deviceid); - if (!uemis_get_answer(mountpath, "initSession", 1, 6, &result)) + param_buff[0] = deviceid; + if (uemis_get_answer(mountpath, "initSession", 1, 6, result).empty()) goto bail; uemis_info(translate("gettextFromC", "Start download")); - if (!uemis_get_answer(mountpath, "processSync", 0, 2, &result)) + if (uemis_get_answer(mountpath, "processSync", 0, 2, result).empty()) goto bail; param_buff[1] = "notempty"; } /* if the user clicked cancel, exit gracefully */ if (import_thread_cancelled) { #if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i thread canceled, bailing\n"); + report_info("d_u_i thread canceled, bailing\n"); #endif break; } /* if we got an error or got nothing back, stop trying */ - if (!success || !param_buff[3]) { + if (!success || param_buff[3].empty()) { #if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i after download nothing found, giving up\n"); + report_info("d_u_i after download nothing found, giving up\n"); #endif break; } #if UEMIS_DEBUG & 2 if (debug_round != -1) if (debug_round-- == 0) { - fprintf(debugfile, "d_u_i debug_round is now 0, bailing\n"); + report_info("d_u_i debug_round is now 0, bailing\n"); goto bail; } #endif @@ -1484,48 +1395,46 @@ const char *do_uemis_import(device_data_t *data) * The loaded block of dive logs is useless and all new loaded dive logs need to * be deleted from the download_table. */ - if (uemis_mem_status == UEMIS_MEM_FULL) + if (mem_status == uemis_mem_status::full) do_delete_dives(data->log->dives, match_dive_and_log); #if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i out of memory, bailing instead of processing\n"); + report_info("d_u_i out of memory, bailing instead of processing\n"); #endif break; } } - if (end == -2 && sscanf(newmax, "%d", &end) != 1) - end = start; + if (end == -2) + end = newmax; #if UEMIS_DEBUG & 2 - fprintf(debugfile, "Done: read from object_id %d to %d\n", first, end); + report_info("Done: read from object_id %d to %d\n", first, end); #endif /* Regardless on where we are with the memory situation, it's time now * to see if we have to clean some dead bodies from our download table */ - next_table_index = 0; - while (next_table_index < data->log->dives->nr) { - if (data->log->dives->dives[next_table_index]->hidden_by_filter) - uemis_delete_dive(data, data->log->dives->dives[next_table_index]->dc.diveid); + for (size_t next_table_index = 0; next_table_index < data->log->dives.size(); ) { + if (data->log->dives[next_table_index]->hidden_by_filter) + uemis_delete_dive(data, data->log->dives[next_table_index]->dcs[0].diveid); else next_table_index++; } - if (uemis_mem_status != UEMIS_MEM_OK) + if (mem_status != uemis_mem_status::ok) result = translate("gettextFromC", ERR_FS_ALMOST_FULL); if (data->sync_time) uemis_info(translate("gettextFromC", "Time sync not supported by dive computer")); bail: - (void)uemis_get_answer(mountpath, "terminateSync", 0, 3, &result); - if (!strcmp(param_buff[0], "error")) { - if (!strcmp(param_buff[2], "Out of Memory")) + (void)uemis_get_answer(mountpath, "terminateSync", 0, 3, result); + if (param_buff[0] == "error") { + if (param_buff[2] == "Out of Memory") result = translate("gettextFromC", ERR_FS_FULL); else result = param_buff[2]; } - free(deviceid); - if (!data->log->dives->nr) + if (data->log->dives.empty()) result = translate("gettextFromC", ERR_NO_FILES); return result; } diff --git a/core/uemis.c b/core/uemis.cpp similarity index 63% rename from core/uemis.c rename to core/uemis.cpp index b5d6d006c..279a3199a 100644 --- a/core/uemis.c +++ b/core/uemis.cpp @@ -14,12 +14,44 @@ #include "gettext.h" #include "uemis.h" +#include "dive.h" +#include "divecomputer.h" #include "divesite.h" #include "errorhelper.h" +#include "format.h" #include "sample.h" #include #include +struct uemis_sample +{ + uint16_t dive_time; + uint16_t water_pressure; // (in cbar) + uint16_t dive_temperature; // (in dC) + uint8_t ascent_speed; // (units unclear) + uint8_t work_fact; + uint8_t cold_fact; + uint8_t bubble_fact; + uint16_t ascent_time; + uint16_t ascent_time_opt; + uint16_t p_amb_tol; + uint16_t satt; + uint16_t hold_depth; + uint16_t hold_time; + uint8_t active_tank; + // bloody glib, when compiled for Windows, forces the whole program to use + // the Windows packing rules. So to avoid problems on Windows (and since + // only tank_pressure is currently used and that exactly once) I give in and + // make this silly low byte / high byte 8bit entries + uint8_t tank_pressure_low; // (in cbar) + uint8_t tank_pressure_high; + uint8_t consumption_low; // (units unclear) + uint8_t consumption_high; + uint8_t rgt; // (remaining gas time in minutes) + uint8_t cns; + uint8_t flags[8]; +} __attribute((packed)); + /* * following code is based on code found in at base64.sourceforge.net/b64.c * AUTHOR: Bob Trower 08/04/01 @@ -78,109 +110,66 @@ static void decode(uint8_t *inbuf, uint8_t *outbuf, int inbuf_len) /* * convert the base64 data blog */ -static int uemis_convert_base64(char *base64, uint8_t **data) +static std::vector convert_base64(std::string_view base64) { - int len, datalen; + int datalen; + int len = (int)base64.size(); - len = strlen(base64); datalen = (len / 4 + 1) * 3; if (datalen < 0x123 + 0x25) /* less than header + 1 sample??? */ report_info("suspiciously short data block %d", datalen); - *data = malloc(datalen); - if (!*data) { - fprintf(stderr, "Out of memory\n"); - return 0; - } - decode((unsigned char *)base64, *data, len); + std::vector res(datalen); + decode((unsigned char *)base64.begin(), res.data(), len); - if (memcmp(*data, "Dive\01\00\00", 7)) + if (memcmp(res.data(), "Dive\01\00\00", 7)) report_info("Missing Dive100 header"); - return datalen; + return res; } -struct uemis_helper { - uint32_t diveid; - int lbs; - int divespot; - struct dive_site *dive_site; - struct uemis_helper *next; -}; -static struct uemis_helper *uemis_helper = NULL; - -static struct uemis_helper *uemis_get_helper(uint32_t diveid) +struct uemis::helper &uemis::get_helper(uint32_t diveid) { - struct uemis_helper **php = &uemis_helper; - struct uemis_helper *hp = *php; - - while (hp) { - if (hp->diveid == diveid) - return hp; - if (hp->next) { - hp = hp->next; - continue; - } - php = &hp->next; - break; - } - hp = *php = calloc(1, sizeof(struct uemis_helper)); - hp->diveid = diveid; - hp->next = NULL; - return hp; + return helper_table[diveid]; } -static void uemis_weight_unit(int diveid, int lbs) +void uemis::weight_unit(int diveid, int lbs) { - struct uemis_helper *hp = uemis_get_helper(diveid); - if (hp) - hp->lbs = lbs; + struct uemis::helper &hp = get_helper(diveid); + hp.lbs = lbs; } -int uemis_get_weight_unit(uint32_t diveid) +int uemis::get_weight_unit(uint32_t diveid) const { - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->diveid == diveid) - return hp->lbs; - hp = hp->next; - } - /* odd - we should have found this; default to kg */ - return 0; + auto it = helper_table.find(diveid); + return it != helper_table.end() ? it->second.lbs : 0; } -void uemis_mark_divelocation(int diveid, int divespot, struct dive_site *ds) +void uemis::mark_divelocation(int diveid, int divespot, struct dive_site *ds) { - struct uemis_helper *hp = uemis_get_helper(diveid); - hp->divespot = divespot; - hp->dive_site = ds; + struct uemis::helper &hp = get_helper(diveid); + hp.divespot = divespot; + hp.dive_site = ds; } /* support finding a dive spot based on the diveid */ -int uemis_get_divespot_id_by_diveid(uint32_t diveid) +int uemis::get_divespot_id_by_diveid(uint32_t diveid) const { - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->diveid == diveid) - return hp->divespot; - hp = hp->next; - } - return -1; + auto it = helper_table.find(diveid); + return it != helper_table.end() ? it->second.divespot : -1; } -void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude) +void uemis::set_divelocation(int divespot, const std::string &text, double longitude, double latitude) { - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->divespot == divespot) { - struct dive_site *ds = hp->dive_site; + for (auto it: helper_table) { + if (it.second.divespot == divespot) { + struct dive_site *ds = it.second.dive_site; if (ds) { - ds->name = strdup(text); + ds->name = text; ds->location = create_location(latitude, longitude); } } - hp = hp->next; } } @@ -194,9 +183,9 @@ void uemis_set_divelocation(int divespot, char *text, double longitude, double l * when we write them to the XML file we'll always have the English strings, * regardless of locale */ -static void uemis_event(struct dive *dive, struct divecomputer *dc, struct sample *sample, uemis_sample_t *u_sample) +void uemis::event(struct dive *dive, struct divecomputer *dc, struct sample *sample, const uemis_sample *u_sample) { - uint8_t *flags = u_sample->flags; + const uint8_t *flags = u_sample->flags; int stopdepth; static int lastndl; @@ -259,13 +248,13 @@ static void uemis_event(struct dive *dive, struct divecomputer *dc, struct sampl * flags[3].bit0 | flags[5].bit1 != 0 ==> in deco * flags[0].bit7 == 1 ==> Safety Stop * otherwise NDL */ - stopdepth = rel_mbar_to_depth(u_sample->hold_depth, dive); + stopdepth = dive->rel_mbar_to_depth(u_sample->hold_depth); if ((flags[3] & 1) | (flags[5] & 2)) { /* deco */ sample->in_deco = true; sample->stopdepth.mm = stopdepth; sample->stoptime.seconds = u_sample->hold_time * 60; - sample->ndl.seconds = 0; + sample->ndl = 0_sec; } else if (flags[0] & 128) { /* safety stop - distinguished from deco stop by having * both ndl and stop information */ @@ -277,12 +266,12 @@ static void uemis_event(struct dive *dive, struct divecomputer *dc, struct sampl /* NDL */ sample->in_deco = false; lastndl = sample->ndl.seconds = u_sample->hold_time * 60; - sample->stopdepth.mm = 0; - sample->stoptime.seconds = 0; + sample->stopdepth = 0_m; + sample->stoptime = 0_sec; } #if UEMIS_DEBUG & 32 printf("%dm:%ds: p_amb_tol:%d surface:%d holdtime:%d holddepth:%d/%d ---> stopdepth:%d stoptime:%d ndl:%d\n", - sample->time.seconds / 60, sample->time.seconds % 60, u_sample->p_amb_tol, dive->dc.surface_pressure.mbar, + sample->time.seconds / 60, sample->time.seconds % 60, u_sample->p_amb_tol, dive->dcs[0].surface_pressure.mbar, u_sample->hold_time, u_sample->hold_depth, stopdepth, sample->stopdepth.mm, sample->stoptime.seconds, sample->ndl.seconds); #endif } @@ -290,33 +279,29 @@ static void uemis_event(struct dive *dive, struct divecomputer *dc, struct sampl /* * parse uemis base64 data blob into struct dive */ -void uemis_parse_divelog_binary(char *base64, void *datap) +void uemis::parse_divelog_binary(std::string_view base64, struct dive *dive) { - int datalen; - int i; - uint8_t *data; struct sample *sample = NULL; - uemis_sample_t *u_sample; - struct dive *dive = datap; - struct divecomputer *dc = &dive->dc; - int template, gasoffset; + uemis_sample *u_sample; + struct divecomputer *dc = &dive->dcs[0]; + int dive_template, gasoffset; uint8_t active = 0; - datalen = uemis_convert_base64(base64, &data); - dive->dc.airtemp.mkelvin = C_to_mkelvin((*(uint16_t *)(data + 45)) / 10.0); - dive->dc.surface_pressure.mbar = *(uint16_t *)(data + 43); - if (*(uint8_t *)(data + 19)) - dive->dc.salinity = SEAWATER_SALINITY; /* avg grams per 10l sea water */ + auto data = convert_base64(base64); + dive->dcs[0].airtemp.mkelvin = C_to_mkelvin((*(uint16_t *)(data.data() + 45)) / 10.0); + dive->dcs[0].surface_pressure.mbar = *(uint16_t *)(data.data() + 43); + if (*(uint8_t *)(data.data() + 19)) + dive->dcs[0].salinity = SEAWATER_SALINITY; /* avg grams per 10l sea water */ else - dive->dc.salinity = FRESHWATER_SALINITY; /* grams per 10l fresh water */ + dive->dcs[0].salinity = FRESHWATER_SALINITY; /* grams per 10l fresh water */ /* this will allow us to find the last dive read so far from this computer */ - dc->model = strdup("Uemis Zurich"); - dc->deviceid = *(uint32_t *)(data + 9); - dc->diveid = *(uint16_t *)(data + 7); + dc->model = "Uemis Zurich"; + dc->deviceid = *(uint32_t *)(data.data() + 9); + dc->diveid = *(uint16_t *)(data.data() + 7); /* remember the weight units used in this dive - we may need this later when * parsing the weight */ - uemis_weight_unit(dc->diveid, *(uint8_t *)(data + 24)); + weight_unit(dc->diveid, *(uint8_t *)(data.data() + 24)); /* dive template in use: 0 = air 1 = nitrox (B) @@ -325,13 +310,13 @@ void uemis_parse_divelog_binary(char *base64, void *datap) uemis cylinder data is insane - it stores seven tank settings in a block and the template tells us which of the four groups of tanks we need to look at */ - gasoffset = template = *(uint8_t *)(data + 115); - if (template == 3) + gasoffset = dive_template = *(uint8_t *)(data.data() + 115); + if (dive_template == 3) gasoffset = 4; - if (template == 0) - template = 1; - for (i = 0; i < template; i++) { - float volume = *(float *)(data + 116 + 25 * (gasoffset + i)) * 1000.0f; + if (dive_template == 0) + dive_template = 1; + for (int i = 0; i < dive_template; i++) { + float volume = *(float *)(data.data() + 116 + 25 * (gasoffset + i)) * 1000.0f; /* uemis always assumes a working pressure of 202.6bar (!?!?) - I first thought * it was 3000psi, but testing against all my dives gets me that strange number. * Still, that's of course completely bogus and shows they don't get how @@ -339,18 +324,18 @@ void uemis_parse_divelog_binary(char *base64, void *datap) * we store the incorrect working pressure to get the SAC calculations "close" * but the user will have to correct this manually */ - cylinder_t *cyl = get_or_create_cylinder(dive, i); + cylinder_t *cyl = dive->get_or_create_cylinder(i); cyl->type.size.mliter = lrintf(volume); - cyl->type.workingpressure.mbar = 202600; - cyl->gasmix.o2.permille = *(uint8_t *)(data + 120 + 25 * (gasoffset + i)) * 10; - cyl->gasmix.he.permille = 0; + cyl->type.workingpressure = 202600_mbar; + cyl->gasmix.o2.permille = *(uint8_t *)(data.data() + 120 + 25 * (gasoffset + i)) * 10; + cyl->gasmix.he = 0_percent; } /* first byte of divelog data is at offset 0x123 */ - i = 0x123; - u_sample = (uemis_sample_t *)(data + i); - while ((i <= datalen) && (data[i] != 0 || data[i + 1] != 0)) { + size_t i = 0x123; + u_sample = (uemis_sample *)(data.data() + i); + while ((i <= data.size()) && (data[i] != 0 || data[i + 1] != 0)) { if (u_sample->active_tank != active) { - if (u_sample->active_tank >= dive->cylinders.nr) { + if (u_sample->active_tank >= static_cast(dive->cylinders.size())) { report_info("got invalid sensor #%d was #%d", u_sample->active_tank, active); } else { active = u_sample->active_tank; @@ -359,35 +344,32 @@ void uemis_parse_divelog_binary(char *base64, void *datap) } sample = prepare_sample(dc); sample->time.seconds = u_sample->dive_time; - sample->depth.mm = rel_mbar_to_depth(u_sample->water_pressure, dive); + sample->depth.mm = dive->rel_mbar_to_depth(u_sample->water_pressure); sample->temperature.mkelvin = C_to_mkelvin(u_sample->dive_temperature / 10.0); add_sample_pressure(sample, active, (u_sample->tank_pressure_high * 256 + u_sample->tank_pressure_low) * 10); sample->cns = u_sample->cns; - uemis_event(dive, dc, sample, u_sample); - finish_sample(dc); + event(dive, dc, sample, u_sample); i += 0x25; u_sample++; } if (sample) - dive->dc.duration.seconds = sample->time.seconds - 1; + dive->dcs[0].duration = sample->time - 1_sec; /* get data from the footer */ - char buffer[24]; - - snprintf(buffer, sizeof(buffer), "%1u.%02u", data[18], data[17]); - add_extra_data(dc, "FW Version", buffer); - snprintf(buffer, sizeof(buffer), "%08x", *(uint32_t *)(data + 9)); - add_extra_data(dc, "Serial", buffer); - snprintf(buffer, sizeof(buffer), "%d", *(uint16_t *)(data + i + 35)); - add_extra_data(dc, "main battery after dive", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION_TUPLE(*(uint16_t *)(data + i + 24), 60)); - add_extra_data(dc, "no fly time", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION_TUPLE(*(uint16_t *)(data + i + 26), 60)); - add_extra_data(dc, "no dive time", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION_TUPLE(*(uint16_t *)(data + i + 28), 60)); - add_extra_data(dc, "desat time", buffer); - snprintf(buffer, sizeof(buffer), "%u", *(uint16_t *)(data + i + 30)); - add_extra_data(dc, "allowed altitude", buffer); + add_extra_data(dc, "FW Version", + format_string_std("%1u.%02u", data[18], data[17])); + add_extra_data(dc, "Serial", + format_string_std("%08x", *(uint32_t *)(data.data() + 9))); + add_extra_data(dc, "main battery after dive", + std::to_string(*(uint16_t *)(data.data() + i + 35))); + add_extra_data(dc, "no fly time", + format_string_std("%0u:%02u", FRACTION_TUPLE(*(uint16_t *)(data.data() + i + 24), 60))); + add_extra_data(dc, "no dive time", + format_string_std("%0u:%02u", FRACTION_TUPLE(*(uint16_t *)(data.data() + i + 26), 60))); + add_extra_data(dc, "desat time", + format_string_std("%0u:%02u", FRACTION_TUPLE(*(uint16_t *)(data.data() + i + 28), 60))); + add_extra_data(dc, "allowed altitude", + std::to_string(*(uint16_t *)(data.data() + i + 30))); return; } diff --git a/core/uemis.h b/core/uemis.h index 9c0a70995..e8cb69b69 100644 --- a/core/uemis.h +++ b/core/uemis.h @@ -6,50 +6,38 @@ #ifndef UEMIS_H #define UEMIS_H -#include -#include "dive.h" +#include "libdivecomputer.h" // for device_data_t, which is a typedef, not a struct :( -#ifdef __cplusplus -extern "C" { -#endif +#include +#include +#include +#include -void uemis_parse_divelog_binary(char *base64, void *divep); -int uemis_get_weight_unit(uint32_t diveid); -void uemis_mark_divelocation(int diveid, int divespot, struct dive_site *ds); -void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude); -int uemis_get_divespot_id_by_diveid(uint32_t diveid); +struct dive; +struct uemis_sample; +struct dive_site; -typedef struct -{ - uint16_t dive_time; - uint16_t water_pressure; // (in cbar) - uint16_t dive_temperature; // (in dC) - uint8_t ascent_speed; // (units unclear) - uint8_t work_fact; - uint8_t cold_fact; - uint8_t bubble_fact; - uint16_t ascent_time; - uint16_t ascent_time_opt; - uint16_t p_amb_tol; - uint16_t satt; - uint16_t hold_depth; - uint16_t hold_time; - uint8_t active_tank; - // bloody glib, when compiled for Windows, forces the whole program to use - // the Windows packing rules. So to avoid problems on Windows (and since - // only tank_pressure is currently used and that exactly once) I give in and - // make this silly low byte / high byte 8bit entries - uint8_t tank_pressure_low; // (in cbar) - uint8_t tank_pressure_high; - uint8_t consumption_low; // (units unclear) - uint8_t consumption_high; - uint8_t rgt; // (remaining gas time in minutes) - uint8_t cns; - uint8_t flags[8]; -} __attribute((packed)) uemis_sample_t; +struct uemis { + void parse_divelog_binary(std::string_view base64, struct dive *dive); + int get_weight_unit(uint32_t diveid) const; + void mark_divelocation(int diveid, int divespot, struct dive_site *ds); + void set_divelocation(int divespot, const std::string &text, double longitude, double latitude); + int get_divespot_id_by_diveid(uint32_t diveid) const; +private: + struct helper { + int lbs = 9; + int divespot = 9; + struct dive_site *dive_site = nullptr; + }; + // Use a hash-table (std::unordered_map) to access dive information. + // Might also use a balanced binary tree (std::map) or a sorted array (std::vector). + std::unordered_map helper_table; -#ifdef __cplusplus -} -#endif + static void event(struct dive *dive, struct divecomputer *dc, struct sample *sample, const uemis_sample *u_sample); + struct helper &get_helper(uint32_t diveid); + void weight_unit(int diveid, int lbs); +}; + +std::string do_uemis_import(device_data_t *data); #endif // UEMIS_H diff --git a/core/units.c b/core/units.cpp similarity index 67% rename from core/units.c rename to core/units.cpp index 6d4c5fbbb..73e4b0c42 100644 --- a/core/units.c +++ b/core/units.cpp @@ -3,14 +3,11 @@ #include "gettext.h" #include "pref.h" -#define IMPERIAL_UNITS \ - { \ - .length = FEET, .volume = CUFT, .pressure = PSI, .temperature = FAHRENHEIT, .weight = LBS, \ - .vertical_speed_time = MINUTES, .duration_units = MIXED, .show_units_table = false \ - } - const struct units SI_units = SI_UNITS; -const struct units IMPERIAL_units = IMPERIAL_UNITS; +const struct units IMPERIAL_units = { + .length = units::FEET, .volume = units::CUFT, .pressure = units::PSI, .temperature = units::FAHRENHEIT, .weight = units::LBS, + .vertical_speed_time = units::MINUTES, .duration_units = units::MIXED, .show_units_table = false +}; int get_pressure_units(int mb, const char **units) { @@ -19,13 +16,13 @@ int get_pressure_units(int mb, const char **units) const struct units *units_p = get_units(); switch (units_p->pressure) { - case BAR: + case units::BAR: default: pressure = (mb + 500) / 1000; unit = translate("gettextFromC", "bar"); break; - case PSI: - pressure = (int)lrint(mbar_to_PSI(mb)); + case units::PSI: + pressure = int_cast(mbar_to_PSI(mb)); unit = translate("gettextFromC", "psi"); break; } @@ -40,7 +37,7 @@ double get_temp_units(unsigned int mk, const char **units) const char *unit; const struct units *units_p = get_units(); - if (units_p->temperature == FAHRENHEIT) { + if (units_p->temperature == units::FAHRENHEIT) { deg = mkelvin_to_F(mk); unit = "°F"; } else { @@ -60,13 +57,13 @@ double get_volume_units(unsigned int ml, int *frac, const char **units) const struct units *units_p = get_units(); switch (units_p->volume) { - case LITER: + case units::LITER: default: vol = ml / 1000.0; unit = translate("gettextFromC", "ℓ"); decimals = 1; break; - case CUFT: + case units::CUFT: vol = ml_to_cuft(ml); unit = translate("gettextFromC", "cuft"); decimals = 2; @@ -81,7 +78,7 @@ double get_volume_units(unsigned int ml, int *frac, const char **units) int units_to_sac(double volume) { - if (get_units()->volume == CUFT) + if (get_units()->volume == units::CUFT) return lrint(cuft_to_l(volume) * 1000.0); else return lrint(volume * 1000); @@ -90,7 +87,7 @@ int units_to_sac(double volume) depth_t units_to_depth(double depth) { depth_t internaldepth; - if (get_units()->length == METERS) { + if (get_units()->length == units::METERS) { internaldepth.mm = lrint(depth * 1000); } else { internaldepth.mm = feet_to_mm(depth); @@ -106,13 +103,13 @@ double get_depth_units(int mm, int *frac, const char **units) const struct units *units_p = get_units(); switch (units_p->length) { - case METERS: + case units::METERS: default: d = mm / 1000.0; unit = translate("gettextFromC", "m"); decimals = d < 20; break; - case FEET: + case units::FEET: d = mm_to_feet(mm); unit = translate("gettextFromC", "ft"); decimals = 0; @@ -130,20 +127,20 @@ double get_vertical_speed_units(unsigned int mms, int *frac, const char **units) double d; const char *unit; const struct units *units_p = get_units(); - const double time_factor = units_p->vertical_speed_time == MINUTES ? 60.0 : 1.0; + const double time_factor = units_p->vertical_speed_time == units::MINUTES ? 60.0 : 1.0; switch (units_p->length) { - case METERS: + case units::METERS: default: d = mms / 1000.0 * time_factor; - if (units_p->vertical_speed_time == MINUTES) + if (units_p->vertical_speed_time == units::MINUTES) unit = translate("gettextFromC", "m/min"); else unit = translate("gettextFromC", "m/s"); break; - case FEET: + case units::FEET: d = mm_to_feet(mms) * time_factor; - if (units_p->vertical_speed_time == MINUTES) + if (units_p->vertical_speed_time == units::MINUTES) unit = translate("gettextFromC", "ft/min"); else unit = translate("gettextFromC", "ft/s"); @@ -163,7 +160,7 @@ double get_weight_units(unsigned int grams, int *frac, const char **units) const char *unit; const struct units *units_p = get_units(); - if (units_p->weight == LBS) { + if (units_p->weight == units::LBS) { value = grams_to_lbs(grams); unit = translate("gettextFromC", "lbs"); decimals = 0; @@ -183,3 +180,22 @@ const struct units *get_units() { return &prefs.units; } + +// Calculate the distance in meters between two coordinates. +unsigned int get_distance(location_t loc1, 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); +} + diff --git a/core/units.h b/core/units.h index c18f6f414..d34d4deb5 100644 --- a/core/units.h +++ b/core/units.h @@ -7,12 +7,6 @@ #define M_PI 3.14159265358979323846 #endif -#ifdef __cplusplus -extern "C" { -#else -#include -#endif - #define FRACTION_TUPLE(n, x) ((unsigned)(n) / (x)), ((unsigned)(n) % (x)) #define SIGNED_FRAC_TRIPLET(n, x) ((n) >= 0 ? '+': '-'), ((n) >= 0 ? (unsigned)(n) / (x) : (-(n) / (x))), ((unsigned)((n) >= 0 ? (n) : -(n)) % (x)) @@ -21,14 +15,9 @@ extern "C" { #define O2_DENSITY 1331 // mg/Liter #define N2_DENSITY 1165 #define HE_DENSITY 166 -#define SURFACE_PRESSURE 1013 // mbar #define ZERO_C_IN_MKELVIN 273150 // mKelvin -#ifdef __cplusplus #define M_OR_FT(_m, _f) ((prefs.units.length == units::METERS) ? ((_m) * 1000) : (feet_to_mm(_f))) -#else -#define M_OR_FT(_m, _f) ((prefs.units.length == METERS) ? ((_m) * 1000) : (feet_to_mm(_f))) -#endif /* Salinity is expressed in weight in grams per 10l */ #define SEAWATER_SALINITY 10300 @@ -38,7 +27,7 @@ extern "C" { #include /* - * Some silly typedefs to make our units very explicit. + * Some silly structs to make our units very explicit. * * Also, the units are chosen so that values can be expressible as * integers, so that we never have FP rounding issues. And they @@ -72,96 +61,228 @@ extern "C" { * actual value. So there is hopefully little fear of using a value * in millikelvin as Fahrenheit by mistake. * + * In general, to initialize a variable, use named initializers: + * depth_t depth = { .mm = 10'000; }; // 10 m + * However, for convenience, we define a number of user-defined + * literals, which make the above more readable: + * depht_t depth = 10_m; + * Currently, we only support integer literals, but might also + * do floating points if that seems practical. + * + * Currently we define: + * _sec -> duration_t in seconds + * _min -> duration_t in minutes + * _mm -> depth_t in millimeters + * _m -> depth_t in meters + * _ft -> depth_t in feet + * _mbar -> pressure_t in millibar + * _bar -> pressure_t in bar + * _atm -> pressure_t in atmospheres + * _baro2 -> o2pressure_t in bar + * _K -> temperature_t in kelvin + * _ml -> volume_t in milliliters + * _l -> volume_t in liters + * _percent -> volume_t in liters + * _permille -> fraction_t in ‰ + * _percent -> fraction_t in % + * * We don't actually use these all yet, so maybe they'll change, but * I made a number of types as guidelines. */ -typedef int64_t timestamp_t; +using timestamp_t = int64_t; -typedef struct +/* + * There is a semi-common pattern where lrint() is used to round + * doubles to long integers and then cast down to a less wide + * int. Since this is unwieldy, encapsulate this in this function + */ +template +INT int_cast(double v) { - int32_t seconds; // durations up to 34 yrs -} duration_t; + return static_cast(lrint(v)); +} -static const duration_t zero_duration = { 0 }; +// Base class for all unit types using the "Curiously recurring template pattern" +// (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) +// to implement addition, subtraction and negation. +// Multiplication and division (which result in a different type) +// are not implemented. If we want that, we should switch to a proper +// units libary. +// Also note that some units may be based on unsigned integers and +// therefore subtraction may be ill-defined. +template +struct unit_base { + auto &get_base() { + auto &[v] = static_cast(*this); + return v; + } + auto get_base() const { + auto [v] = static_cast(*this); + return v; + } + template + static T from_base(base_type v) { + return { {}, v }; + } + T operator+(const T &v2) const { + return from_base(get_base() + v2.get_base()); + } + T &operator+=(const T &v2) { + get_base() += v2.get_base(); + return static_cast(*this); + } + T operator-(const T &v2) const { + return from_base(get_base() - v2.get_base()); + } + T &operator-=(const T &v2) { + get_base() -= v2.get_base(); + return static_cast(*this); + } +}; -typedef struct +struct duration_t : public unit_base { - int32_t seconds; // offsets up to +/- 34 yrs -} offset_t; - -typedef struct + int32_t seconds = 0; // durations up to 34 yrs +}; +static inline duration_t operator""_sec(unsigned long long sec) { - int32_t mm; -} depth_t; // depth to 2000 km - -typedef struct + return duration_t { .seconds = static_cast(sec) }; +} +static inline duration_t operator""_min(unsigned long long min) { - int32_t mbar; // pressure up to 2000 bar -} pressure_t; + return duration_t { .seconds = static_cast(min * 60) }; +} -typedef struct +struct offset_t : public unit_base { - uint16_t mbar; -} o2pressure_t; // pressure up to 65 bar + int32_t seconds = 0; // offsets up to +/- 34 yrs +}; -typedef struct +struct depth_t : public unit_base // depth to 2000 km { - int16_t degrees; -} bearing_t; // compass bearing - -typedef struct + int32_t mm = 0; +}; +static inline depth_t operator""_mm(unsigned long long mm) { - uint32_t mkelvin; // up to 4 MK (temperatures in K are always positive) -} temperature_t; - -typedef struct + return depth_t { .mm = static_cast(mm) }; +} +static inline depth_t operator""_m(unsigned long long m) { - uint64_t mkelvin; // up to 18446744073 MK (temperatures in K are always positive) -} temperature_sum_t; - -typedef struct + return depth_t { .mm = static_cast(m * 1000) }; +} +static inline depth_t operator""_ft(unsigned long long ft) { - int mliter; -} volume_t; + return depth_t { .mm = static_cast(round(ft * 304.8)) }; +} -typedef struct +struct pressure_t : public unit_base { - int permille; -} fraction_t; - -typedef struct + int32_t mbar = 0; // pressure up to 2000 bar +}; +static inline pressure_t operator""_mbar(unsigned long long mbar) { - int grams; -} weight_t; - -typedef struct + return pressure_t { .mbar = static_cast(mbar) }; +} +static inline pressure_t operator""_bar(unsigned long long bar) { - int udeg; -} degrees_t; + return pressure_t { .mbar = static_cast(bar * 1000) }; +} +static inline pressure_t operator""_atm(unsigned long long atm) +{ + return pressure_t { .mbar = static_cast(round(atm * 1013.25)) }; +} -typedef struct pos { +struct o2pressure_t : public unit_base +{ + uint16_t mbar = 0; +}; +static inline o2pressure_t operator""_baro2(unsigned long long bar) +{ + return o2pressure_t { .mbar = static_cast(bar * 1000) }; +} + +struct bearing_t : public unit_base +{ + int16_t degrees = 0; +}; + +struct temperature_t : public unit_base +{ + uint32_t mkelvin = 0; // up to 4 MK (temperatures in K are always positive) +}; +static inline temperature_t operator""_K(unsigned long long K) +{ + return temperature_t { .mkelvin = static_cast(K * 1000) }; +} + +struct temperature_sum_t : public unit_base +{ + uint64_t mkelvin = 0; // up to 18446744073 MK (temperatures in K are always positive) +}; + +struct volume_t : public unit_base +{ + int mliter = 0; +}; +static inline volume_t operator""_ml(unsigned long long ml) +{ + return volume_t { .mliter = static_cast(ml) }; +} +static inline volume_t operator""_l(unsigned long long l) +{ + return volume_t { .mliter = static_cast(l * 1000) }; +} + +struct fraction_t : public unit_base +{ + int permille = 0; +}; +static inline fraction_t operator""_permille(unsigned long long permille) +{ + return fraction_t { .permille = static_cast(permille) }; +} +static inline fraction_t operator""_percent(unsigned long long percent) +{ + return fraction_t { .permille = static_cast(percent * 10) }; +} + +struct weight_t : public unit_base +{ + int grams = 0; +}; + +struct degrees_t : public unit_base +{ + int udeg = 0; +}; + +struct location_t { degrees_t lat, lon; -} location_t; - -static const location_t zero_location = { { 0 }, { 0 }}; +}; extern void parse_location(const char *, location_t *); +extern unsigned int get_distance(location_t loc1, location_t loc2); static inline bool has_location(const location_t *loc) { return loc->lat.udeg || loc->lon.udeg; } -static inline bool same_location(const location_t *a, const location_t *b) +static inline bool operator==(const location_t &a, const location_t &b) { - return (a->lat.udeg == b->lat.udeg) && (a->lon.udeg == b->lon.udeg); + return (a.lat.udeg == b.lat.udeg) && (a.lon.udeg == b.lon.udeg); +} + +static inline bool operator!=(const location_t &a, const location_t &b) +{ + return !(a == b); } static inline location_t create_location(double lat, double lon) { location_t location = { - { (int) lrint(lat * 1000000) }, - { (int) lrint(lon * 1000000) } + { .udeg = int_cast(lat * 1000000) }, + { .udeg = int_cast(lon * 1000000) } }; return location; } @@ -178,7 +299,7 @@ static inline double grams_to_lbs(int grams) static inline int lbs_to_grams(double lbs) { - return (int)lrint(lbs * 453.6); + return int_cast(lbs * 453.6); } static inline double ml_to_cuft(int ml) @@ -206,11 +327,6 @@ static inline long feet_to_mm(double feet) return lrint(feet * 304.8); } -static inline int to_feet(depth_t depth) -{ - return (int)lrint(mm_to_feet(depth.mm)); -} - static inline double mkelvin_to_C(int mkelvin) { return (mkelvin - ZERO_C_IN_MKELVIN) / 1000.0; @@ -253,29 +369,27 @@ static inline double to_PSI(pressure_t pressure) static inline double bar_to_atm(double bar) { - return bar / SURFACE_PRESSURE * 1000; + return bar / (1_atm).mbar * 1000; } static inline double mbar_to_atm(int mbar) { - return (double)mbar / SURFACE_PRESSURE; + return (double)mbar / (1_atm).mbar; } static inline double mbar_to_PSI(int mbar) { - pressure_t p = { mbar }; + pressure_t p = { .mbar = mbar }; return to_PSI(p); } -static inline int32_t altitude_to_pressure(int32_t altitude) // altitude in mm above sea level -{ // returns atmospheric pressure in mbar - return (int32_t) (1013.0 * exp(- altitude / 7800000.0)); +static inline pressure_t altitude_to_pressure(int32_t altitude) { // altitude in mm above sea level + return pressure_t { .mbar = int_cast (1013.0 * exp(- altitude / 7800000.0)) }; } - -static inline int32_t pressure_to_altitude(int32_t pressure) // pressure in mbar +static inline int32_t pressure_to_altitude(pressure_t pressure) { // returns altitude in mm above sea level - return (int32_t) (log(1013.0 / pressure) * 7800000); + return (int32_t) (log(1013.0 / pressure.mbar) * 7800000); } /* @@ -327,15 +441,15 @@ struct units { * actually use. Similarly, C instead of Kelvin. * And kg instead of g. */ -#define SI_UNITS \ - { \ - .length = METERS, .volume = LITER, .pressure = BAR, .temperature = CELSIUS, .weight = KG, \ - .vertical_speed_time = MINUTES, .duration_units = MIXED, .show_units_table = false \ +#define SI_UNITS \ + { \ + .length = units::METERS, .volume = units::LITER, .pressure = units::BAR, .temperature = units::CELSIUS, .weight = units::KG, \ + .vertical_speed_time = units::MINUTES, .duration_units = units::MIXED, .show_units_table = false \ } extern const struct units SI_units, IMPERIAL_units; -extern const struct units *get_units(void); +extern const struct units *get_units(); extern int get_pressure_units(int mb, const char **units); extern double get_depth_units(int mm, int *frac, const char **units); @@ -346,8 +460,5 @@ extern double get_vertical_speed_units(unsigned int mms, int *frac, const char * extern depth_t units_to_depth(double depth); extern int units_to_sac(double volume); -#ifdef __cplusplus -} -#endif #endif diff --git a/core/unix.cpp b/core/unix.cpp index c0b288cc2..801507295 100644 --- a/core/unix.cpp +++ b/core/unix.cpp @@ -1,9 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 /* unix.c */ /* implements UNIX specific functions */ -#include "ssrf.h" -#include "dive.h" #include "file.h" +#include "subsurfacestartup.h" #include "subsurface-string.h" #include "device.h" #include "libdivecomputer.h" @@ -35,34 +34,32 @@ static std::string make_default_filename() return system_default_path() + "/" + user + ".xml"; } -extern "C" { - // the DE should provide us with a default font and font size... -const char unix_system_divelist_default_font[] = "Sans"; -const char *system_divelist_default_font = unix_system_divelist_default_font; +using namespace std::string_literals; +std::string system_divelist_default_font = "Sans"s; double system_divelist_default_font_size = -1.0; -void subsurface_OS_pref_setup(void) +void subsurface_OS_pref_setup() { // nothing } -bool subsurface_ignore_font(const char *) +bool subsurface_ignore_font(const std::string &) { // there are no old default fonts 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; } int enumerate_devices(device_callback_t callback, void *userdata, unsigned int transport) @@ -189,12 +186,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 */ } @@ -203,5 +200,3 @@ bool subsurface_user_is_root() { return geteuid() == 0; } - -} diff --git a/core/uploadDiveLogsDE.cpp b/core/uploadDiveLogsDE.cpp index 0589934c2..ab12b00e4 100644 --- a/core/uploadDiveLogsDE.cpp +++ b/core/uploadDiveLogsDE.cpp @@ -7,8 +7,11 @@ #include "core/errorhelper.h" #include "core/qthelper.h" #include "core/dive.h" -#include "core/membuffer.h" +#include "core/divelist.h" +#include "core/divelog.h" #include "core/divesite.h" +#include "core/membuffer.h" +#include "core/range.h" #include "core/cloudstorage.h" #include "core/xmlparams.h" #ifndef SUBSURFACE_MOBILE @@ -87,15 +90,13 @@ bool uploadDiveLogsDE::prepareDives(const QString &tempfile, bool selected) } /* walk the dive list in chronological order */ - int i; - struct dive *dive; - for_each_dive (i, dive) { + for (auto [i, dive]: enumerated_range(divelog.dives)) { char filename[PATH_MAX]; int streamsize; char *membuf; xmlDoc *transformed; struct zip_source *s; - struct membufferpp mb; + membuffer mb; struct xml_params *params = alloc_xml_params(); /* @@ -112,17 +113,18 @@ bool uploadDiveLogsDE::prepareDives(const QString &tempfile, bool selected) if (ds) { put_format(&mb, "location, " gps='", "'"); put_format(&mb, ">\n"); - if (ds->taxonomy.nr) { - for (int j = 0; j < ds->taxonomy.nr; j++) { - struct taxonomy *t = &ds->taxonomy.category[j]; - if (t->category != TC_NONE && t->category == prefs.geocoding.category[j] && t->value) { - put_format(&mb, " category); - put_format(&mb, " origin='%d' value='", t->origin); - put_quoted(&mb, t->value, 1, 0); + for (int i = 0; i < 3; i++) { + if (prefs.geocoding.category[i] == TC_NONE) + continue; + for (auto const &t: ds->taxonomy) { + if (t.category == prefs.geocoding.category[i] && !t.value.empty()) { + put_format(&mb, " \n"); } } @@ -130,7 +132,7 @@ bool uploadDiveLogsDE::prepareDives(const QString &tempfile, bool selected) put_format(&mb, "\n\n"); } - save_one_dive_to_mb(&mb, dive, false); + save_one_dive_to_mb(&mb, *dive, false); if (ds) { put_format(&mb, "\n"); diff --git a/core/uploadDiveLogsDE.h b/core/uploadDiveLogsDE.h index 0259d1fb1..11b99c262 100644 --- a/core/uploadDiveLogsDE.h +++ b/core/uploadDiveLogsDE.h @@ -6,7 +6,6 @@ #include #include - class uploadDiveLogsDE : public QObject { Q_OBJECT @@ -39,4 +38,5 @@ private: QHttpMultiPart *multipart; QTimer timeout; }; + #endif // UPLOADDIVELOGSDE_H diff --git a/core/uploadDiveShare.cpp b/core/uploadDiveShare.cpp index 329cce9a1..420e59cc5 100644 --- a/core/uploadDiveShare.cpp +++ b/core/uploadDiveShare.cpp @@ -27,7 +27,7 @@ uploadDiveShare::uploadDiveShare(): void uploadDiveShare::doUpload(bool selected, const QString &uid, bool noPublic) { //generate json - struct membufferpp buf; + membuffer buf; export_list(&buf, NULL, selected, false); QByteArray json_data(buf.buffer, buf.len); diff --git a/core/uploadDiveShare.h b/core/uploadDiveShare.h index fdd941e67..26729b1e9 100644 --- a/core/uploadDiveShare.h +++ b/core/uploadDiveShare.h @@ -4,7 +4,6 @@ #include #include - class uploadDiveShare : public QObject { Q_OBJECT @@ -29,4 +28,5 @@ private: QNetworkReply *reply; QTimer timeout; }; + #endif // UPLOADDIVESHARE_H diff --git a/core/version.c b/core/version.c deleted file mode 100644 index 9774d7b5f..000000000 --- a/core/version.c +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "ssrf-version.h" - -// let's leave the two redundant functions in case we change our minds on git SHAs -const char *subsurface_git_version(void) -{ - return CANONICAL_VERSION_STRING_4; -} - -const char *subsurface_canonical_version(void) -{ - return CANONICAL_VERSION_STRING; -} - diff --git a/core/version.cpp b/core/version.cpp new file mode 100644 index 000000000..e6b47d579 --- /dev/null +++ b/core/version.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "ssrf-version.h" + +// let's leave the two redundant functions in case we change our minds on git SHAs +const char *subsurface_git_version() +{ + return CANONICAL_VERSION_STRING_4; +} + +const char *subsurface_canonical_version() +{ + return CANONICAL_VERSION_STRING; +} + +static int min_datafile_version = 0; + +int get_min_datafile_version() +{ + return min_datafile_version; +} + +void report_datafile_version(int version) +{ + if (min_datafile_version == 0 || min_datafile_version > version) + min_datafile_version = version; +} + +int clear_min_datafile_version() +{ + return min_datafile_version; +} diff --git a/core/version.h b/core/version.h index 98385e970..d516d88f2 100644 --- a/core/version.h +++ b/core/version.h @@ -1,15 +1,13 @@ #ifndef VERSION_H #define VERSION_H -#ifdef __cplusplus -extern "C" { -#endif +/* this is used for both git and xml format */ +static constexpr int dataformat_version = 3; -const char *subsurface_git_version(void); -const char *subsurface_canonical_version(void); - -#ifdef __cplusplus -} -#endif +const char *subsurface_git_version(); +const char *subsurface_canonical_version(); +int get_min_datafile_version(); +void report_datafile_version(int version); +int clear_min_datafile_version(); #endif diff --git a/core/videoframeextractor.cpp b/core/videoframeextractor.cpp index d377a7fc9..eec4b0825 100644 --- a/core/videoframeextractor.cpp +++ b/core/videoframeextractor.cpp @@ -70,7 +70,7 @@ void VideoFrameExtractor::processItem(QString originalFilename, QString filename // Determine the time where we want to extract the image. // If the duration is < 10 sec, just snap the first frame - duration_t position = { 0 }; + duration_t position; if (duration.seconds > 10) { // We round to second-precision. To be sure that we don't attempt reading past the // video's end, round down by one second. @@ -83,7 +83,7 @@ void VideoFrameExtractor::processItem(QString originalFilename, QString filename .arg(position.seconds % 60, 2, 10, QChar('0')); QProcess ffmpeg; - ffmpeg.start(prefs.ffmpeg_executable, QStringList { + ffmpeg.start(prefs.ffmpeg_executable.c_str(), QStringList { "-ss", posString, "-i", filename, "-vframes", "1", "-q:v", "2", "-f", "image2", "-" }); if (!ffmpeg.waitForStarted()) { diff --git a/core/webservice.h b/core/webservice.h index 1675a1743..bdb9ddfbf 100644 --- a/core/webservice.h +++ b/core/webservice.h @@ -2,11 +2,7 @@ #ifndef WEBSERVICE_H #define WEBSERVICE_H -#ifdef __cplusplus -extern "C" { -#endif - -//extern void webservice_download_dialog(void); +//extern void webservice_download_dialog(); //extern bool webservice_request_user_xml(const gchar *, gchar **, unsigned int *, unsigned int *); extern int divelogde_upload(char *fn, char **error); extern unsigned int download_dialog_parse_response(char *xmldata, unsigned int len); @@ -18,8 +14,4 @@ enum { DD_STATUS_ERROR_PARSE, }; - -#ifdef __cplusplus -} -#endif #endif // WEBSERVICE_H diff --git a/core/windows.cpp b/core/windows.cpp index 76a37fe6a..a4f04e304 100644 --- a/core/windows.cpp +++ b/core/windows.cpp @@ -1,13 +1,12 @@ // SPDX-License-Identifier: GPL-2.0 /* windows.c */ /* implements Windows specific functions */ -#include "ssrf.h" #include -#include "dive.h" #include "device.h" #include "libdivecomputer.h" #include "file.h" #include "errorhelper.h" +#include "subsurfacestartup.h" #include "subsurfacesysinfo.h" #undef _WIN32_WINNT #define _WIN32_WINNT 0x500 @@ -100,42 +99,39 @@ static std::wstring make_default_filename() return path + L"\\" + filename; } -extern "C" { - -const char non_standard_system_divelist_default_font[] = "Calibri"; -const char current_system_divelist_default_font[] = "Segoe UI"; -const char *system_divelist_default_font = non_standard_system_divelist_default_font; +using namespace std::string_literals; +static std::string non_standard_system_divelist_default_font = "Calibri"s; +static std::string current_system_divelist_default_font = "Segoe UI"s; +std::string system_divelist_default_font; double system_divelist_default_font_size = -1; -void subsurface_OS_pref_setup(void) +void subsurface_OS_pref_setup() { - if (isWin7Or8()) - system_divelist_default_font = current_system_divelist_default_font; + system_divelist_default_font = isWin7Or8() ? current_system_divelist_default_font + : non_standard_system_divelist_default_font; } -bool subsurface_ignore_font(const char *font) +bool subsurface_ignore_font(const std::string &font) { // if this is running on a recent enough version of Windows and the font // passed in is the pre 4.3 default font, ignore it - if (isWin7Or8() && strcmp(font, non_standard_system_divelist_default_font) == 0) - return true; - return false; + return isWin7Or8() && font == non_standard_system_divelist_default_font; } #define utf8_to_utf16(s) utf8_to_utf16_fl(s, __FILE__, __LINE__) /* '\' not included at the end. */ -const char *system_default_directory(void) +std::string system_default_directory() { static std::string path = utf16_to_utf8(system_default_path()); - return path.c_str(); + return path; } -const char *system_default_filename(void) +std::string system_default_filename() { static std::string path = utf16_to_utf8(make_default_filename()); - return path.c_str(); + return path; } int enumerate_devices(device_callback_t callback, void *userdata, unsigned int transport) @@ -375,7 +371,7 @@ static struct { } console_desc; #endif -void subsurface_console_init(void) +void subsurface_console_init() { /* if this is a console app already, do nothing */ #ifndef WIN32_CONSOLE_APP @@ -406,7 +402,7 @@ void subsurface_console_init(void) #endif } -void subsurface_console_exit(void) +void subsurface_console_exit() { #ifndef WIN32_CONSOLE_APP /* close handles */ @@ -427,5 +423,3 @@ bool subsurface_user_is_root() /* FIXME: Detect admin rights */ return false; } - -} diff --git a/core/windowtitleupdate.cpp b/core/windowtitleupdate.cpp index a0cc277b3..a6c5f2425 100644 --- a/core/windowtitleupdate.cpp +++ b/core/windowtitleupdate.cpp @@ -3,7 +3,7 @@ WindowTitleUpdate windowTitleUpdate; -extern "C" void updateWindowTitle() +void updateWindowTitle() { emit windowTitleUpdate.updateTitle(); } diff --git a/core/worldmap-save.cpp b/core/worldmap-save.cpp index 76826da66..efe1bf19a 100644 --- a/core/worldmap-save.cpp +++ b/core/worldmap-save.cpp @@ -10,10 +10,12 @@ #include #include "dive.h" -#include "membuffer.h" +#include "divelist.h" +#include "divelog.h" #include "divesite.h" #include "errorhelper.h" #include "file.h" +#include "membuffer.h" #include "save-html.h" #include "format.h" #include "worldmap-save.h" @@ -28,43 +30,39 @@ static const char *getGoogleApi() static void writeMarkers(struct membuffer *b, bool selected_only) { - int i, dive_no = 0; - struct dive *dive; - std::string pre, post; + int dive_no = 0; - for_each_dive (i, dive) { - if (selected_only) { - if (!dive->selected) + for (auto &dive: divelog.dives) { + if (selected_only && !dive->selected) continue; - } - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!dive_site_has_gps_location(ds)) + struct dive_site *ds = dive->dive_site; + if (!ds || !ds->has_gps_location()) continue; put_degrees(b, ds->location.lat, "temp = new google.maps.Marker({position: new google.maps.LatLng(", ""); put_degrees(b, ds->location.lon, ",", ")});\n"); put_string(b, "markers.push(temp);\ntempinfowindow = new google.maps.InfoWindow({content: '
'+'
'+'
'+'
"); - pre = format_string_std("

%s ", translate("gettextFromC", "Date:")); - put_HTML_date(b, dive, pre.c_str(), "

"); + std::string pre = format_string_std("

%s ", translate("gettextFromC", "Date:")); + put_HTML_date(b, *dive, pre.c_str(), "

"); pre = format_string_std("

%s ", translate("gettextFromC", "Time:")); - put_HTML_time(b, dive, pre.c_str(), "

"); + put_HTML_time(b, *dive, pre.c_str(), "

"); pre = format_string_std("

%s ", translate("gettextFromC", "Duration:")); - post = format_string_std(" %s

", translate("gettextFromC", "min")); + std::string post = format_string_std(" %s

", translate("gettextFromC", "min")); put_duration(b, dive->duration, pre.c_str(), post.c_str()); put_string(b, "

"); put_HTML_quoted(b, translate("gettextFromC", "Max. depth:")); - put_HTML_depth(b, dive, " ", "

"); + put_HTML_depth(b, *dive, " ", "

"); put_string(b, "

"); put_HTML_quoted(b, translate("gettextFromC", "Air temp.:")); - put_HTML_airtemp(b, dive, " ", "

"); + put_HTML_airtemp(b, *dive, " ", "

"); put_string(b, "

"); put_HTML_quoted(b, translate("gettextFromC", "Water temp.:")); - put_HTML_watertemp(b, dive, " ", "

"); + put_HTML_watertemp(b, *dive, " ", "

"); pre = format_string_std("

%s ", translate("gettextFromC", "Location:")); put_string(b, pre.c_str()); - put_HTML_quoted(b, get_dive_location(dive)); + put_HTML_quoted(b, dive->get_location().c_str()); put_string(b, "

"); pre = format_string_std("

%s ", translate("gettextFromC", "Notes:")); - put_HTML_notes(b, dive, pre.c_str(), "

"); + put_HTML_notes(b, *dive, pre.c_str(), "

"); put_string(b, "

'+'
'+'
'});\ninfowindows.push(tempinfowindow);\n"); put_format(b, "google.maps.event.addListener(markers[%d], 'mouseover', function() {\ninfowindows[%d].open(map,markers[%d]);}", dive_no, dive_no, dive_no); put_format(b, ");google.maps.event.addListener(markers[%d], 'mouseout', function() {\ninfowindows[%d].close();});\n", dive_no, dive_no); @@ -106,11 +104,11 @@ static void export_doit(struct membuffer *b, const bool selected_only) put_string(b, "\t\n\n
\n\n"); } -extern "C" void export_worldmap_HTML(const char *file_name, const bool selected_only) +void export_worldmap_HTML(const char *file_name, const bool selected_only) { FILE *f; - struct membufferpp buf; + membuffer buf; export_doit(&buf, selected_only); f = subsurface_fopen(file_name, "w+"); diff --git a/core/worldmap-save.h b/core/worldmap-save.h index f3e2ddd29..5b83b4bb9 100644 --- a/core/worldmap-save.h +++ b/core/worldmap-save.h @@ -2,14 +2,6 @@ #ifndef WORLDMAP_SAVE_H #define WORLDMAP_SAVE_H -#ifdef __cplusplus -extern "C" { -#endif - extern void export_worldmap_HTML(const char *file_name, bool selected_only); -#ifdef __cplusplus -} -#endif - #endif diff --git a/core/xmlparams.cpp b/core/xmlparams.cpp index a12739a4d..3ab6f1c4c 100644 --- a/core/xmlparams.cpp +++ b/core/xmlparams.cpp @@ -1,42 +1,42 @@ // SPDX-License-Identifier: GPL-2.0 #include "xmlparams.h" -extern "C" struct xml_params *alloc_xml_params() +struct xml_params *alloc_xml_params() { return new xml_params; } -extern "C" void free_xml_params(struct xml_params *params) +void free_xml_params(struct xml_params *params) { delete params; } -extern "C" void xml_params_resize(struct xml_params *params, int count) +void xml_params_resize(struct xml_params *params, int count) { params->items.resize(count); } -extern "C" void xml_params_add(struct xml_params *params, const char *key, const char *value) +void xml_params_add(struct xml_params *params, const char *key, const char *value) { params->items.push_back({ std::string(key), std::string(value) }); } -extern "C" void xml_params_add_int(struct xml_params *params, const char *key, int value) +void xml_params_add_int(struct xml_params *params, const char *key, int value) { params->items.push_back({ std::string(key), std::to_string(value) }); } -extern "C" int xml_params_count(const struct xml_params *params) +int xml_params_count(const struct xml_params *params) { return (int)params->items.size(); } -extern "C" const char *xml_params_get_key(const struct xml_params *params, int idx) +const char *xml_params_get_key(const struct xml_params *params, int idx) { return params->items[idx].first.c_str(); } -extern "C" const char *xml_params_get_value(const struct xml_params *params, int idx) +const char *xml_params_get_value(const struct xml_params *params, int idx) { return params->items[idx].second.c_str(); } @@ -48,7 +48,7 @@ extern void xml_params_set_value(struct xml_params *params, int idx, const char params->items[idx].second = value; } -extern "C" const char **xml_params_get(const struct xml_params *params) +const char **xml_params_get(const struct xml_params *params) { if (!params) return nullptr; diff --git a/core/xmlparams.h b/core/xmlparams.h index 38e175935..e91a30ce0 100644 --- a/core/xmlparams.h +++ b/core/xmlparams.h @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 // Small helper class that keeps track of key/value pairs to -// pass to the XML-routines as parameters. Uses C++ for memory -// management, but provides a C interface via anonymous struct. +// pass to the XML-routines as parameters. +#ifndef XMLPARAMS_H +#define XMLPARAMS_H -#ifdef __cplusplus #include #include @@ -12,16 +12,6 @@ struct xml_params { mutable std::vector data; }; -#else - -struct xml_params; - -#endif - -#ifdef __cplusplus -extern "C" { -#endif - // Return values marked as "not stable" may be invalidated when calling // an xml_params_*() function that takes a non-const xml_params parameter. extern struct xml_params *alloc_xml_params(); @@ -35,6 +25,4 @@ extern const char *xml_params_get_value(const struct xml_params *params, int idx extern void xml_params_set_value(struct xml_params *params, int idx, const char *value); extern const char **xml_params_get(const struct xml_params *params); // not stable -#ifdef __cplusplus -} #endif diff --git a/desktop-widgets/CMakeLists.txt b/desktop-widgets/CMakeLists.txt index 4d4ed03c9..fb09f51f6 100644 --- a/desktop-widgets/CMakeLists.txt +++ b/desktop-widgets/CMakeLists.txt @@ -68,6 +68,8 @@ set(SUBSURFACE_INTERFACE about.h configuredivecomputerdialog.cpp configuredivecomputerdialog.h + divecomponentselection.cpp + divecomponentselection.h divelistview.cpp divelistview.h divelogexportdialog.cpp diff --git a/desktop-widgets/configuredivecomputerdialog.cpp b/desktop-widgets/configuredivecomputerdialog.cpp index 03af9121a..d4698d76a 100644 --- a/desktop-widgets/configuredivecomputerdialog.cpp +++ b/desktop-widgets/configuredivecomputerdialog.cpp @@ -145,7 +145,6 @@ ConfigureDiveComputerDialog::ConfigureDiveComputerDialog(const QString &filename ui.connectBluetoothButton->setVisible(false); #endif - memset(&device_data, 0, sizeof(device_data)); fill_computer_list(); unsigned int selectedDiveComputerIndex = 0; @@ -267,7 +266,7 @@ ConfigureDiveComputerDialog::ConfigureDiveComputerDialog(const QString &filename OstcFirmwareCheck::OstcFirmwareCheck(const QString &product) : parent(0) { QUrl url; - memset(&devData, 1, sizeof(devData)); + devData = device_data_t(); if (product == "OSTC 3" || product == "OSTC 3+" || product == "OSTC cR" || product == "OSTC Plus") { url = QUrl("http://www.heinrichsweikamp.net/autofirmware/ostc3_changelog.txt"); latestFirmwareHexFile = QString("http://www.heinrichsweikamp.net/autofirmware/ostc3_firmware.hex"); @@ -312,7 +311,7 @@ void OstcFirmwareCheck::checkLatest(QWidget *_parent, device_data_t *data, const QStringList fwParts = latestFirmwareAvailable.split("."); int latestFirmwareAvailableNumber; - if (strcmp(data->product, "OSTC 4") == 0) { + if (data->product == "OSTC 4") { unsigned char X, Y, Z, beta; X = (firmwareOnDevice & 0xF800) >> 11; Y = (firmwareOnDevice & 0x07C0) >> 6; @@ -934,23 +933,23 @@ void ConfigureDiveComputerDialog::getDeviceData() QString device = ui.device->currentText(); #ifdef BT_SUPPORT if (isBluetoothAddress(device)) { - QString name; - device = copy_qstring(extractBluetoothNameAddress(device, name)); - device_data.btname = copy_qstring(name); + auto [new_address, name] = extractBluetoothNameAddress(device); + device = new_address; + device_data.btname = name.toStdString(); device_data.bluetooth_mode = true; } else #endif { device_data.bluetooth_mode = false; } - device_data.devname = copy_qstring(device); + device_data.devname = device.toStdString(); const DiveComputerEntry selectedDiveComputer = supportedDiveComputers[ui.DiveComputerList->currentRow()]; QString vendor = selectedDiveComputer.vendor; QString product = selectedDiveComputer.product; - device_data.vendor = copy_qstring(vendor); - device_data.product = copy_qstring(product); + device_data.vendor = vendor.toStdString(); + device_data.product = product.toStdString(); device_data.descriptor = descriptorLookup.value(vendor.toLower() + product.toLower()); device_data.diveid = 0; @@ -1533,10 +1532,10 @@ void ConfigureDiveComputerDialog::dc_open() ui.progressBar->setFormat(tr("Connected to device")); - qPrefDiveComputer::set_device(device_data.devname); - qPrefDiveComputer::set_device_name(device_data.btname); - qPrefDiveComputer::set_vendor(device_data.vendor); - qPrefDiveComputer::set_product(device_data.product); + qPrefDiveComputer::set_device(device_data.devname.c_str()); + qPrefDiveComputer::set_device_name(device_data.btname.c_str()); + qPrefDiveComputer::set_vendor(device_data.vendor.c_str()); + qPrefDiveComputer::set_product(device_data.product.c_str()); } void ConfigureDiveComputerDialog::dc_close() diff --git a/desktop-widgets/divecomponentselection.cpp b/desktop-widgets/divecomponentselection.cpp new file mode 100644 index 000000000..0603a9081 --- /dev/null +++ b/desktop-widgets/divecomponentselection.cpp @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "divecomponentselection.h" +#include "core/dive.h" +#include "core/divesite.h" +#include "core/format.h" +#include "core/qthelper.h" // for get_dive_date_string() +#include "core/selection.h" +#include "core/string-format.h" + +#include +#include + +template +static void assign_paste_data(QCheckBox &checkbox, const T &src, std::optional &dest) +{ + if (checkbox.isChecked()) + dest = src; + else + dest = {}; +} + +template +static void set_checked(QCheckBox &checkbox, const std::optional &v) +{ + checkbox.setChecked(v.has_value()); +} + +DiveComponentSelection::DiveComponentSelection(dive_paste_data &data, QWidget *parent) : + QDialog(parent), data(data) +{ + ui.setupUi(this); + set_checked(*ui.divesite, data.divesite); + set_checked(*ui.diveguide, data.diveguide); + set_checked(*ui.buddy, data.buddy); + set_checked(*ui.rating, data.rating); + set_checked(*ui.visibility, data.visibility); + set_checked(*ui.notes, data.notes); + set_checked(*ui.suit, data.suit); + set_checked(*ui.tags, data.tags); + set_checked(*ui.cylinders, data.cylinders); + set_checked(*ui.weights, data.weights); + set_checked(*ui.number, data.number); + set_checked(*ui.when, data.when); + connect(ui.buttonBox, &QDialogButtonBox::clicked, this, &DiveComponentSelection::buttonClicked); + QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_W), this); + connect(close, &QShortcut::activated, this, &DiveComponentSelection::close); + QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this); + connect(quit, &QShortcut::activated, parent, &QWidget::close); +} + +void DiveComponentSelection::buttonClicked(QAbstractButton *button) +{ + if (current_dive && ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + // Divesite is special, as we store the uuid not the pointer, since the + // user may delete the divesite after copying. + assign_paste_data(*ui.divesite, current_dive->dive_site ? current_dive->dive_site->uuid : uint32_t(), data.divesite); + assign_paste_data(*ui.diveguide, current_dive->diveguide, data.diveguide); + assign_paste_data(*ui.buddy, current_dive->buddy, data.buddy); + assign_paste_data(*ui.rating, current_dive->rating, data.rating); + assign_paste_data(*ui.visibility, current_dive->visibility, data.visibility); + assign_paste_data(*ui.notes, current_dive->notes, data.notes); + assign_paste_data(*ui.suit, current_dive->suit, data.suit); + assign_paste_data(*ui.tags, current_dive->tags, data.tags); + assign_paste_data(*ui.cylinders, current_dive->cylinders, data.cylinders); + assign_paste_data(*ui.weights, current_dive->weightsystems, data.weights); + assign_paste_data(*ui.number, current_dive->number, data.number); + assign_paste_data(*ui.when, current_dive->when, data.when); + + std::string text; + if (data.divesite && current_dive->dive_site) + text += tr("Dive site: ").toStdString() + current_dive->dive_site->name + '\n'; + if (data.diveguide) + text += tr("Dive guide: ").toStdString() + current_dive->diveguide + '\n'; + if (data.buddy) + text += tr("Buddy: ").toStdString() + current_dive->buddy + '\n'; + if (data.rating) + text += tr("Rating: ").toStdString() + std::string(current_dive->rating, '*') + '\n'; + if (data.visibility) + text += tr("Visibility: ").toStdString() + std::string(current_dive->visibility, '*') + '\n'; + if (data.wavesize) + text += tr("Wave size: ").toStdString() + std::string(current_dive->wavesize, '*') + '\n'; + if (data.current) + text += tr("Current: ").toStdString() + std::string(current_dive->current, '*') + '\n'; + if (data.surge) + text += tr("Surge: ").toStdString() + std::string(current_dive->surge, '*') + '\n'; + if (data.chill) + text += tr("Chill: ").toStdString() + std::string(current_dive->chill, '*') + '\n'; + if (data.notes) + text += tr("Notes:\n").toStdString() + current_dive->notes + '\n'; + if (data.suit) + text += tr("Suit: ").toStdString() + current_dive->suit + '\n'; + if (data.tags) + text += tr("Tags: ").toStdString() + taglist_get_tagstring(current_dive->tags) + '\n'; + if (data.cylinders) + text += tr("Cylinders:\n").toStdString() + formatGas(current_dive).toStdString(); + if (data.weights) + text += tr("Weights:\n").toStdString() + formatWeightList(current_dive).toStdString(); + if (data.number) + text += tr("Dive number: ").toStdString() + casprintf_loc("%d", current_dive->number) + '\n'; + if (data.when) + text += tr("Date / time: ").toStdString() + get_dive_date_string(current_dive->when).toStdString() + '\n'; + QApplication::clipboard()->setText(QString::fromStdString(text)); + } +} diff --git a/desktop-widgets/divecomponentselection.h b/desktop-widgets/divecomponentselection.h new file mode 100644 index 000000000..00aa5122f --- /dev/null +++ b/desktop-widgets/divecomponentselection.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef DIVECOMPONENTSELECTION_H +#define DIVECOMPONENTSELECTION_H + +#include "ui_divecomponentselection.h" + +struct dive_paste_data; + +class DiveComponentSelection : public QDialog { + Q_OBJECT +public: + explicit DiveComponentSelection(dive_paste_data &data, QWidget *parent = nullptr); +private +slots: + void buttonClicked(QAbstractButton *button); + +private: + Ui::DiveComponentSelectionDialog ui; + dive_paste_data &data; +}; + +#endif diff --git a/desktop-widgets/divelistview.cpp b/desktop-widgets/divelistview.cpp index 495f6f597..fc4e32763 100644 --- a/desktop-widgets/divelistview.cpp +++ b/desktop-widgets/divelistview.cpp @@ -22,6 +22,7 @@ #include "commands/command_base.h" #include "core/errorhelper.h" #include "core/qthelper.h" +#include "core/range.h" #include "core/trip.h" #include "desktop-widgets/divelistview.h" #include "core/metrics.h" @@ -439,13 +440,13 @@ void DiveListView::selectDiveSitesOnMap(const std::vector &dives) // the dive-site selection is controlled by the filter not // by the selected dives. if (!DiveFilter::instance()->diveSiteMode()) { - QVector selectedSites; + std::vector selectedSites; selectedSites.reserve(dives.size()); for (dive *d: dives) { - if (!d->hidden_by_filter && d->dive_site && !selectedSites.contains(d->dive_site)) + if (!d->hidden_by_filter && d->dive_site && !range_contains(selectedSites, d->dive_site)) selectedSites.push_back(d->dive_site); } - MapWidget::instance()->setSelected(selectedSites); + MapWidget::instance()->setSelected(std::move(selectedSites)); } #endif } @@ -495,8 +496,8 @@ void DiveListView::selectionChanged(const QItemSelection &selected, const QItemS removeFromSelection.push_back(dive); } else if (dive_trip *trip = model->data(index, DiveTripModelBase::TRIP_ROLE).value()) { deselect_trip(trip); - for (int i = 0; i < trip->dives.nr; ++i) - removeFromSelection.push_back(trip->dives.dives[i]); + for (auto dive: trip->dives) + removeFromSelection.push_back(dive); } } for (const QModelIndex &index: newSelected.indexes()) { @@ -509,8 +510,8 @@ void DiveListView::selectionChanged(const QItemSelection &selected, const QItemS addToSelection.push_back(dive); } else if (dive_trip *trip = model->data(index, DiveTripModelBase::TRIP_ROLE).value()) { select_trip(trip); - for (int i = 0; i < trip->dives.nr; ++i) - addToSelection.push_back(trip->dives.dives[i]); + for (struct dive *d: trip->dives) + addToSelection.push_back(d); selectTripItems(index); } } @@ -547,12 +548,12 @@ static bool can_merge(const struct dive *a, const struct dive *b, enum asked_use if (a->when > b->when) return false; /* Don't merge dives if there's more than half an hour between them */ - if (dive_endtime(a) + 30 * 60 < b->when) { + if (a->endtime() + 30 * 60 < b->when) { if (*have_asked == NOTYET) { if (QMessageBox::warning(MainWindow::instance(), MainWindow::tr("Warning"), MainWindow::tr("Trying to merge dives with %1min interval in between").arg( - (b->when - dive_endtime(a)) / 60), + (b->when - a->endtime()) / 60), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { *have_asked = DONTMERGE; return false; @@ -595,7 +596,7 @@ void DiveListView::mergeDives() void DiveListView::splitDives() { for (struct dive *d: getDiveSelection()) - Command::splitDives(d, duration_t{-1}); + Command::splitDives(d, duration_t{ .seconds = -1}); } void DiveListView::addDivesToTrip() @@ -619,8 +620,8 @@ void DiveListView::merge_trip(const QModelIndex &a, int offset) int i = a.row() + offset; QModelIndex b = a.sibling(i, 0); - dive_trip_t *trip_a = a.data(DiveTripModelBase::TRIP_ROLE).value(); - dive_trip_t *trip_b = b.data(DiveTripModelBase::TRIP_ROLE).value(); + dive_trip *trip_a = a.data(DiveTripModelBase::TRIP_ROLE).value(); + dive_trip *trip_b = b.data(DiveTripModelBase::TRIP_ROLE).value(); if (trip_a == trip_b || !trip_a || !trip_b) return; Command::mergeTrips(trip_a, trip_b); @@ -665,7 +666,7 @@ void DiveListView::addToTrip(int delta) struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value(); int nr = selectionModel()->selectedRows().count(); QModelIndex t; - dive_trip_t *trip = NULL; + dive_trip *trip = NULL; // now look for the trip to add to, for this, loop over the selected dives and // check if its sibling is a trip. @@ -707,7 +708,7 @@ void DiveListView::contextMenuEvent(QContextMenuEvent *event) // let's remember where we are contextMenuIndex = indexAt(event->pos()); struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value(); - dive_trip_t *trip = contextMenuIndex.data(DiveTripModelBase::TRIP_ROLE).value(); + dive_trip *trip = contextMenuIndex.data(DiveTripModelBase::TRIP_ROLE).value(); QMenu popup(this); if (currentLayout == DiveTripModelBase::TREE) { // verify if there is a node that`s not expanded. @@ -754,9 +755,9 @@ void DiveListView::contextMenuEvent(QContextMenuEvent *event) bottom = first_selected_dive(); } } - if (is_trip_before_after(top, (currentOrder == Qt::AscendingOrder))) + if (divelog.is_trip_before_after(top, (currentOrder == Qt::AscendingOrder))) popup.addAction(tr("Add dive(s) to trip immediately above","",amount_selected), this, &DiveListView::addToTripAbove); - if (is_trip_before_after(bottom, (currentOrder == Qt::DescendingOrder))) + if (divelog.is_trip_before_after(bottom, (currentOrder == Qt::DescendingOrder))) popup.addAction(tr("Add dive(s) to trip immediately below","",amount_selected), this, &DiveListView::addToTripBelow); } } @@ -834,18 +835,15 @@ void DiveListView::matchImagesToDives(const QStringList &fileNames) // Create the data structure of pictures to be added: a list of pictures per dive. std::vector pics; for (const QString &fileName: fileNames) { - struct dive *d; - picture *pic = create_picture(qPrintable(fileName), shiftDialog.amount(), shiftDialog.matchAll(), &d); + auto [pic, d] = create_picture(fileName.toStdString(), shiftDialog.amount(), shiftDialog.matchAll()); if (!pic) continue; - PictureObj pObj(*pic); - free(pic); - auto it = std::find_if(pics.begin(), pics.end(), [d](const Command::PictureListForAddition &l) { return l.d == d; }); + auto it = std::find_if(pics.begin(), pics.end(), [dive=d](const Command::PictureListForAddition &l) { return l.d == dive; }); if (it == pics.end()) - pics.push_back(Command::PictureListForAddition { d, { std::move(pObj) } }); + pics.push_back(Command::PictureListForAddition { d, { std::move(*pic) } }); else - it->pics.push_back(std::move(pObj)); + it->pics.push_back(std::move(*pic)); } if (pics.empty()) diff --git a/desktop-widgets/divelogexportdialog.cpp b/desktop-widgets/divelogexportdialog.cpp index befe03bdd..a7cc4b4fb 100644 --- a/desktop-widgets/divelogexportdialog.cpp +++ b/desktop-widgets/divelogexportdialog.cpp @@ -207,7 +207,7 @@ void DiveLogExportDialog::on_buttonBox_accepted() if (!filename.contains('.')) filename.append(".xml"); QByteArray bt = QFile::encodeName(filename); - std::vector sites = getDiveSitesToExport(ui->exportSelected->isChecked()); + auto sites = getDiveSitesToExport(ui->exportSelected->isChecked()); save_dive_sites_logic(bt.data(), sites.data(), (int)sites.size(), ui->anonymize->isChecked()); } } else if (ui->exportImageDepths->isChecked()) { diff --git a/desktop-widgets/divelogimportdialog.cpp b/desktop-widgets/divelogimportdialog.cpp index 23ac210b7..760c65244 100644 --- a/desktop-widgets/divelogimportdialog.cpp +++ b/desktop-widgets/divelogimportdialog.cpp @@ -967,7 +967,7 @@ void DiveLogImportDialog::on_buttonBox_accepted() } QString source = fileNames.size() == 1 ? fileNames[0] : tr("multiple files"); - Command::importDives(&log, IMPORT_MERGE_ALL_TRIPS, source); + Command::importDives(&log, import_flags::merge_all_trips, source); } TagDragDelegate::TagDragDelegate(QObject *parent) : QStyledItemDelegate(parent) diff --git a/desktop-widgets/diveplanner.cpp b/desktop-widgets/diveplanner.cpp index a20b615c5..2184f6fce 100644 --- a/desktop-widgets/diveplanner.cpp +++ b/desktop-widgets/diveplanner.cpp @@ -23,27 +23,32 @@ #include #endif -DivePlannerWidget::DivePlannerWidget(dive &planned_dive, int dcNr, PlannerWidgets *parent) +DivePlannerWidget::DivePlannerWidget(const dive &planned_dive, int &dcNr, PlannerWidgets *parent) { DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); CylindersModel *cylinders = DivePlannerPointsModel::instance()->cylindersModel(); ui.setupUi(this); + + // should be the same order as in dive_comp_type! + QStringList divemodes = QStringList(); + for (int i = 0; i < FREEDIVE; i++) + divemodes.append(gettextFromC::tr(divemode_text_ui[i])); + ui.divemode->insertItems(0, divemodes); + ui.tableWidget->setTitle(tr("Dive planner points")); + ui.tableWidget->setBtnToolTip(tr("Add dive data point")); ui.tableWidget->setModel(plannerModel); connect(ui.tableWidget, &TableView::itemClicked, plannerModel, &DivePlannerPointsModel::remove); - ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::GAS, new AirTypesDelegate(planned_dive, this)); - ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::DIVEMODE, new DiveTypesDelegate(this)); + connect(ui.tableWidget, &TableView::addButtonClicked, plannerModel, &DivePlannerPointsModel::addDefaultStop); + ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::GAS, new GasTypesDelegate(planned_dive, dcNr, this)); + ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::DIVEMODE, new DiveTypesDelegate(planned_dive, dcNr, this)); + ui.cylinderTableWidget->setTitle(tr("Available gases")); ui.cylinderTableWidget->setBtnToolTip(tr("Add cylinder")); ui.cylinderTableWidget->setModel(cylinders); - connect(ui.cylinderTableWidget, &TableView::itemClicked, cylinders, &CylindersModel::remove); - ui.waterType->setItemData(0, FRESHWATER_SALINITY); - ui.waterType->setItemData(1, SEAWATER_SALINITY); - ui.waterType->setItemData(2, EN13319_SALINITY); - waterTypeUpdateTexts(); - QTableView *view = ui.cylinderTableWidget->view(); + connect(ui.cylinderTableWidget, &TableView::itemClicked, cylinders, &CylindersModel::remove); view->setColumnHidden(CylindersModel::START, true); view->setColumnHidden(CylindersModel::END, true); view->setColumnHidden(CylindersModel::DEPTH, false); @@ -52,18 +57,22 @@ DivePlannerWidget::DivePlannerWidget(dive &planned_dive, int dcNr, PlannerWidget view->setColumnHidden(CylindersModel::SENSORS, true); view->setItemDelegateForColumn(CylindersModel::TYPE, new TankInfoDelegate(this)); auto tankUseDelegate = new TankUseDelegate(this); - tankUseDelegate->setCurrentDC(get_dive_dc(&planned_dive, dcNr)); + tankUseDelegate->setDiveDc(planned_dive, dcNr); view->setItemDelegateForColumn(CylindersModel::USE, tankUseDelegate); connect(ui.cylinderTableWidget, &TableView::addButtonClicked, plannerModel, &DivePlannerPointsModel::addCylinder_clicked); - connect(ui.tableWidget, &TableView::addButtonClicked, plannerModel, &DivePlannerPointsModel::addDefaultStop); connect(cylinders, &CylindersModel::dataChanged, plannerModel, &DivePlannerPointsModel::emitDataChanged); connect(cylinders, &CylindersModel::dataChanged, plannerModel, &DivePlannerPointsModel::cylinderModelEdited); connect(cylinders, &CylindersModel::rowsInserted, plannerModel, &DivePlannerPointsModel::cylinderModelEdited); connect(cylinders, &CylindersModel::rowsRemoved, plannerModel, &DivePlannerPointsModel::cylinderModelEdited); - ui.tableWidget->setBtnToolTip(tr("Add dive data point")); + ui.waterType->setItemData(0, FRESHWATER_SALINITY); + ui.waterType->setItemData(1, SEAWATER_SALINITY); + ui.waterType->setItemData(2, EN13319_SALINITY); + waterTypeUpdateTexts(); + connect(ui.startTime, &QDateEdit::timeChanged, plannerModel, &DivePlannerPointsModel::setStartTime); connect(ui.dateEdit, &QDateEdit::dateChanged, plannerModel, &DivePlannerPointsModel::setStartDate); + connect(ui.divemode, QOverload::of(&QComboBox::currentIndexChanged), parent, &PlannerWidgets::setDiveMode); connect(ui.ATMPressure, QOverload::of(&QSpinBox::valueChanged), this, &DivePlannerWidget::atmPressureChanged); connect(ui.atmHeight, QOverload::of(&QSpinBox::valueChanged), this, &DivePlannerWidget::heightChanged); connect(ui.waterType, QOverload::of(&QComboBox::currentIndexChanged), this, &DivePlannerWidget::waterTypeChanged); @@ -77,14 +86,14 @@ DivePlannerWidget::DivePlannerWidget(dive &planned_dive, int dcNr, PlannerWidget QShortcut *closeKey = new QShortcut(QKeySequence(Qt::Key_Escape), this); connect(closeKey, &QShortcut::activated, plannerModel, &DivePlannerPointsModel::cancelPlan); - // This makes shure the spinbox gets a setMinimum(0) on it so we can't have negative time or depth. + // This makes sure the spinbox gets a setMinimum(0) on it so we can't have negative time or depth. // Limit segments to a depth of 1000 m/3300 ft and a duration of 100 h. Setting the limit for // the depth will be done in settingChanged() since this depends on the chosen units. ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::RUNTIME, new SpinBoxDelegate(0, INT_MAX, 1, this)); ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::DURATION, new SpinBoxDelegate(0, 6000, 1, this)); ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::CCSETPOINT, new DoubleSpinBoxDelegate(0, 2, 0.01, this)); - connect(&diveListNotifier, &DiveListNotifier::settingsChanged, this, &DivePlannerWidget::settingsChanged); + connect(&diveListNotifier, &DiveListNotifier::settingsChanged, parent, &PlannerWidgets::settingsChanged); /* set defaults. */ ui.ATMPressure->setValue(1013); @@ -116,11 +125,6 @@ void DivePlannerWidget::setSurfacePressure(int surface_pressure) ui.ATMPressure->setValue(surface_pressure); } -void PlannerSettingsWidget::setDiveMode(int mode) -{ - ui.rebreathermode->setCurrentIndex(mode); -} - void DivePlannerWidget::setSalinity(int salinity) { bool mapped = false; @@ -159,26 +163,27 @@ void DivePlannerWidget::settingsChanged() } ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::DEPTH, new SpinBoxDelegate(0, maxDepth, 1, this)); ui.atmHeight->blockSignals(true); - ui.atmHeight->setValue((int) get_depth_units((int) pressure_to_altitude(DivePlannerPointsModel::instance()->getSurfacePressure()), NULL,NULL)); + ui.atmHeight->setValue((int) get_depth_units((int) pressure_to_altitude(DivePlannerPointsModel::instance()->getSurfacePressure()), NULL, NULL)); ui.atmHeight->blockSignals(false); - ui.dateEdit->setDisplayFormat(prefs.date_format); - ui.startTime->setDisplayFormat(prefs.time_format); + ui.dateEdit->setDisplayFormat(QString::fromStdString(prefs.date_format)); + ui.startTime->setDisplayFormat(QString::fromStdString(prefs.time_format)); } -void DivePlannerWidget::atmPressureChanged(const int pressure) +void DivePlannerWidget::atmPressureChanged(int pressure_in_mbar) { + pressure_t pressure { .mbar = pressure_in_mbar }; DivePlannerPointsModel::instance()->setSurfacePressure(pressure); ui.atmHeight->blockSignals(true); - ui.atmHeight->setValue((int) get_depth_units((int) pressure_to_altitude(pressure), NULL,NULL)); + ui.atmHeight->setValue((int) get_depth_units((int) pressure_to_altitude(pressure), NULL, NULL)); ui.atmHeight->blockSignals(false); } void DivePlannerWidget::heightChanged(const int height) { // height is in ft or in meters - int pressure = (int) (altitude_to_pressure(units_to_depth((double) height).mm)); + pressure_t pressure = altitude_to_pressure(units_to_depth((double) height).mm); ui.ATMPressure->blockSignals(true); - ui.ATMPressure->setValue(pressure); + ui.ATMPressure->setValue(pressure.mbar); ui.ATMPressure->blockSignals(false); DivePlannerPointsModel::instance()->setSurfacePressure(pressure); } @@ -212,7 +217,23 @@ void DivePlannerWidget::customSalinityChanged(double density) } } -void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreathermode) +void DivePlannerWidget::setDiveMode(int mode) +{ + ui.divemode->setCurrentIndex(mode); +} + +void DivePlannerWidget::setColumnVisibility(int mode) +{ + ui.tableWidget->view()->setColumnHidden(DivePlannerPointsModel::CCSETPOINT, mode != CCR); + ui.tableWidget->view()->setColumnHidden(DivePlannerPointsModel::DIVEMODE, mode == OC || (mode == CCR && !prefs.allowOcGasAsDiluent)); + + // This is needed as Qt sets the column width to 0 when hiding a column + ui.tableWidget->view()->setVisible(false); // This will cause the resize to include rows outside the current viewport + ui.tableWidget->view()->resizeColumnsToContents(); + ui.tableWidget->view()->setVisible(true); +} + +void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t divemode) { if (mode == RECREATIONAL) { ui.label_gflow->setDisabled(false); @@ -265,7 +286,7 @@ void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreatherm ui.backgasBreaks->setChecked(false); ui.backgasBreaks->blockSignals(false); } - ui.bailout->setDisabled(!(rebreathermode == CCR || rebreathermode == PSCR)); + ui.bailout->setDisabled(!IS_REBREATHER_MODE(divemode)); ui.bottompo2->setDisabled(false); ui.decopo2->setDisabled(false); ui.safetystop->setDisabled(true); @@ -277,7 +298,7 @@ void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreatherm ui.min_switch_duration->setDisabled(false); ui.surface_segment->setDisabled(false); ui.label_min_switch_duration->setDisabled(false); - ui.sacfactor->setDisabled(false); + ui.sacfactor->setDisabled(IS_REBREATHER_MODE(divemode)); ui.problemsolvingtime->setDisabled(false); ui.sacfactor->setValue(PlannerShared::sacfactor()); ui.problemsolvingtime->setValue(prefs.problemsolvingtime); @@ -299,7 +320,7 @@ void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreatherm ui.backgasBreaks->setChecked(false); ui.backgasBreaks->blockSignals(false); } - ui.bailout->setDisabled(!(rebreathermode == CCR || rebreathermode == PSCR)); + ui.bailout->setDisabled(!IS_REBREATHER_MODE(divemode)); ui.bottompo2->setDisabled(false); ui.decopo2->setDisabled(false); ui.safetystop->setDisabled(true); @@ -311,7 +332,7 @@ void PlannerSettingsWidget::disableDecoElements(int mode, divemode_t rebreatherm ui.min_switch_duration->setDisabled(false); ui.surface_segment->setDisabled(false); ui.label_min_switch_duration->setDisabled(false); - ui.sacfactor->setDisabled(false); + ui.sacfactor->setDisabled(IS_REBREATHER_MODE(divemode)); ui.problemsolvingtime->setDisabled(false); ui.sacfactor->setValue(PlannerShared::sacfactor()); ui.problemsolvingtime->setValue(prefs.problemsolvingtime); @@ -359,7 +380,6 @@ PlannerSettingsWidget::PlannerSettingsWidget(PlannerWidgets *parent) ui.decopo2->setValue(PlannerShared::decopo2()); ui.backgasBreaks->setChecked(prefs.doo2breaks); PlannerShared::set_dobailout(false); - setBailoutVisibility(false); ui.o2narcotic->setChecked(prefs.o2narcotic); ui.drop_stone_mode->setChecked(prefs.drop_stone_mode); ui.switch_at_req_stop->setChecked(prefs.switch_at_req_stop); @@ -370,12 +390,6 @@ PlannerSettingsWidget::PlannerSettingsWidget(PlannerWidgets *parent) ui.vpmb_deco->setChecked(prefs.planner_deco_mode == VPMB); disableDecoElements((int) prefs.planner_deco_mode, OC); - // should be the same order as in dive_comp_type! - QStringList rebreather_modes = QStringList(); - for (int i = 0; i < FREEDIVE; i++) - rebreather_modes.append(gettextFromC::tr(divemode_text_ui[i])); - ui.rebreathermode->insertItems(0, rebreather_modes); - connect(ui.recreational_deco, &QAbstractButton::clicked, [] { PlannerShared::set_planner_deco_mode(RECREATIONAL); }); connect(ui.buehlmann_deco, &QAbstractButton::clicked, [] { PlannerShared::set_planner_deco_mode(BUEHLMANN); }); connect(ui.vpmb_deco, &QAbstractButton::clicked, [] { PlannerShared::set_planner_deco_mode(VPMB); }); @@ -404,12 +418,10 @@ PlannerSettingsWidget::PlannerSettingsWidget(PlannerWidgets *parent) connect(ui.switch_at_req_stop, &QAbstractButton::toggled, plannerModel, &DivePlannerPointsModel::setSwitchAtReqStop); connect(ui.min_switch_duration, QOverload::of(&QSpinBox::valueChanged), &PlannerShared::set_min_switch_duration); connect(ui.surface_segment, QOverload::of(&QSpinBox::valueChanged), &PlannerShared::set_surface_segment); - connect(ui.rebreathermode, QOverload::of(&QComboBox::currentIndexChanged), plannerModel, &DivePlannerPointsModel::setRebreatherMode); - connect(ui.rebreathermode, QOverload::of(&QComboBox::currentIndexChanged), this, &PlannerSettingsWidget::setBailoutVisibility); - connect(ui.recreational_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(RECREATIONAL, parent->getRebreatherMode()); }); - connect(ui.buehlmann_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(BUEHLMANN, parent->getRebreatherMode()); }); - connect(ui.vpmb_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(VPMB, parent->getRebreatherMode()); }); + connect(ui.recreational_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(RECREATIONAL, parent->getDiveMode()); }); + connect(ui.buehlmann_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(BUEHLMANN, parent->getDiveMode()); }); + connect(ui.vpmb_deco, &QAbstractButton::clicked, [this, parent] { disableDecoElements(VPMB, parent->getDiveMode()); }); connect(ui.sacfactor, QOverload::of(&QDoubleSpinBox::valueChanged), &PlannerShared::set_sacfactor); connect(ui.problemsolvingtime, QOverload::of(&QSpinBox::valueChanged), plannerModel, &DivePlannerPointsModel::setProblemSolvingTime); @@ -519,8 +531,9 @@ void PlannerSettingsWidget::setBackgasBreaks(bool dobreaks) void PlannerSettingsWidget::setBailoutVisibility(int mode) { - ui.bailout->setDisabled(!(mode == CCR || mode == PSCR)); - ui.sacFactor->setDisabled(mode == CCR); + bool isRebreatherMode = IS_REBREATHER_MODE(mode); + ui.bailout->setDisabled(!isRebreatherMode); + ui.sacfactor->setDisabled(isRebreatherMode); } PlannerDetails::PlannerDetails(QWidget *parent) : QWidget(parent) @@ -537,7 +550,7 @@ void PlannerDetails::setPlanNotes(QString plan) } PlannerWidgets::PlannerWidgets() : - planned_dive(alloc_dive()), + planned_dive(std::make_unique()), dcNr(0), plannerWidget(*planned_dive, dcNr, this), plannerSettingsWidget(this) @@ -561,9 +574,9 @@ int PlannerWidgets::getDcNr() return dcNr; } -divemode_t PlannerWidgets::getRebreatherMode() const +divemode_t PlannerWidgets::getDiveMode() const { - return get_dive_dc_const(planned_dive.get(), dcNr)->divemode; + return planned_dive->get_dc(dcNr)->divemode; } void PlannerWidgets::preparePlanDive(const dive *currentDive, int currentDcNr) @@ -575,13 +588,14 @@ void PlannerWidgets::preparePlanDive(const dive *currentDive, int currentDcNr) // plan the dive in the same mode as the currently selected one if (currentDive) { - plannerSettingsWidget.setDiveMode(get_dive_dc_const(currentDive, currentDcNr)->divemode); - plannerSettingsWidget.setBailoutVisibility(get_dive_dc_const(currentDive, currentDcNr)->divemode); + planned_dive->get_dc(dcNr)->divemode = currentDive->get_dc(currentDcNr)->divemode; if (currentDive->salinity) plannerWidget.setSalinity(currentDive->salinity); else // No salinity means salt water plannerWidget.setSalinity(SEAWATER_SALINITY); } + + plannerWidget.setDiveMode(getDiveMode()); } void PlannerWidgets::planDive() @@ -589,6 +603,7 @@ void PlannerWidgets::planDive() DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); plannerWidget.setReplanButton(false); + plannerWidget.setupStartTime(timestampToDateTime(planned_dive->when)); // This will reload the profile! } @@ -596,6 +611,8 @@ void PlannerWidgets::prepareReplanDive(const dive *currentDive, int currentDcNr) { copy_dive(currentDive, planned_dive.get()); dcNr = currentDcNr; + + plannerWidget.setDiveMode(getDiveMode()); } void PlannerWidgets::replanDive() @@ -609,6 +626,7 @@ void PlannerWidgets::replanDive() plannerWidget.setSurfacePressure(planned_dive->surface_pressure.mbar); if (planned_dive->salinity) plannerWidget.setSalinity(planned_dive->salinity); + reset_cylinders(planned_dive.get(), true); DivePlannerPointsModel::instance()->cylindersModel()->updateDive(planned_dive.get(), dcNr); } @@ -657,3 +675,18 @@ void PlannerWidgets::printDecoPlan() plannerDetails.divePlanOutput()->setHtml(origPlan); // restore original plan #endif } + +void PlannerWidgets::setDiveMode(int mode) +{ + planned_dive->get_dc(dcNr)->divemode = (divemode_t)mode; + DivePlannerPointsModel::instance()->cylindersChanged(); + + plannerWidget.setColumnVisibility(mode); + plannerSettingsWidget.setBailoutVisibility(mode); +} + +void PlannerWidgets::settingsChanged() +{ + plannerWidget.settingsChanged(); + setDiveMode(getDiveMode()); +} diff --git a/desktop-widgets/diveplanner.h b/desktop-widgets/diveplanner.h index 0c1cfd1c5..a63d867ba 100644 --- a/desktop-widgets/diveplanner.h +++ b/desktop-widgets/diveplanner.h @@ -3,8 +3,8 @@ #define DIVEPLANNER_H #include "core/divemode.h" -#include "core/owning_ptrs.h" +#include #include #include #include @@ -18,9 +18,11 @@ struct dive; class DivePlannerWidget : public QWidget { Q_OBJECT public: - explicit DivePlannerWidget(dive &planned_dive, int dcNr, PlannerWidgets *parent); + explicit DivePlannerWidget(const dive &planned_dive, int &dcNr, PlannerWidgets *parent); ~DivePlannerWidget(); void setReplanButton(bool replan); + void setColumnVisibility(int mode); + void setDiveMode(int mode); public slots: void setupStartTime(QDateTime startTime); @@ -48,9 +50,8 @@ public slots: void settingsChanged(); void setBackgasBreaks(bool dobreaks); - void disableDecoElements(int mode, divemode_t rebreathermode); + void disableDecoElements(int mode, divemode_t divemode); void disableBackgasBreaks(bool enabled); - void setDiveMode(int mode); void setBailoutVisibility(int mode); private: @@ -86,12 +87,14 @@ public: void replanDive(); struct dive *getDive() const; int getDcNr(); - divemode_t getRebreatherMode() const; + divemode_t getDiveMode() const; + void settingsChanged(); public slots: void printDecoPlan(); + void setDiveMode(int mode); private: - OwningDivePtr planned_dive; + std::unique_ptr planned_dive; int dcNr; public: DivePlannerWidget plannerWidget; diff --git a/desktop-widgets/diveplanner.ui b/desktop-widgets/diveplanner.ui index 74e7b3bb4..89c29c1e5 100644 --- a/desktop-widgets/diveplanner.ui +++ b/desktop-widgets/diveplanner.ui @@ -62,7 +62,7 @@ 0 - + @@ -98,7 +98,30 @@ - + + + + + 0 + 0 + + + + Dive mode + + + + + + + + + + 6 + + + + @@ -112,20 +135,20 @@ - - - Altitude - - - - ATM pressure - + + + + Altitude + + + + Water type @@ -133,6 +156,25 @@ + + + + 0 + 0 + + + + mbar + + + 689 + + + 1100 + + + + @@ -154,26 +196,7 @@ - - - - - 0 - 0 - - - - mbar - - - 689 - - - 1100 - - - - + @@ -197,7 +220,7 @@ - + false @@ -237,7 +260,7 @@ - + @@ -253,7 +276,7 @@ - + @@ -285,6 +308,7 @@ startTime + divemode buttonBox scrollArea diff --git a/desktop-widgets/divesiteimportdialog.cpp b/desktop-widgets/divesiteimportdialog.cpp index 66f003253..63163279f 100644 --- a/desktop-widgets/divesiteimportdialog.cpp +++ b/desktop-widgets/divesiteimportdialog.cpp @@ -10,20 +10,18 @@ #include -// Caller keeps ownership of "imported". The contents of "imported" will be consumed on execution of the dialog. -// On return, it will be empty. -DivesiteImportDialog::DivesiteImportDialog(struct dive_site_table &imported, QString source, QWidget *parent) : QDialog(parent), - importedSource(std::move(source)) +DivesiteImportDialog::DivesiteImportDialog(dive_site_table imported, QString source, QWidget *parent) : QDialog(parent), + importedSites(std::move(imported)), + importedSource(std::move(source)), + divesiteImportedModel(std::make_unique(importedSites)) { QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_W), this); QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this); - divesiteImportedModel = new DivesiteImportedModel(this); - int startingWidth = defaultModelFont().pointSize(); ui.setupUi(this); - ui.importedDivesitesView->setModel(divesiteImportedModel); + ui.importedDivesitesView->setModel(divesiteImportedModel.get()); ui.importedDivesitesView->setSelectionBehavior(QAbstractItemView::SelectRows); ui.importedDivesitesView->setSelectionMode(QAbstractItemView::SingleSelection); ui.importedDivesitesView->horizontalHeader()->setStretchLastSection(true); @@ -35,45 +33,34 @@ DivesiteImportDialog::DivesiteImportDialog(struct dive_site_table &imported, QSt ui.selectAllButton->setEnabled(true); ui.unselectAllButton->setEnabled(true); - connect(ui.importedDivesitesView, &QTableView::clicked, divesiteImportedModel, &DivesiteImportedModel::changeSelected); - connect(ui.selectAllButton, &QPushButton::clicked, divesiteImportedModel, &DivesiteImportedModel::selectAll); - connect(ui.unselectAllButton, &QPushButton::clicked, divesiteImportedModel, &DivesiteImportedModel::selectNone); + connect(ui.importedDivesitesView, &QTableView::clicked, divesiteImportedModel.get(), &DivesiteImportedModel::changeSelected); + connect(ui.selectAllButton, &QPushButton::clicked, divesiteImportedModel.get(), &DivesiteImportedModel::selectAll); + connect(ui.unselectAllButton, &QPushButton::clicked, divesiteImportedModel.get(), &DivesiteImportedModel::selectNone); connect(close, SIGNAL(activated()), this, SLOT(close())); connect(quit, SIGNAL(activated()), parent, SLOT(close())); ui.ok->setEnabled(true); - - importedSites = imported; - imported.nr = imported.allocated = 0; - imported.dive_sites = nullptr; - - divesiteImportedModel->repopulate(&importedSites); } DivesiteImportDialog::~DivesiteImportDialog() { - clear_dive_site_table(&importedSites); } void DivesiteImportDialog::on_cancel_clicked() { - clear_dive_site_table(&importedSites); done(-1); } void DivesiteImportDialog::on_ok_clicked() { // delete non-selected dive sites - struct dive_site_table selectedSites = empty_dive_site_table; - for (int i = 0; i < importedSites.nr; i++) - if (divesiteImportedModel->data(divesiteImportedModel->index(i, 0), Qt::CheckStateRole) == Qt::Checked) { - struct dive_site *newSite = alloc_dive_site(); - copy_dive_site(importedSites.dive_sites[i], newSite); - add_dive_site_to_table(newSite, &selectedSites); - } + dive_site_table selectedSites; + for (size_t i = 0; i < importedSites.size(); i++) { + if (divesiteImportedModel->data(divesiteImportedModel->index(i, 0), Qt::CheckStateRole) == Qt::Checked) + selectedSites.push_back(std::move(importedSites[i])); + } + importedSites.clear(); // Hopefully, the model is not used thereafter! - Command::importDiveSites(&selectedSites, importedSource); - clear_dive_site_table(&selectedSites); - clear_dive_site_table(&importedSites); + Command::importDiveSites(std::move(selectedSites), importedSource); accept(); } diff --git a/desktop-widgets/divesiteimportdialog.h b/desktop-widgets/divesiteimportdialog.h index b137234e0..5af296830 100644 --- a/desktop-widgets/divesiteimportdialog.h +++ b/desktop-widgets/divesiteimportdialog.h @@ -21,7 +21,8 @@ class DivesiteImportedModel; class DivesiteImportDialog : public QDialog { Q_OBJECT public: - DivesiteImportDialog(struct dive_site_table &imported, QString source, QWidget *parent = 0 ); + // Note: takes ownership of importedd table + DivesiteImportDialog(dive_site_table imported, QString source, QWidget *parent = 0); ~DivesiteImportDialog(); public @@ -31,10 +32,10 @@ slots: private: Ui::DivesiteImportDialog ui; - struct dive_site_table importedSites; + dive_site_table importedSites; QString importedSource; - DivesiteImportedModel *divesiteImportedModel; + std::unique_ptr divesiteImportedModel; }; #endif // DIVESITEIMPORTDIALOG_H diff --git a/desktop-widgets/divesitelistview.cpp b/desktop-widgets/divesitelistview.cpp index 65e8c04c9..a15c01aaa 100644 --- a/desktop-widgets/divesitelistview.cpp +++ b/desktop-widgets/divesitelistview.cpp @@ -62,9 +62,9 @@ void DiveSiteListView::diveSiteClicked(const QModelIndex &index) MainWindow::instance()->editDiveSite(ds); break; case LocationInformationModel::REMOVE: - if (ds->dives.nr > 0 && + if (!ds->dives.empty() && QMessageBox::warning(this, tr("Delete dive site?"), - tr("This dive site has %n dive(s). Do you really want to delete it?\n", "", ds->dives.nr), + tr("This dive site has %n dive(s). Do you really want to delete it?\n", "", ds->dives.size()), QMessageBox::Yes|QMessageBox::No) == QMessageBox::No) return; Command::deleteDiveSites(QVector{ds}); @@ -97,10 +97,10 @@ void DiveSiteListView::diveSiteAdded(struct dive_site *, int idx) void DiveSiteListView::diveSiteChanged(struct dive_site *ds, int field) { - int idx = get_divesite_idx(ds, divelog.sites); - if (idx < 0) + size_t idx = divelog.sites.get_idx(ds); + if (idx == std::string::npos) return; - QModelIndex globalIdx = LocationInformationModel::instance()->index(idx, field); + QModelIndex globalIdx = LocationInformationModel::instance()->index(static_cast(idx), field); QModelIndex localIdx = model->mapFromSource(globalIdx); ui.diveSites->view()->scrollTo(localIdx); } @@ -115,14 +115,14 @@ void DiveSiteListView::on_filterText_textChanged(const QString &text) model->setFilter(text); } -QVector DiveSiteListView::selectedDiveSites() +std::vector DiveSiteListView::selectedDiveSites() { const QModelIndexList indices = ui.diveSites->view()->selectionModel()->selectedRows(); - QVector sites; + std::vector sites; sites.reserve(indices.size()); for (const QModelIndex &idx: indices) { struct dive_site *ds = model->getDiveSite(idx); - sites.append(ds); + sites.push_back(ds); } return sites; } diff --git a/desktop-widgets/divesitelistview.h b/desktop-widgets/divesitelistview.h index 3457ac5e3..eeea2a71f 100644 --- a/desktop-widgets/divesitelistview.h +++ b/desktop-widgets/divesitelistview.h @@ -22,7 +22,7 @@ private slots: private: Ui::DiveSiteListView ui; DiveSiteSortedModel *model; - QVector selectedDiveSites(); + std::vector selectedDiveSites(); void hideEvent(QHideEvent *) override; void showEvent(QShowEvent *) override; }; diff --git a/desktop-widgets/downloadfromdivecomputer.cpp b/desktop-widgets/downloadfromdivecomputer.cpp index 28d231b72..6ac7d9b9e 100644 --- a/desktop-widgets/downloadfromdivecomputer.cpp +++ b/desktop-widgets/downloadfromdivecomputer.cpp @@ -9,7 +9,6 @@ #include "core/settings/qPrefDiveComputer.h" #include "core/subsurface-float.h" #include "core/subsurface-string.h" -#include "core/uemis.h" #include "core/downloadfromdcthread.h" #include "desktop-widgets/divelistview.h" #include "desktop-widgets/mainwindow.h" @@ -64,7 +63,7 @@ DownloadFromDCWidget::DownloadFromDCWidget(const QString &filename, QWidget *par ui.product->setModel(&productModel); ui.syncDiveComputerTime->setChecked(prefs.sync_dc_time); - progress_bar_text = ""; + progress_bar_text.clear(); timer->setInterval(200); @@ -213,21 +212,19 @@ DELETEDCBUTTON(4) void DownloadFromDCWidget::updateProgressBar() { - static char *last_text = NULL; - - if (empty_string(last_text)) { + if (last_text.empty()) { // if we get the first actual text after the download is finished // (which happens for example on the OSTC), then don't bother - if (!empty_string(progress_bar_text) && nearly_equal(progress_bar_fraction, 1.0)) - progress_bar_text = ""; + if (!progress_bar_text.empty() && nearly_equal(progress_bar_fraction, 1.0)) + progress_bar_text.clear(); } - if (!empty_string(progress_bar_text)) { + if (!progress_bar_text.empty()) { // once the progress bar text is set, setup the maximum so the user sees actual progress - ui.progressBar->setFormat(progress_bar_text); + ui.progressBar->setFormat(QString::fromStdString(progress_bar_text)); ui.progressBar->setMaximum(100); #if defined(Q_OS_MAC) // on mac the progress bar doesn't show its text - ui.progressText->setText(progress_bar_text); + ui.progressText->setText(QString::fromStdString(progress_bar_text)); #endif } else { if (nearly_0(progress_bar_fraction)) { @@ -249,8 +246,7 @@ void DownloadFromDCWidget::updateProgressBar() } } ui.progressBar->setValue(lrint(progress_bar_fraction * 100)); - free(last_text); - last_text = strdup(progress_bar_text); + last_text = progress_bar_text; } void DownloadFromDCWidget::updateState(states state) @@ -263,10 +259,10 @@ void DownloadFromDCWidget::updateState(states state) ui.progressBar->hide(); markChildrenAsEnabled(); timer->stop(); - progress_bar_text = ""; + progress_bar_text.clear(); #if defined(Q_OS_MAC) // on mac we show the text in a label - ui.progressText->setText(progress_bar_text); + ui.progressText->setText(QString::fromStdString(progress_bar_text)); #endif } @@ -289,10 +285,10 @@ void DownloadFromDCWidget::updateState(states state) ui.progressBar->setValue(0); ui.progressBar->hide(); markChildrenAsEnabled(); - progress_bar_text = ""; + progress_bar_text.clear(); #if defined(Q_OS_MAC) // on mac we show the text in a label - ui.progressText->setText(progress_bar_text); + ui.progressText->setText(QString::fromStdString(progress_bar_text)); #endif } @@ -301,19 +297,19 @@ void DownloadFromDCWidget::updateState(states state) // If we find an error, offer to retry, otherwise continue the interaction to pick the dives the user wants else if (currentState == DOWNLOADING && state == DONE) { timer->stop(); - if (QString(progress_bar_text).contains("error", Qt::CaseInsensitive)) { + if (QString::fromStdString(progress_bar_text).contains("error", Qt::CaseInsensitive)) { updateProgressBar(); markChildrenAsEnabled(); - progress_bar_text = ""; + progress_bar_text.clear(); } else { if (diveImportedModel->numDives() != 0) - progress_bar_text = ""; + progress_bar_text.clear(); ui.progressBar->setValue(100); markChildrenAsEnabled(); } #if defined(Q_OS_MAC) // on mac we show the text in a label - ui.progressText->setText(progress_bar_text); + ui.progressText->setText(QString::fromStdString(progress_bar_text)); #endif } @@ -331,13 +327,14 @@ void DownloadFromDCWidget::updateState(states state) else if (state == ERRORED) { timer->stop(); - QMessageBox::critical(this, TITLE_OR_TEXT(tr("Error"), diveImportedModel->thread.error), QMessageBox::Ok); + QMessageBox::critical(this, TITLE_OR_TEXT(tr("Error"), + QString::fromStdString(diveImportedModel->thread.error)), QMessageBox::Ok); markChildrenAsEnabled(); - progress_bar_text = ""; + progress_bar_text.clear(); ui.progressBar->hide(); #if defined(Q_OS_MAC) // on mac we show the text in a label - ui.progressText->setText(progress_bar_text); + ui.progressText->setText(QString::fromStdString(progress_bar_text)); #endif } @@ -425,8 +422,7 @@ void DownloadFromDCWidget::on_downloadCancelRetryButton_clicked() data->setDevName(btDeviceSelectionDialog->getSelectedDeviceAddress()); data->setDevBluetoothName(btDeviceSelectionDialog->getSelectedDeviceName()); } else { - QString name, address; - address = extractBluetoothNameAddress(ui.device->currentText(), name); + auto [address, name] = extractBluetoothNameAddress(ui.device->currentText()); data->setDevName(address); data->setDevBluetoothName(name); } @@ -444,12 +440,11 @@ void DownloadFromDCWidget::on_downloadCancelRetryButton_clicked() // this breaks an "else if" across lines... not happy... #endif if (data->vendor() == "Uemis") { - char *colon; - char *devname = copy_qstring(ui.device->currentText()); - - if ((colon = strstr(devname, ":\\ (UEMISSDA)")) != NULL) { - *(colon + 2) = '\0'; - report_info("shortened devname to \"%s\"", devname); + QString devname = ui.device->currentText(); + auto colon = devname.indexOf(":\\ (UEMISSDA)"); + if (colon >= 0) { + devname.resize(colon + 2); + report_info("shortened devname to \"%s\"", qPrintable(devname)); } data->setDevName(devname); } else { @@ -466,7 +461,7 @@ void DownloadFromDCWidget::on_downloadCancelRetryButton_clicked() qPrefDiveComputer::set_device(data->devName()); // before we start, remember where the dive_table ended - previousLast = divelog.dives->nr; + previousLast = static_cast(divelog.dives.size()); diveImportedModel->startDownload(); // FIXME: We should get the _actual_ device info instead of whatever @@ -544,7 +539,7 @@ void DownloadFromDCWidget::onDownloadThreadFinished() showRememberedDCs(); if (currentState == DOWNLOADING) { - if (diveImportedModel->thread.error.isEmpty()) + if (diveImportedModel->thread.error.empty()) updateState(DONE); else updateState(ERRORED); @@ -570,11 +565,11 @@ void DownloadFromDCWidget::on_ok_clicked() if (currentState != DONE && currentState != ERRORED) return; - int flags = IMPORT_IS_DOWNLOADED; + int flags = import_flags::is_downloaded; if (preferDownloaded()) - flags |= IMPORT_PREFER_IMPORTED; + flags |= import_flags::prefer_imported; if (ui.createNewTrip->isChecked()) - flags |= IMPORT_ADD_TO_NEW_TRIP; + flags |= import_flags::add_to_new_trip; diveImportedModel->recordDives(flags); diff --git a/desktop-widgets/downloadfromdivecomputer.h b/desktop-widgets/downloadfromdivecomputer.h index b85a6334f..a6a0a321f 100644 --- a/desktop-widgets/downloadfromdivecomputer.h +++ b/desktop-widgets/downloadfromdivecomputer.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "core/libdivecomputer.h" #include "desktop-widgets/configuredivecomputerdialog.h" @@ -77,6 +78,7 @@ private: QStringListModel productModel; Ui::DownloadFromDiveComputer ui; QString filename; + std::string last_text; bool downloading; int previousLast; diff --git a/desktop-widgets/filterconstraintwidget.cpp b/desktop-widgets/filterconstraintwidget.cpp index 508c65b77..8cc7016d2 100644 --- a/desktop-widgets/filterconstraintwidget.cpp +++ b/desktop-widgets/filterconstraintwidget.cpp @@ -283,13 +283,13 @@ void FilterConstraintWidget::update() { // The user might have changed the date and/or time format. Let's update the widgets. if (dateFrom) - dateFrom->setDisplayFormat(prefs.date_format); + dateFrom->setDisplayFormat(QString::fromStdString(prefs.date_format)); if (dateTo) - dateTo->setDisplayFormat(prefs.date_format); + dateTo->setDisplayFormat(QString::fromStdString(prefs.date_format)); if (timeFrom) - timeFrom->setDisplayFormat(prefs.time_format); + timeFrom->setDisplayFormat(QString::fromStdString(prefs.time_format)); if (timeTo) - timeTo->setDisplayFormat(prefs.time_format); + timeTo->setDisplayFormat(QString::fromStdString(prefs.time_format)); QModelIndex idx = model->index(row, 0); setIndex(negate.get(), idx, FilterConstraintModel::NEGATE_INDEX_ROLE); diff --git a/desktop-widgets/filterwidget.cpp b/desktop-widgets/filterwidget.cpp index b4d3188cf..f6ae3c55f 100644 --- a/desktop-widgets/filterwidget.cpp +++ b/desktop-widgets/filterwidget.cpp @@ -97,7 +97,7 @@ void FilterWidget::selectPreset(int i) void FilterWidget::loadPreset(int index) { ignoreSignal = true; // When reloading the filter UI, we get numerous constraintChanged signals. Ignore them. - FilterData filter = filter_preset_get(index); + FilterData filter = divelog.filter_presets[index].data; setFilterData(filter); ignoreSignal = false; presetModified = false; @@ -227,7 +227,8 @@ void FilterWidget::updatePresetLabel() int presetId = selectedPreset(); QString text; if (presetId >= 0) { - text = QString(filter_preset_name(presetId).c_str()); + const std::string &name = divelog.filter_presets[presetId].name; + text = QString::fromStdString(name); if (presetModified) text += " (" + tr("modified") + ")"; } @@ -240,13 +241,15 @@ void FilterWidget::on_addSetButton_clicked() // Thus, if the user selects an item and modify the filter, // they can simply overwrite the preset. int presetId = selectedPreset(); - QString selectedPreset = presetId >= 0 ? QString(filter_preset_name(presetId).c_str()) : QString(); + QString selectedPreset = presetId >= 0 ? + QString::fromStdString(divelog.filter_presets[presetId].name) : + QString(); AddFilterPresetDialog dialog(selectedPreset, this); QString name = dialog.doit(); if (name.isEmpty()) return; - int idx = filter_preset_id(name.toStdString()); + int idx = divelog.filter_presets.preset_id(name.toStdString()); if (idx >= 0) Command::editFilterPreset(idx, createFilterData()); else diff --git a/desktop-widgets/findmovedimagesdialog.cpp b/desktop-widgets/findmovedimagesdialog.cpp index a68291b14..3f432d922 100644 --- a/desktop-widgets/findmovedimagesdialog.cpp +++ b/desktop-widgets/findmovedimagesdialog.cpp @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include "findmovedimagesdialog.h" +#include "core/divelog.h" +#include "core/divelist.h" #include "core/picture.h" #include "core/qthelper.h" #include "desktop-widgets/divelistview.h" // TODO: used for lastUsedImageDir() @@ -184,12 +186,12 @@ void FindMovedImagesDialog::on_scanButton_clicked() // We have to collect the names of the image filenames in the main thread bool onlySelected = ui.onlySelectedDives->isChecked(); QVector imagePaths; - int i; - struct dive *dive; - for_each_dive (i, dive) - if (!onlySelected || dive->selected) - FOR_EACH_PICTURE(dive) - imagePaths.append(QString(picture->filename)); + for (auto &dive: divelog.dives) { + if (!onlySelected || dive->selected) { + for (auto &picture: dive->pictures) + imagePaths.append(QString::fromStdString(picture.filename)); + } + } stopScanning = 0; QFuture> future = QtConcurrent::run( // Note that we capture everything but "this" by copy to avoid dangling references. diff --git a/desktop-widgets/locationinformation.cpp b/desktop-widgets/locationinformation.cpp index d2ece3f17..36cc679fb 100644 --- a/desktop-widgets/locationinformation.cpp +++ b/desktop-widgets/locationinformation.cpp @@ -129,21 +129,21 @@ void LocationInformationWidget::updateLabels() clearLabels(); return; } - if (diveSite->name) - ui.diveSiteName->setText(diveSite->name); + if (!diveSite->name.empty()) + ui.diveSiteName->setText(QString::fromStdString(diveSite->name)); else ui.diveSiteName->clear(); - const char *country = taxonomy_get_country(&diveSite->taxonomy); - if (country) - ui.diveSiteCountry->setText(country); + std::string country = taxonomy_get_country(diveSite->taxonomy); + if (!country.empty()) + ui.diveSiteCountry->setText(QString::fromStdString(country)); else ui.diveSiteCountry->clear(); - if (diveSite->description) - ui.diveSiteDescription->setText(diveSite->description); + if (!diveSite->description.empty()) + ui.diveSiteDescription->setText(QString::fromStdString(diveSite->description)); else ui.diveSiteDescription->clear(); - if (diveSite->notes) - ui.diveSiteNotes->setPlainText(diveSite->notes); + if (!diveSite->notes.empty()) + ui.diveSiteNotes->setPlainText(QString::fromStdString(diveSite->notes)); else ui.diveSiteNotes->clear(); if (has_location(&diveSite->location)) @@ -152,7 +152,7 @@ void LocationInformationWidget::updateLabels() ui.diveSiteCoordinates->clear(); coordinatesSetWarning(false); - ui.locationTags->setText(constructLocationTags(&diveSite->taxonomy, false)); + ui.locationTags->setText(QString::fromStdString(taxonomy_get_location_tags(diveSite->taxonomy, false))); } void LocationInformationWidget::unitsChanged() @@ -172,17 +172,17 @@ void LocationInformationWidget::diveSiteChanged(struct dive_site *ds, int field) return; // A different dive site was changed -> do nothing. switch (field) { case LocationInformationModel::NAME: - ui.diveSiteName->setText(diveSite->name); + ui.diveSiteName->setText(QString::fromStdString(diveSite->name)); return; case LocationInformationModel::DESCRIPTION: - ui.diveSiteDescription->setText(diveSite->description); + ui.diveSiteDescription->setText(QString::fromStdString(diveSite->description)); return; case LocationInformationModel::NOTES: - ui.diveSiteNotes->setText(diveSite->notes); + ui.diveSiteNotes->setText(QString::fromStdString(diveSite->notes)); return; case LocationInformationModel::TAXONOMY: - ui.diveSiteCountry->setText(taxonomy_get_country(&diveSite->taxonomy)); - ui.locationTags->setText(constructLocationTags(&diveSite->taxonomy, false)); + ui.diveSiteCountry->setText(QString::fromStdString(taxonomy_get_country(diveSite->taxonomy))); + ui.locationTags->setText(QString::fromStdString(taxonomy_get_location_tags(diveSite->taxonomy, false))); return; case LocationInformationModel::LOCATION: filter_model.setCoordinates(diveSite->location); @@ -217,7 +217,7 @@ static location_t parseGpsText(const QString &text) double lat, lon; if (parseGpsText(text.trimmed(), &lat, &lon)) return create_location(lat, lon); - return zero_location; + return location_t(); } // Check if GPS text is parseable @@ -259,11 +259,11 @@ void LocationInformationWidget::initFields(dive_site *ds) if (ds) { filter_model.set(ds, ds->location); updateLabels(); - enableLocationButtons(dive_site_has_gps_location(ds)); - DiveFilter::instance()->startFilterDiveSites(QVector{ ds }); + enableLocationButtons(ds->has_gps_location()); + DiveFilter::instance()->startFilterDiveSites(std::vector{ ds }); filter_model.invalidate(); } else { - filter_model.set(0, zero_location); + filter_model.set(0, location_t()); clearLabels(); } @@ -272,7 +272,7 @@ void LocationInformationWidget::initFields(dive_site *ds) void LocationInformationWidget::on_GPSbutton_clicked() { - QFileInfo finfo(system_default_directory()); + QFileInfo finfo(QString::fromStdString(system_default_directory())); QString fileName = QFileDialog::getOpenFileName(this, tr("Select GPS file to open"), finfo.absolutePath(), @@ -282,7 +282,7 @@ void LocationInformationWidget::on_GPSbutton_clicked() ImportGPS GPSDialog(this, fileName, &ui); // Create a GPS import QDialog GPSDialog.coords.start_dive = current_dive->when; // initialise - GPSDialog.coords.end_dive = dive_endtime(current_dive); + GPSDialog.coords.end_dive = current_dive->endtime(); if (getCoordsFromGPXFile(&GPSDialog.coords, fileName) == 0) { // Get coordinates from GPS file GPSDialog.updateUI(); // If successful, put results in Dialog if (!GPSDialog.exec()) // and show QDialog @@ -342,15 +342,13 @@ void LocationInformationWidget::reverseGeocode() if (!ds || !has_location(&location)) return; taxonomy_data taxonomy = reverseGeoLookup(location.lat, location.lon); - if (ds != diveSite) { - free_taxonomy(&taxonomy); + if (ds != diveSite) return; - } // This call transfers ownership of the taxonomy memory into an EditDiveSiteTaxonomy object Command::editDiveSiteTaxonomy(ds, taxonomy); } -DiveLocationFilterProxyModel::DiveLocationFilterProxyModel(QObject *) : currentLocation(zero_location) +DiveLocationFilterProxyModel::DiveLocationFilterProxyModel(QObject *) { } @@ -390,9 +388,9 @@ bool DiveLocationFilterProxyModel::lessThan(const QModelIndex &source_left, cons // If there is a current location, sort by that - otherwise use the provided column if (has_location(¤tLocation)) { // The dive sites are -2 because of the first two items. - struct dive_site *ds1 = get_dive_site(source_left.row() - 2, divelog.sites); - struct dive_site *ds2 = get_dive_site(source_right.row() - 2, divelog.sites); - return get_distance(&ds1->location, ¤tLocation) < get_distance(&ds2->location, ¤tLocation); + auto loc1 = (divelog.sites)[source_left.row() - 2]->location; + auto loc2 = (divelog.sites)[source_right.row() - 2]->location; + return get_distance(loc1, currentLocation) < get_distance(loc2, currentLocation); } return source_left.data().toString().compare(source_right.data().toString(), Qt::CaseInsensitive) < 0; } @@ -413,6 +411,9 @@ QVariant DiveLocationModel::data(const QModelIndex &index, int role) const static const QIcon plusIcon(":list-add-icon"); static const QIcon geoCode(":geotag-icon"); + if (index.row() < 0 || index.row() >= (int)divelog.sites.size() + 2) + return QVariant(); + if (index.row() <= 1) { // two special cases. if (index.column() == LocationInformationModel::DIVESITE) return QVariant::fromValue(RECENTLY_ADDED_DIVESITE); @@ -430,8 +431,8 @@ QVariant DiveLocationModel::data(const QModelIndex &index, int role) const } // The dive sites are -2 because of the first two items. - struct dive_site *ds = get_dive_site(index.row() - 2, divelog.sites); - return LocationInformationModel::getDiveSiteData(ds, index.column(), role); + const auto &ds = (divelog.sites)[index.row() - 2]; + return LocationInformationModel::getDiveSiteData(*ds, index.column(), role); } int DiveLocationModel::columnCount(const QModelIndex&) const @@ -441,7 +442,7 @@ int DiveLocationModel::columnCount(const QModelIndex&) const int DiveLocationModel::rowCount(const QModelIndex&) const { - return divelog.sites->nr + 2; + return (int)divelog.sites.size() + 2; } Qt::ItemFlags DiveLocationModel::flags(const QModelIndex &index) const @@ -562,12 +563,10 @@ void DiveLocationLineEdit::refreshDiveSiteCache() static struct dive_site *get_dive_site_name_start_which_str(const QString &str) { - struct dive_site *ds; - int i; - for_each_dive_site (i, ds, divelog.sites) { - QString dsName(ds->name); + for (const auto &ds: divelog.sites) { + QString dsName = QString::fromStdString(ds->name); if (dsName.toLower().startsWith(str.toLower())) - return ds; + return ds.get(); } return NULL; } @@ -587,10 +586,10 @@ void DiveLocationLineEdit::setTemporaryDiveSiteName(const QString &name) // the user entered text. QString i1_name; if (struct dive_site *ds = get_dive_site_name_start_which_str(name)) { - const QString orig_name = QString(ds->name).toLower(); + const QString orig_name = QString::fromStdString(ds->name).toLower(); const QString new_name = name.toLower(); if (new_name != orig_name) - i1_name = QString(ds->name); + i1_name = QString::fromStdString(ds->name); } model->setData(i1, i1_name); @@ -667,16 +666,15 @@ void DiveLocationLineEdit::setCurrentDiveSite(struct dive *d) { location_t currentLocation; if (d) { - currDs = get_dive_site_for_dive(d); - currentLocation = dive_get_gps_location(d); + currDs = d->dive_site; + currentLocation = d->get_gps_location(); } else { currDs = nullptr; - currentLocation = zero_location; } if (!currDs) clear(); else - setText(currDs->name); + setText(QString::fromStdString(currDs->name)); proxy->setCurrentLocation(currentLocation); delegate.setCurrentLocation(currentLocation); } diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index a38c69b7b..806b0d428 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -38,6 +38,7 @@ #include "core/settings/qPrefDisplay.h" #include "desktop-widgets/about.h" +#include "desktop-widgets/divecomponentselection.h" #include "desktop-widgets/divelistview.h" #include "desktop-widgets/divelogexportdialog.h" #include "desktop-widgets/divelogimportdialog.h" @@ -82,7 +83,7 @@ namespace { int progressCounter = 0; } -extern "C" int updateProgress(const char *text) +int updateProgress(const char *text) { if (verbose) report_info("git storage: %s", text); @@ -107,11 +108,9 @@ extern "C" int updateProgress(const char *text) MainWindow *MainWindow::m_Instance = nullptr; -extern "C" void showErrorFromC(char *buf) +static void showError(std::string err) { - QString error(buf); - free(buf); - emit MainWindow::instance()->showError(error); + emit MainWindow::instance()->showError(QString::fromStdString(err)); } MainWindow::MainWindow() : @@ -209,8 +208,6 @@ MainWindow::MainWindow() : #ifdef NO_USERMANUAL ui.menuHelp->removeAction(ui.actionUserManual); #endif - memset(©PasteDive, 0, sizeof(copyPasteDive)); - memset(&what, 0, sizeof(what)); updateManager = new UpdateManager(this); undoAction = Command::undoAction(this); @@ -243,7 +240,7 @@ MainWindow::MainWindow() : setupSocialNetworkMenu(); set_git_update_cb(&updateProgress); - set_error_cb(&showErrorFromC); + set_error_cb(&::showError); // full screen support is buggy on Windows and Ubuntu. // require the FULLSCREEN_SUPPORT macro to enable it! @@ -289,7 +286,7 @@ void MainWindow::editDiveSite(dive_site *ds) void MainWindow::startDiveSiteEdit() { if (current_dive) - editDiveSite(get_dive_site_for_dive(current_dive)); + editDiveSite(current_dive->dive_site); } void MainWindow::enableDisableCloudActions() @@ -331,7 +328,7 @@ void MainWindow::divesSelected(const std::vector &selection, dive *curre // Activate cursor keys to switch through DCs if there are more than one DC. if (currentDive) { - bool nr = number_of_computers(current_dive) > 1; + bool nr = currentDive->number_of_computers() > 1; enableShortcuts(); ui.actionNextDC->setEnabled(nr); ui.actionPreviousDC->setEnabled(nr); @@ -422,7 +419,7 @@ void MainWindow::on_actionCloudstorageopen_triggered() std::string encoded = encodeFileName(*filename); if (!parse_file(encoded.c_str(), &divelog)) setCurrentFile(encoded); - process_loaded_dives(); + divelog.process_loaded_dives(); hideProgressBar(); refreshDisplay(); updateAutogroup(); @@ -431,7 +428,7 @@ void MainWindow::on_actionCloudstorageopen_triggered() // Return whether saving to cloud is OK. If it isn't, show an error return false. static bool saveToCloudOK() { - if (!divelog.dives->nr) { + if (divelog.dives.empty()) { report_error("%s", qPrintable(gettextFromC::tr("Don't save an empty log to the cloud"))); return false; } @@ -552,8 +549,8 @@ void MainWindow::updateLastUsedDir(const QString &dir) static QString get_current_filename() { - return existing_filename.empty() ? QString(prefs.default_filename) - : QString::fromStdString(existing_filename); + return QString::fromStdString(existing_filename.empty() ? prefs.default_filename + : existing_filename); } void MainWindow::on_actionPrint_triggered() { @@ -666,7 +663,7 @@ void MainWindow::on_actionReplanDive_triggered() if (!plannerStateClean() || !current_dive || !userMayChangeAppState()) return; - const struct divecomputer *dc = get_dive_dc(current_dive, profile->dc); + const struct divecomputer *dc = current_dive->get_dc(profile->dc); if (!(is_dc_planner(dc) || is_dc_manually_added_dive(dc))) { if (QMessageBox::warning(this, tr("Warning"), tr("Trying to replan a dive profile that has not been manually added."), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) @@ -691,6 +688,7 @@ void MainWindow::on_actionDivePlanner_triggered() setApplicationState(ApplicationState::PlanDive); disableShortcuts(true); + profile->exitEditMode(); plannerWidgets->preparePlanDive(current_dive, profile->dc); profile->setPlanState(plannerWidgets->getDive(), plannerWidgets->getDcNr()); plannerWidgets->planDive(); @@ -701,20 +699,8 @@ void MainWindow::on_actionAddDive_triggered() if (!plannerStateClean()) return; - // create a dive an hour from now with a default depth (15m/45ft) and duration (40 minutes) - // as a starting point for the user to edit - struct dive d = { 0 }; - d.id = dive_getUniqID(); - d.when = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset() + 3600; - d.dc.duration.seconds = 40 * 60; - d.dc.maxdepth.mm = M_OR_FT(15, 45); - d.dc.meandepth.mm = M_OR_FT(13, 39); // this creates a resonable looking safety stop - make_manually_added_dive_dc(&d.dc); - fake_dc(&d.dc); - add_default_cylinder(&d); - fixup_dive(&d); - - Command::addDive(&d, divelog.autogroup, true); + auto d = divelog.dives.default_dive(); + Command::addDive(std::move(d), divelog.autogroup, true); } void MainWindow::on_actionRenumber_triggered() @@ -1103,7 +1089,7 @@ void MainWindow::loadRecentFiles() QString file = s.value(key).toString(); // never add our cloud URL to the recent files - if (file.startsWith(prefs.cloud_base_url)) + if (file.startsWith(QString::fromStdString(prefs.cloud_base_url))) continue; // but allow local git repos QRegularExpression gitrepo("(.*)\\[[^]]+]"); @@ -1141,7 +1127,7 @@ void MainWindow::updateRecentFilesMenu() void MainWindow::addRecentFile(const QString &file, bool update) { // never add Subsurface cloud file to the recent files - it has its own menu entry - if (file.startsWith(prefs.cloud_base_url)) + if (file.startsWith(QString::fromStdString(prefs.cloud_base_url))) return; QString localFile = QDir::toNativeSeparators(file); int index = recentFiles.indexOf(localFile); @@ -1184,7 +1170,7 @@ void MainWindow::recentFileTriggered(bool) loadFiles(std::vector { filename.toStdString() }); } -int MainWindow::file_save_as(void) +int MainWindow::file_save_as() { QString filename; std::string default_filename = existing_filename; @@ -1204,7 +1190,7 @@ int MainWindow::file_save_as(void) selection_dialog.setFileMode(QFileDialog::AnyFile); selection_dialog.setDefaultSuffix(""); if (default_filename.empty()) { - QFileInfo defaultFile(system_default_filename()); + QFileInfo defaultFile(QString::fromStdString(system_default_filename())); selection_dialog.setDirectory(qPrintable(defaultFile.absolutePath())); } /* if the exit/cancel button is pressed return */ @@ -1232,9 +1218,8 @@ int MainWindow::file_save_as(void) return 0; } -int MainWindow::file_save(void) +int MainWindow::file_save() { - const char *current_default; bool is_cloud = false; if (existing_filename.empty()) @@ -1244,11 +1229,11 @@ int MainWindow::file_save(void) if (is_cloud && !saveToCloudOK()) return -1; - current_default = prefs.default_filename; + const std::string ¤t_default = prefs.default_filename; if (existing_filename == current_default) { /* if we are using the default filename the directory * that we are creating the file in may not exist */ - QDir current_def_dir = QFileInfo(current_default).absoluteDir(); + QDir current_def_dir = QFileInfo(QString::fromStdString(current_default)).absoluteDir(); if (!current_def_dir.exists()) current_def_dir.mkpath(current_def_dir.absolutePath()); } @@ -1317,7 +1302,7 @@ void MainWindow::importFiles(const std::vector &fileNames) parse_file(encoded.c_str(), &log); } QString source = fileNames.size() == 1 ? QString::fromStdString(fileNames[0]) : tr("multiple files"); - Command::importDives(&log, IMPORT_MERGE_ALL_TRIPS, source); + Command::importDives(&log, import_flags::merge_all_trips, source); } void MainWindow::loadFiles(const std::vector &fileNames) @@ -1338,13 +1323,13 @@ void MainWindow::loadFiles(const std::vector &fileNames) } hideProgressBar(); updateRecentFiles(); - process_loaded_dives(); + divelog.process_loaded_dives(); refreshDisplay(); updateAutogroup(); int min_datafile_version = get_min_datafile_version(); - if (min_datafile_version >0 && min_datafile_version < DATAFORMAT_VERSION) { + if (min_datafile_version >0 && min_datafile_version < dataformat_version) { QMessageBox::warning(this, tr("Opening datafile from older version"), tr("You opened a data file from an older version of Subsurface. We recommend " "you read the manual to learn about the changes in the new version, especially " @@ -1409,12 +1394,12 @@ void MainWindow::on_actionImportDiveSites_triggered() parse_file(fileNamePtr.data(), &log); } // The imported dive sites still have pointers to imported dives - remove them - for (int i = 0; i < log.sites->nr; ++i) - log.sites->dive_sites[i]->dives.nr = 0; + for (const auto &ds: log.sites) + ds->dives.clear(); QString source = fileNames.size() == 1 ? fileNames[0] : tr("multiple files"); - DivesiteImportDialog divesiteImport(*log.sites, source, this); + DivesiteImportDialog divesiteImport(std::move(log.sites), source, this); divesiteImport.exec(); } @@ -1440,13 +1425,13 @@ void MainWindow::on_copy_triggered() { // open dialog to select what gets copied // copy the displayed dive - DiveComponentSelection dialog(this, ©PasteDive, &what); + DiveComponentSelection dialog(paste_data, this); dialog.exec(); } void MainWindow::on_paste_triggered() { - Command::pasteDives(©PasteDive, what); + Command::pasteDives(paste_data); } void MainWindow::on_actionFilterTags_triggered() @@ -1587,8 +1572,8 @@ void MainWindow::hideProgressBar() void MainWindow::divesChanged(const QVector &dives, DiveField) { for (struct dive *d: dives) { - report_info("dive #%d changed, cache is %s", d->number, dive_cache_is_valid(d) ? "valid" : "invalidated"); + report_info("dive #%d changed, cache is %s", d->number, d->cache_is_valid() ? "valid" : "invalidated"); // a brute force way to deal with that would of course be to call - // invalidate_dive_cache(d); + // d->invalidate_cache(); } } diff --git a/desktop-widgets/mainwindow.h b/desktop-widgets/mainwindow.h index 12587347b..6e757b216 100644 --- a/desktop-widgets/mainwindow.h +++ b/desktop-widgets/mainwindow.h @@ -203,8 +203,7 @@ private: bool plannerStateClean(); void setupSocialNetworkMenu(); QDialog *findMovedImagesDialog; - struct dive copyPasteDive; - struct dive_components what; + dive_paste_data paste_data; QStringList recentFiles; QAction *actionsRecent[NUM_RECENT_FILES]; diff --git a/desktop-widgets/mapwidget.cpp b/desktop-widgets/mapwidget.cpp index 3d8e9b3ca..ec1ba782e 100644 --- a/desktop-widgets/mapwidget.cpp +++ b/desktop-widgets/mapwidget.cpp @@ -61,7 +61,7 @@ void MapWidget::centerOnIndex(const QModelIndex& idx) { CHECK_IS_READY_RETURN_VOID(); dive_site *ds = idx.model()->index(idx.row(), LocationInformationModel::DIVESITE).data().value(); - if (!ds || ds == RECENTLY_ADDED_DIVESITE || !dive_site_has_gps_location(ds)) + if (!ds || ds == RECENTLY_ADDED_DIVESITE || !ds->has_gps_location()) m_mapHelper->centerOnSelectedDiveSite(); else centerOnDiveSite(ds); @@ -79,10 +79,10 @@ bool MapWidget::editMode() const return isReady && m_mapHelper->editMode(); } -void MapWidget::setSelected(const QVector &divesites) +void MapWidget::setSelected(std::vector divesites) { CHECK_IS_READY_RETURN_VOID(); - m_mapHelper->setSelected(divesites); + m_mapHelper->setSelected(std::move(divesites)); m_mapHelper->centerOnSelectedDiveSite(); } @@ -94,10 +94,10 @@ void MapWidget::selectedDivesChanged(const QList &list) std::vector selection; selection.reserve(list.size()); for (int idx: list) { - if (dive *d = get_dive(idx)) - selection.push_back(d); + if (idx >= 0 && static_cast(idx) < divelog.dives.size()) + selection.push_back(divelog.dives[idx].get()); } - setSelection(selection, current_dive, -1); + setSelection(std::move(selection), current_dive, -1); } void MapWidget::coordinatesChanged(struct dive_site *ds, const location_t &location) diff --git a/desktop-widgets/mapwidget.h b/desktop-widgets/mapwidget.h index 123b7ad52..09ce7f006 100644 --- a/desktop-widgets/mapwidget.h +++ b/desktop-widgets/mapwidget.h @@ -4,6 +4,7 @@ #include "core/units.h" #include "core/subsurface-qt/divelistnotifier.h" +#include #include #include @@ -23,7 +24,7 @@ public: static MapWidget *instance(); void reload(); - void setSelected(const QVector &divesites); + void setSelected(std::vector divesites); bool editMode() const; public slots: diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index d0ca3e541..68036a45a 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -219,9 +219,7 @@ void TankInfoDelegate::setModelData(QWidget *, QAbstractItemModel *model, const return; } - volume_t tankSize = {0}; - pressure_t tankPressure = {0}; - get_tank_info_data(&tank_info_table, qPrintable(cylinderName), &tankSize, &tankPressure); + auto [tankSize, tankPressure] = get_tank_info_data(tank_info_table, cylinderName.toStdString()); mymodel->setData(IDX(CylindersModel::TYPE), cylinderName, CylindersModel::TEMP_ROLE); mymodel->setData(IDX(CylindersModel::WORKINGPRESS), tankPressure.mbar, CylindersModel::TEMP_ROLE); mymodel->setData(IDX(CylindersModel::SIZE), tankSize.mliter, CylindersModel::TEMP_ROLE); @@ -248,24 +246,32 @@ void TankInfoDelegate::editorClosed(QWidget *, QAbstractItemDelegate::EndEditHin mymodel->setData(IDX(CylindersModel::TYPE), currCombo.activeText, CylindersModel::COMMIT_ROLE); } -TankUseDelegate::TankUseDelegate(QObject *parent) : QStyledItemDelegate(parent), currentdc(nullptr) +TankUseDelegate::TankUseDelegate(QObject *parent) : QStyledItemDelegate(parent), currentDive(nullptr), currentDcNr(0) { } -void TankUseDelegate::setCurrentDC(divecomputer *dc) +void TankUseDelegate::setDiveDc(const dive &d, int &dcNr) { - currentdc = dc; + currentDive = &d; + currentDcNr = &dcNr; } QWidget *TankUseDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { QComboBox *comboBox = new QComboBox(parent); - if (!currentdc) + const divecomputer *dc = currentDive->get_dc(*currentDcNr); + if (!dc) return comboBox; - bool isCcrDive = currentdc->divemode == CCR; + bool isCcrDive = dc->divemode == CCR; + bool isFreeDive = dc->divemode == FREEDIVE; for (int i = 0; i < NUM_GAS_USE; i++) { - if (isCcrDive || (i != DILUENT && i != OXYGEN)) - comboBox->addItem(gettextFromC::tr(cylinderuse_text[i])); + if (isFreeDive && i != NOT_USED) + continue; + + if (!isCcrDive && (i == DILUENT || i == OXYGEN)) + continue; + + comboBox->addItem(gettextFromC::tr(cylinderuse_text[i])); } return comboBox; } @@ -300,8 +306,7 @@ QWidget *SensorDelegate::createEditor(QWidget *parent, const QStyleOptionViewIte return comboBox; std::vector sensors; - for (int i = 0; i < currentdc->samples; ++i) { - auto &sample = currentdc->sample[i]; + for (const auto &sample: currentdc->samples) { for (int s = 0; s < MAX_SENSORS; ++s) { if (sample.pressure[s].mbar) { if (std::find(sensors.begin(), sensors.end(), sample.sensor[s]) == sensors.end()) @@ -338,11 +343,10 @@ void WSInfoDelegate::editorClosed(QWidget *, QAbstractItemDelegate::EndEditHint void WSInfoDelegate::setModelData(QWidget *, QAbstractItemModel *, const QModelIndex &) const { WeightModel *mymodel = qobject_cast(currCombo.model); - QString weightName = currCombo.activeText; - ws_info_t *info = get_weightsystem_description(qPrintable(weightName)); - int grams = info ? info->grams : 0; + std::string weightName = currCombo.activeText.toStdString(); + weight_t weight = get_weightsystem_weight(weightName.c_str()); - mymodel->setTempWS(currCombo.currRow, weightsystem_t{ { grams }, copy_qstring(weightName), false }); + mymodel->setTempWS(currCombo.currRow, weightsystem_t( weight, std::move(weightName), false )); } static QAbstractItemModel *createWSInfoModel(QWidget *parent) @@ -354,21 +358,20 @@ WSInfoDelegate::WSInfoDelegate(QObject *parent) : ComboBoxDelegate(&createWSInfo { } -void AirTypesDelegate::editorClosed(QWidget *, QAbstractItemDelegate::EndEditHint) +void GasTypesDelegate::editorClosed(QWidget *, QAbstractItemDelegate::EndEditHint) { } -void AirTypesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const +void GasTypesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { if (!index.isValid()) return; QComboBox *combo = qobject_cast(editor); - model->setData(index, QVariant(combo->currentIndex())); + model->setData(index, combo->currentData(Qt::UserRole)); } -AirTypesDelegate::AirTypesDelegate(const dive &d, QObject *parent) : - ComboBoxDelegate([&d] (QWidget *parent) { return new GasSelectionModel(d, parent); }, - parent, false) +GasTypesDelegate::GasTypesDelegate(const dive &d, int &dcNr, QObject *parent) : + ComboBoxDelegate([&d, &dcNr] (QWidget *parent) { return new GasSelectionModel(d, dcNr, parent); }, parent, false) { } @@ -381,15 +384,11 @@ void DiveTypesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, if (!index.isValid()) return; QComboBox *combo = qobject_cast(editor); - model->setData(index, QVariant(combo->currentIndex())); + model->setData(index, combo->currentData(Qt::UserRole)); } -static QAbstractItemModel *createDiveTypeSelectionModel(QWidget *parent) -{ - return new DiveTypeSelectionModel(parent); -} - -DiveTypesDelegate::DiveTypesDelegate(QObject *parent) : ComboBoxDelegate(&createDiveTypeSelectionModel, parent, false) +DiveTypesDelegate::DiveTypesDelegate(const dive &d, int &dcNr, QObject *parent) : + ComboBoxDelegate([&d, &dcNr] (QWidget *parent) { return new DiveTypeSelectionModel(d, dcNr, parent); }, parent, false) { } @@ -425,7 +424,7 @@ QWidget *DoubleSpinBoxDelegate::createEditor(QWidget *parent, const QStyleOption return w; } -LocationFilterDelegate::LocationFilterDelegate(QObject *) : currentLocation(zero_location) +LocationFilterDelegate::LocationFilterDelegate(QObject *) { } @@ -464,26 +463,26 @@ void LocationFilterDelegate::paint(QPainter *painter, const QStyleOptionViewItem for (int i = 0; i < 3; i++) { if (prefs.geocoding.category[i] == TC_NONE) continue; - const char *value = taxonomy_get_value(&ds->taxonomy, prefs.geocoding.category[i]); - if (empty_string(value)) + std::string value = taxonomy_get_value(ds->taxonomy, prefs.geocoding.category[i]); + if (!value.empty()) continue; if(!bottomText.isEmpty()) bottomText += " / "; - bottomText += QString(value); + bottomText += QString::fromStdString(value); } if (bottomText.isEmpty()) bottomText = printGPSCoords(&ds->location); - if (dive_site_has_gps_location(ds) && currentDiveHasGPS) { + if (ds->has_gps_location() && currentDiveHasGPS) { // so we are showing a completion and both the current dive site and the completion // have a GPS fix... so let's show the distance - if (same_location(&ds->location, ¤tLocation)) { + if (ds->location == currentLocation) { bottomText += tr(" (same GPS fix)"); } else { - int distanceMeters = get_distance(&ds->location, ¤tLocation); + int distanceMeters = get_distance(ds->location, currentLocation); QString distance = distance_string(distanceMeters); - int nr = nr_of_dives_at_dive_site(ds); + size_t nr = ds->nr_of_dives(); bottomText += tr(" (~%1 away").arg(distance); bottomText += tr(", %n dive(s) here)", "", nr); } diff --git a/desktop-widgets/modeldelegates.h b/desktop-widgets/modeldelegates.h index 9d3a392b0..7e59d0363 100644 --- a/desktop-widgets/modeldelegates.h +++ b/desktop-widgets/modeldelegates.h @@ -75,12 +75,13 @@ class TankUseDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit TankUseDelegate(QObject *parent = 0); - void setCurrentDC(divecomputer *dc); + void setDiveDc(const dive &d, int &dcNr); private: void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; - divecomputer *currentdc; + const dive *currentDive; + int *currentDcNr; }; class SensorDelegate : public QStyledItemDelegate { @@ -103,10 +104,10 @@ private: void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) override; }; -class AirTypesDelegate : public ComboBoxDelegate { +class GasTypesDelegate : public ComboBoxDelegate { Q_OBJECT public: - explicit AirTypesDelegate(const dive &d, QObject *parent = 0); + explicit GasTypesDelegate(const dive &d, int &dcNr, QObject *parent = 0); private: void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) override; @@ -115,7 +116,7 @@ private: class DiveTypesDelegate : public ComboBoxDelegate { Q_OBJECT public: - explicit DiveTypesDelegate(QObject *parent = 0); + explicit DiveTypesDelegate(const dive &d, int &dcNr, QObject *parent = 0); private: void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void editorClosed(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) override; diff --git a/desktop-widgets/plannerSettings.ui b/desktop-widgets/plannerSettings.ui index 33e3d2456..d4eb66ec1 100644 --- a/desktop-widgets/plannerSettings.ui +++ b/desktop-widgets/plannerSettings.ui @@ -255,7 +255,7 @@ 2 - + bar @@ -274,7 +274,7 @@ - + Qt::Vertical @@ -287,7 +287,7 @@ - + Postpone gas change if a stop is not required @@ -297,14 +297,14 @@ - + Last stop at 6m - + + @@ -314,7 +314,7 @@ - + % @@ -327,7 +327,7 @@ - + min @@ -346,14 +346,14 @@ - + Plan backgas breaks - + Qt::Vertical @@ -366,21 +366,21 @@ - + Min. switch duration O₂% below 100% - + Drop to first depth - + % @@ -393,7 +393,7 @@ - + GFLow @@ -403,7 +403,7 @@ - + GFHigh @@ -413,7 +413,7 @@ - + Maximize bottom time allowed by gas and no decompression limits @@ -423,7 +423,7 @@ - + Conservatism level @@ -440,7 +440,7 @@ - + Qt::LeftToRight @@ -453,7 +453,7 @@ - + Qt::Vertical @@ -466,7 +466,7 @@ - + Qt::Vertical @@ -479,24 +479,7 @@ - - - - - - - 6 - - - - - - - Dive mode - - - - + Bühlmann deco @@ -506,7 +489,7 @@ - + Reserve gas @@ -516,21 +499,21 @@ - + - Bailout: Deco on OC + Rebreather: Bailout / Deco on OC - + Surface segment - + min @@ -777,7 +760,7 @@ - + SAC factor @@ -857,7 +840,7 @@ ascRateStops ascRateLast6m descRate - rebreathermode + divemode recreational_deco reserve_gas safetystop diff --git a/desktop-widgets/preferences/preferences_cloud.cpp b/desktop-widgets/preferences/preferences_cloud.cpp index 01545f3b1..b68ed0d0c 100644 --- a/desktop-widgets/preferences/preferences_cloud.cpp +++ b/desktop-widgets/preferences/preferences_cloud.cpp @@ -30,8 +30,8 @@ void PreferencesCloud::on_resetPassword_clicked() void PreferencesCloud::refreshSettings() { - ui->cloud_storage_email->setText(prefs.cloud_storage_email); - ui->cloud_storage_password->setText(prefs.cloud_storage_password); + ui->cloud_storage_email->setText(QString::fromStdString(prefs.cloud_storage_email)); + ui->cloud_storage_password->setText(QString::fromStdString(prefs.cloud_storage_password)); ui->save_password_local->setChecked(prefs.save_password_local); updateCloudAuthenticationState(); } @@ -57,19 +57,19 @@ void PreferencesCloud::syncSettings() } if (!reg.match(email).hasMatch() || (!newpassword.isEmpty() && !reg.match(newpassword).hasMatch())) { QMessageBox::warning(this, tr("Warning"), emailpasswordformatwarning); - ui->cloud_storage_new_passwd->setText(""); + ui->cloud_storage_new_passwd->setText(QString()); return; } CloudStorageAuthenticate *cloudAuth = new CloudStorageAuthenticate(this); connect(cloudAuth, &CloudStorageAuthenticate::finishedAuthenticate, this, &PreferencesCloud::updateCloudAuthenticationState); connect(cloudAuth, &CloudStorageAuthenticate::passwordChangeSuccessful, this, &PreferencesCloud::passwordUpdateSuccessful); cloudAuth->backend(email, password, "", newpassword); - ui->cloud_storage_new_passwd->setText(""); + ui->cloud_storage_new_passwd->setText(QString()); } } else if (prefs.cloud_verification_status == qPrefCloudStorage::CS_UNKNOWN || prefs.cloud_verification_status == qPrefCloudStorage::CS_INCORRECT_USER_PASSWD || - email != prefs.cloud_storage_email || - password != prefs.cloud_storage_password) { + email.toStdString() != prefs.cloud_storage_email || + password.toStdString() != prefs.cloud_storage_password) { // different credentials - reset verification status int oldVerificationStatus = cloud->cloud_verification_status(); @@ -104,7 +104,7 @@ void PreferencesCloud::syncSettings() cloud->set_save_password_local(ui->save_password_local->isChecked()); cloud->set_cloud_storage_password(password); cloud->set_cloud_verification_status(prefs.cloud_verification_status); - cloud->set_cloud_base_url(prefs.cloud_base_url); + cloud->set_cloud_base_url(QString::fromStdString(prefs.cloud_base_url)); } void PreferencesCloud::updateCloudAuthenticationState() @@ -131,5 +131,5 @@ void PreferencesCloud::updateCloudAuthenticationState() void PreferencesCloud::passwordUpdateSuccessful() { - ui->cloud_storage_password->setText(prefs.cloud_storage_password); + ui->cloud_storage_password->setText(QString::fromStdString(prefs.cloud_storage_password)); } diff --git a/desktop-widgets/preferences/preferences_equipment.cpp b/desktop-widgets/preferences/preferences_equipment.cpp index 1e8c1e1e8..957c2b143 100644 --- a/desktop-widgets/preferences/preferences_equipment.cpp +++ b/desktop-widgets/preferences/preferences_equipment.cpp @@ -26,11 +26,12 @@ void PreferencesEquipment::refreshSettings() ui->include_unused_tanks->setChecked(prefs.include_unused_tanks); ui->display_default_tank_infos->setChecked(prefs.display_default_tank_infos); ui->default_cylinder->clear(); - for (int i = 0; i < tank_info_table.nr; i++) { - const tank_info &ti = tank_info_table.infos[i]; - ui->default_cylinder->addItem(ti.name); - if (qPrefEquipment::default_cylinder() == ti.name) + int i = 0; + for (const tank_info &ti: tank_info_table) { + ui->default_cylinder->addItem(QString::fromStdString(ti.name)); + if (qPrefEquipment::default_cylinder() == QString::fromStdString(ti.name)) ui->default_cylinder->setCurrentIndex(i); + ++i; } } @@ -45,5 +46,5 @@ void PreferencesEquipment::syncSettings() // reset the tank_info_table. It is somewhat questionable // to do this here. Moreover, it is a bit crude, as this // will be called for *any* preferences change. - reset_tank_info_table(&tank_info_table); + reset_tank_info_table(tank_info_table); } diff --git a/desktop-widgets/preferences/preferences_graph.cpp b/desktop-widgets/preferences/preferences_graph.cpp index d4bdf81c5..6ac68c27e 100644 --- a/desktop-widgets/preferences/preferences_graph.cpp +++ b/desktop-widgets/preferences/preferences_graph.cpp @@ -50,6 +50,7 @@ void PreferencesGraph::refreshSettings() ui->pscrfactor->setValue(lrint(1000.0 / prefs.pscr_ratio)); ui->show_icd->setChecked(prefs.show_icd); + ui->allowOcGasAsDiluent->setChecked(prefs.allowOcGasAsDiluent); } void PreferencesGraph::syncSettings() @@ -75,6 +76,7 @@ void PreferencesGraph::syncSettings() qPrefTechnicalDetails::set_show_scr_ocpo2(ui->show_scr_ocpo2->isChecked()); qPrefTechnicalDetails::set_show_icd(ui->show_icd->isChecked()); qPrefTechnicalDetails::set_display_deco_mode(ui->vpmb->isChecked() ? VPMB : BUEHLMANN); + qPrefTechnicalDetails::set_allowOcGasAsDiluent(ui->allowOcGasAsDiluent->isChecked()); } static const char *danger_gf(int gf) diff --git a/desktop-widgets/preferences/preferences_graph.ui b/desktop-widgets/preferences/preferences_graph.ui index d2633ff6b..562f30daa 100644 --- a/desktop-widgets/preferences/preferences_graph.ui +++ b/desktop-widgets/preferences/preferences_graph.ui @@ -19,7 +19,7 @@ Gas pressure display setup - + @@ -125,21 +125,30 @@ - + + + + + + + Rebreather setup + + + CCR options: - + Dive planner default setpoint - + bar @@ -155,38 +164,35 @@ - + Show O₂ sensor values when viewing pO₂ - + Show CCR setpoints when viewing pO₂ - + pSCR options: - + pSCR metabolic rate O₂ - - Qt::AlignCenter - - + ℓ/min @@ -196,7 +202,7 @@ - + Dilution ratio @@ -206,7 +212,7 @@ - + @@ -216,29 +222,45 @@ - + Show equivalent OC pO₂ with pSCR pO₂ - - - - Show warnings for isobaric counterdiffusion - - - - + + + Planner setup + + + + + + Show warnings for isobaric counterdiffusion + + + + + + + Allow open circuit gas to be used as diluent for CCR + + + + + + + + Ceiling display setup - + diff --git a/desktop-widgets/preferences/preferences_language.cpp b/desktop-widgets/preferences/preferences_language.cpp index 80caaa686..673d36d07 100644 --- a/desktop-widgets/preferences/preferences_language.cpp +++ b/desktop-widgets/preferences/preferences_language.cpp @@ -54,11 +54,11 @@ void PreferencesLanguage::refreshSettings() ui->languageSystemDefault->setChecked(prefs.locale.use_system_language); ui->timeFormatSystemDefault->setChecked(!prefs.time_format_override); ui->dateFormatSystemDefault->setChecked(!prefs.date_format_override); - ui->timeFormatEntry->setCurrentText(prefs.time_format); - ui->dateFormatEntry->setCurrentText(prefs.date_format); - ui->shortDateFormatEntry->setText(prefs.date_format_short); + ui->timeFormatEntry->setCurrentText(QString::fromStdString(prefs.time_format)); + ui->dateFormatEntry->setCurrentText(QString::fromStdString(prefs.date_format)); + ui->shortDateFormatEntry->setText(QString::fromStdString(prefs.date_format_short)); QAbstractItemModel *m = ui->languageDropdown->model(); - QModelIndexList languages = m->match(m->index(0, 0), Qt::UserRole, QString(prefs.locale.lang_locale).replace("-", "_")); + QModelIndexList languages = m->match(m->index(0, 0), Qt::UserRole, QString::fromStdString(prefs.locale.lang_locale).replace("-", "_")); if (languages.count()) ui->languageDropdown->setCurrentIndex(languages.first().row()); } @@ -69,9 +69,9 @@ void PreferencesLanguage::syncSettings() QString currentText = ui->languageDropdown->currentText(); if (useSystemLang != ui->languageSystemDefault->isChecked() || - (!useSystemLang && currentText != prefs.locale.language)) { + (!useSystemLang && currentText.toStdString() != prefs.locale.language)) { // remove the googlemaps cache folder on language change - QDir googlecachedir(QString(system_default_directory()).append("/googlemaps")); + QDir googlecachedir(QString::fromStdString(system_default_directory() + "/googlemaps")); googlecachedir.removeRecursively(); QMessageBox::warning(this, tr("Restart required"), diff --git a/desktop-widgets/preferences/preferences_log.cpp b/desktop-widgets/preferences/preferences_log.cpp index ccbf566a4..030da3c12 100644 --- a/desktop-widgets/preferences/preferences_log.cpp +++ b/desktop-widgets/preferences/preferences_log.cpp @@ -24,7 +24,7 @@ PreferencesLog::~PreferencesLog() void PreferencesLog::on_chooseFile_clicked() { - QFileInfo fi(system_default_filename()); + QFileInfo fi(QString::fromStdString(system_default_filename())); QString choosenFileName = QFileDialog::getOpenFileName(this, tr("Open default log file"), fi.absolutePath(), tr("Subsurface files") + " (*.ssrf *.xml)"); if (!choosenFileName.isEmpty()) @@ -34,7 +34,7 @@ void PreferencesLog::on_chooseFile_clicked() void PreferencesLog::on_btnUseDefaultFile_toggled(bool toggle) { if (toggle) { - ui->defaultfilename->setText(system_default_filename()); + ui->defaultfilename->setText(QString::fromStdString(system_default_filename())); ui->defaultfilename->setEnabled(false); } else { ui->defaultfilename->setEnabled(true); diff --git a/desktop-widgets/preferences/preferences_media.cpp b/desktop-widgets/preferences/preferences_media.cpp index 594493a97..7d669f69c 100644 --- a/desktop-widgets/preferences/preferences_media.cpp +++ b/desktop-widgets/preferences/preferences_media.cpp @@ -41,7 +41,7 @@ void PreferencesMedia::checkFfmpegExecutable() void PreferencesMedia::on_ffmpegFile_clicked() { - QFileInfo fi(system_default_filename()); + QFileInfo fi(QString::fromStdString(system_default_filename())); QString ffmpegFileName = QFileDialog::getOpenFileName(this, tr("Select ffmpeg executable")); if (!ffmpegFileName.isEmpty()) { diff --git a/desktop-widgets/preferences/preferences_network.cpp b/desktop-widgets/preferences/preferences_network.cpp index 7ea08d077..f3c154a00 100644 --- a/desktop-widgets/preferences/preferences_network.cpp +++ b/desktop-widgets/preferences/preferences_network.cpp @@ -27,11 +27,11 @@ PreferencesNetwork::~PreferencesNetwork() void PreferencesNetwork::refreshSettings() { - ui->proxyHost->setText(prefs.proxy_host); + ui->proxyHost->setText(QString::fromStdString(prefs.proxy_host)); ui->proxyPort->setValue(prefs.proxy_port); ui->proxyAuthRequired->setChecked(prefs.proxy_auth); - ui->proxyUsername->setText(prefs.proxy_user); - ui->proxyPassword->setText(prefs.proxy_pass); + ui->proxyUsername->setText(QString::fromStdString(prefs.proxy_user)); + ui->proxyPassword->setText(QString::fromStdString(prefs.proxy_pass)); ui->proxyType->setCurrentIndex(ui->proxyType->findData(prefs.proxy_type)); } @@ -49,9 +49,8 @@ void PreferencesNetwork::syncSettings() void PreferencesNetwork::proxyType_changed(int idx) { - if (idx == -1) { + if (idx < 0) return; - } int proxyType = ui->proxyType->itemData(idx).toInt(); bool hpEnabled = (proxyType == QNetworkProxy::Socks5Proxy || proxyType == QNetworkProxy::HttpProxy); diff --git a/desktop-widgets/preferences/preferencesdialog.cpp b/desktop-widgets/preferences/preferencesdialog.cpp index 593c0cf20..011ecbd56 100644 --- a/desktop-widgets/preferences/preferencesdialog.cpp +++ b/desktop-widgets/preferences/preferencesdialog.cpp @@ -37,12 +37,6 @@ static bool abstractpreferenceswidget_lessthan(const AbstractPreferencesWidget * PreferencesDialog::PreferencesDialog() { - //FIXME: This looks wrong. - //QSettings s; - //s.beginGroup("GeneralSettings"); - //s.setValue("default_directory", system_default_directory()); - //s.endGroup(); - setWindowIcon(QIcon(":subsurface-icon")); setWindowTitle(tr("Preferences")); pagesList = new QListWidget(); @@ -130,7 +124,7 @@ void PreferencesDialog::cancelRequested() void PreferencesDialog::defaultsRequested() { - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; refreshPages(); emit diveListNotifier.settingsChanged(); accept(); diff --git a/desktop-widgets/printdialog.cpp b/desktop-widgets/printdialog.cpp index 8abaac4a2..885d8d321 100644 --- a/desktop-widgets/printdialog.cpp +++ b/desktop-widgets/printdialog.cpp @@ -184,7 +184,7 @@ void PrintDialog::createPrinterObj() } } -void PrintDialog::previewClicked(void) +void PrintDialog::previewClicked() { createPrinterObj(); QPrintPreviewDialog previewDialog(qprinter, this, Qt::Window @@ -194,7 +194,7 @@ void PrintDialog::previewClicked(void) previewDialog.exec(); } -void PrintDialog::exportHtmlClicked(void) +void PrintDialog::exportHtmlClicked() { createPrinterObj(); QString saveFileName = printOptions.p_template; @@ -212,7 +212,7 @@ void PrintDialog::exportHtmlClicked(void) } } -void PrintDialog::printClicked(void) +void PrintDialog::printClicked() { createPrinterObj(); QPrintDialog printDialog(qprinter, this); diff --git a/desktop-widgets/printer.cpp b/desktop-widgets/printer.cpp index 1491caed1..c1a46177c 100644 --- a/desktop-widgets/printer.cpp +++ b/desktop-widgets/printer.cpp @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 #include "printer.h" #include "templatelayout.h" -#include "core/dive.h" // for get_dive_by_uniq_id() +#include "core/divelist.h" +#include "core/divelog.h" #include "core/selection.h" #include "core/statistics.h" #include "core/qthelper.h" @@ -129,7 +130,7 @@ void Printer::render(int pages) // dive id field should be dive_{{dive_no}} se we remove the first 5 characters QString diveIdString = collection.at(elemNo).attribute("id"); int diveId = diveIdString.remove(0, 5).toInt(0, 10); - putProfileImage(collection.at(elemNo).geometry(), viewPort, &painter, get_dive_by_uniq_id(diveId), profile.get()); + putProfileImage(collection.at(elemNo).geometry(), viewPort, &painter, divelog.dives.get_by_uniq_id(diveId), profile.get()); elemNo++; } @@ -160,10 +161,8 @@ std::vector Printer::getDives() const return getDiveSelection(); } else { std::vector res; - int i; - struct dive *dive; - for_each_dive (i, dive) - res.push_back(dive); + for (auto &d: divelog.dives) + res.push_back(d.get()); return res; } } diff --git a/desktop-widgets/profilewidget.cpp b/desktop-widgets/profilewidget.cpp index ce12b6e55..ab28832a7 100644 --- a/desktop-widgets/profilewidget.cpp +++ b/desktop-widgets/profilewidget.cpp @@ -4,6 +4,8 @@ #include "profile-widget/profilewidget2.h" #include "commands/command.h" #include "core/color.h" +#include "core/event.h" +#include "core/sample.h" #include "core/selection.h" #include "core/settings/qPrefTechnicalDetails.h" #include "core/settings/qPrefPartialPressureGas.h" @@ -165,7 +167,7 @@ void ProfileWidget::setDive(const struct dive *d, int dcNr) { stack->setCurrentIndex(1); // show profile - bool freeDiveMode = get_dive_dc_const(d, dcNr)->divemode == FREEDIVE; + bool freeDiveMode = d->get_dc(dcNr)->divemode == FREEDIVE; ui.profCalcCeiling->setDisabled(freeDiveMode); ui.profCalcCeiling->setDisabled(freeDiveMode); ui.profCalcAllTissues ->setDisabled(freeDiveMode); @@ -207,7 +209,7 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn) // The following is valid because number_of_computers is always at least 1. if (d) - dc = std::min(dc, (int)number_of_computers(current_dive) - 1); + dc = std::min(dc, d->number_of_computers() - 1); // Exit edit mode if the dive changed if (endEditMode) @@ -217,8 +219,8 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn) // or already editing the dive, switch to edit mode. if (d && !editedDive && DivePlannerPointsModel::instance()->currentMode() == DivePlannerPointsModel::NOTHING) { - struct divecomputer *comp = get_dive_dc(d, dc); - if (comp && is_dc_manually_added_dive(comp) && comp->samples && comp->samples <= 50) + struct divecomputer *comp = d->get_dc(dc); + if (comp && is_dc_manually_added_dive(comp) && !comp->samples.empty() && comp->samples.size() <= 50) editDive(); } @@ -251,7 +253,7 @@ void ProfileWidget::rotateDC(int dir) { if (!d) return; - int numDC = number_of_computers(d); + int numDC = d->number_of_computers(); int newDC = (dc + dir) % numDC; if (newDC < 0) newDC += numDC; @@ -296,7 +298,7 @@ void ProfileWidget::cylindersChanged(struct dive *changed, int pos) // If we're editing the current dive we have to update the // cylinders of the edited dive. if (editedDive) { - copy_cylinders(&d->cylinders, &editedDive.get()->cylinders); + editedDive.get()->cylinders = d->cylinders; // TODO: Holy moly that function sends too many signals. Fix it! DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc); } @@ -304,7 +306,6 @@ void ProfileWidget::cylindersChanged(struct dive *changed, int pos) void ProfileWidget::setPlanState(const struct dive *d, int dcNr) { - exitEditMode(); dc = dcNr; view->setPlanState(d, dcNr); setDive(d, dcNr); @@ -324,7 +325,7 @@ void ProfileWidget::unsetProfTissues() void ProfileWidget::editDive() { - editedDive.reset(alloc_dive()); + editedDive = std::make_unique(); copy_dive(d, editedDive.get()); // Work on a copy of the dive DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::EDIT); DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc); @@ -344,8 +345,8 @@ void ProfileWidget::exitEditMode() // Update depths of edited dive static void calcDepth(dive &d, int dcNr) { - d.maxdepth.mm = get_dive_dc(&d, dcNr)->maxdepth.mm = 0; - fixup_dive(&d); + d.maxdepth = d.get_dc(dcNr)->maxdepth = 0_m; + divelog.dives.fixup_dive(d); } // Silly RAII-variable setter class: reset variable when going out of scope. diff --git a/desktop-widgets/profilewidget.h b/desktop-widgets/profilewidget.h index b55e0e6a9..4eb1c20c5 100644 --- a/desktop-widgets/profilewidget.h +++ b/desktop-widgets/profilewidget.h @@ -5,7 +5,6 @@ #define PROFILEWIDGET_H #include "ui_profilewidget.h" -#include "core/owning_ptrs.h" #include "core/subsurface-qt/divelistnotifier.h" #include @@ -15,8 +14,6 @@ class ProfileWidget2; class EmptyView; class QStackedWidget; -extern "C" void free_dive(struct dive *); - class ProfileWidget : public QWidget { Q_OBJECT public: @@ -29,6 +26,7 @@ public: void setEnabledToolbar(bool enabled); void nextDC(); void prevDC(); + void exitEditMode(); dive *d; int dc; private @@ -48,9 +46,8 @@ private: QStackedWidget *stack; void setDive(const struct dive *d, int dcNr); void editDive(); - void exitEditMode(); void rotateDC(int dir); - OwningDivePtr editedDive; + std::unique_ptr editedDive; bool placingCommand; }; diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index ad06e84d9..396c6a4aa 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "core/file.h" @@ -23,6 +22,7 @@ #include "profile-widget/profilewidget2.h" #include "commands/command.h" #include "core/metadata.h" +#include "core/range.h" #include "core/tag.h" void RenumberDialog::buttonClicked(QAbstractButton *button) @@ -30,12 +30,10 @@ void RenumberDialog::buttonClicked(QAbstractButton *button) if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { // we remember a list from dive uuid to a new number QVector> renumberedDives; - int i; int newNr = ui.spinBox->value(); - struct dive *d; - for_each_dive (i, d) { + for (auto &d: divelog.dives) { if (!selectedOnly || d->selected) - renumberedDives.append({ d, newNr++ }); + renumberedDives.append({ d.get(), newNr++ }); } Command::renumberDives(renumberedDives); } @@ -64,7 +62,8 @@ RenumberDialog::RenumberDialog(bool selectedOnlyIn, QWidget *parent) : QDialog(p void SetpointDialog::buttonClicked(QAbstractButton *button) { if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) - Command::addEventSetpointChange(d, dcNr, time, pressure_t { (int)(1000.0 * ui.spinbox->value()) }); + Command::addEventSetpointChange(d, dcNr, time, + pressure_t { .mbar = (int)(1000.0 * ui.spinbox->value()) }); } SetpointDialog::SetpointDialog(struct dive *dIn, int dcNrIn, int seconds) : QDialog(MainWindow::instance()), @@ -278,99 +277,6 @@ QString URLDialog::url() const return ui.urlField->toPlainText(); } -#define COMPONENT_FROM_UI(_component) what->_component = ui._component->isChecked() -#define UI_FROM_COMPONENT(_component) ui._component->setChecked(what->_component) - -DiveComponentSelection::DiveComponentSelection(QWidget *parent, struct dive *target, struct dive_components *_what) : targetDive(target) -{ - ui.setupUi(this); - what = _what; - UI_FROM_COMPONENT(divesite); - UI_FROM_COMPONENT(diveguide); - UI_FROM_COMPONENT(buddy); - UI_FROM_COMPONENT(rating); - UI_FROM_COMPONENT(visibility); - UI_FROM_COMPONENT(notes); - UI_FROM_COMPONENT(suit); - UI_FROM_COMPONENT(tags); - UI_FROM_COMPONENT(cylinders); - UI_FROM_COMPONENT(weights); - UI_FROM_COMPONENT(number); - UI_FROM_COMPONENT(when); - connect(ui.buttonBox, &QDialogButtonBox::clicked, this, &DiveComponentSelection::buttonClicked); - QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_W), this); - connect(close, &QShortcut::activated, this, &DiveComponentSelection::close); - QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this); - connect(quit, &QShortcut::activated, parent, &QWidget::close); -} - -void DiveComponentSelection::buttonClicked(QAbstractButton *button) -{ - if (current_dive && ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { - COMPONENT_FROM_UI(divesite); - COMPONENT_FROM_UI(diveguide); - COMPONENT_FROM_UI(buddy); - COMPONENT_FROM_UI(rating); - COMPONENT_FROM_UI(visibility); - COMPONENT_FROM_UI(notes); - COMPONENT_FROM_UI(suit); - COMPONENT_FROM_UI(tags); - COMPONENT_FROM_UI(cylinders); - COMPONENT_FROM_UI(weights); - COMPONENT_FROM_UI(number); - COMPONENT_FROM_UI(when); - selective_copy_dive(current_dive, targetDive, *what, true); - QClipboard *clipboard = QApplication::clipboard(); - QTextStream text; - QString cliptext; - text.setString(&cliptext); - if (what->divesite && current_dive->dive_site) - text << tr("Dive site: ") << current_dive->dive_site->name << "\n"; - if (what->diveguide) - text << tr("Dive guide: ") << current_dive->diveguide << "\n"; - if (what->buddy) - text << tr("Buddy: ") << current_dive->buddy << "\n"; - if (what->rating) - text << tr("Rating: ") + QString("*").repeated(current_dive->rating) << "\n"; - if (what->visibility) - text << tr("Visibility: ") + QString("*").repeated(current_dive->visibility) << "\n"; - if (what->notes) - text << tr("Notes:\n") << current_dive->notes << "\n"; - if (what->suit) - text << tr("Suit: ") << current_dive->suit << "\n"; - if (what-> tags) { - text << tr("Tags: "); - tag_entry *entry = current_dive->tag_list; - while (entry) { - text << entry->tag->name.c_str() << " "; - entry = entry->next; - } - text << "\n"; - } - if (what->cylinders) { - int cyl; - text << tr("Cylinders:\n"); - for (cyl = 0; cyl < current_dive->cylinders.nr; cyl++) { - if (is_cylinder_used(current_dive, cyl)) - text << get_cylinder(current_dive, cyl)->type.description << " " << gasname(get_cylinder(current_dive, cyl)->gasmix) << "\n"; - } - } - if (what->weights) { - int w; - text << tr("Weights:\n"); - for (w = 0; w < current_dive->weightsystems.nr; w++) { - weightsystem_t ws = current_dive->weightsystems.weightsystems[w]; - text << ws.description << ws.weight.grams / 1000 << "kg\n"; - } - } - if (what->number) - text << tr("Dive number: ") << current_dive->number << "\n"; - if (what->when) - text << tr("Date / time: ") << get_dive_date_string(current_dive->when) << "\n"; - clipboard->setText(cliptext); - } -} - AddFilterPresetDialog::AddFilterPresetDialog(const QString &defaultName, QWidget *parent) { ui.setupUi(this); @@ -382,10 +288,9 @@ AddFilterPresetDialog::AddFilterPresetDialog(const QString &defaultName, QWidget // Create a completer so that the user can easily overwrite existing presets. QStringList presets; - int count = filter_presets_count(); - presets.reserve(count); - for (int i = 0; i < count; ++i) - presets.push_back(QString(filter_preset_name(i).c_str())); + presets.reserve(divelog.filter_presets.size()); + for (auto &filter_preset: divelog.filter_presets) + presets.push_back(QString::fromStdString(filter_preset.name)); QCompleter *completer = new QCompleter(presets, this); completer->setCaseSensitivity(Qt::CaseInsensitive); ui.name->setCompleter(completer); @@ -395,7 +300,7 @@ void AddFilterPresetDialog::nameChanged(const QString &text) { QString trimmed = text.trimmed(); bool isEmpty = trimmed.isEmpty(); - bool exists = !isEmpty && filter_preset_id(trimmed.toStdString()) >= 0; + bool exists = !isEmpty && divelog.filter_presets.preset_id(trimmed.toStdString()) >= 0; ui.duplicateWarning->setVisible(exists); ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!isEmpty); } diff --git a/desktop-widgets/simplewidgets.h b/desktop-widgets/simplewidgets.h index 72011d71d..585c4b3f4 100644 --- a/desktop-widgets/simplewidgets.h +++ b/desktop-widgets/simplewidgets.h @@ -7,7 +7,6 @@ class QAbstractButton; class QNetworkReply; class FilterModelBase; struct dive; -struct dive_components; #include "core/units.h" #include @@ -20,7 +19,6 @@ struct dive_components; #include "ui_shifttimes.h" #include "ui_shiftimagetimes.h" #include "ui_urldialog.h" -#include "ui_divecomponentselection.h" #include "ui_listfilter.h" #include "ui_addfilterpreset.h" @@ -102,20 +100,6 @@ private: Ui::URLDialog ui; }; -class DiveComponentSelection : public QDialog { - Q_OBJECT -public: - explicit DiveComponentSelection(QWidget *parent, struct dive *target, struct dive_components *_what); -private -slots: - void buttonClicked(QAbstractButton *button); - -private: - Ui::DiveComponentSelectionDialog ui; - struct dive *targetDive; - struct dive_components *what; -}; - class AddFilterPresetDialog : public QDialog { Q_OBJECT public: diff --git a/desktop-widgets/subsurfacewebservices.cpp b/desktop-widgets/subsurfacewebservices.cpp index 1f911c2d6..7364dbd59 100644 --- a/desktop-widgets/subsurfacewebservices.cpp +++ b/desktop-widgets/subsurfacewebservices.cpp @@ -7,7 +7,7 @@ #include "desktop-widgets/mainwindow.h" #include "commands/command.h" #include "core/device.h" -#include "core/divelist.h" // For IMPORT_MERGE_ALL_TRIPS +#include "core/divelist.h" #include "core/divelog.h" #include "core/errorhelper.h" #include "core/file.h" @@ -458,7 +458,7 @@ void DivelogsDeWebServices::buttonClicked(QAbstractButton *button) /* parse file and import dives */ struct divelog log; parse_file(QFile::encodeName(zipFile.fileName()), &log); - Command::importDives(&log, IMPORT_MERGE_ALL_TRIPS, QStringLiteral("divelogs.de")); + Command::importDives(&log, import_flags::merge_all_trips, QStringLiteral("divelogs.de")); /* store last entered user/pass in config */ qPrefCloudStorage::set_divelogde_user(ui.userID->text()); diff --git a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp index b0453f110..6276cb49b 100644 --- a/desktop-widgets/tab-widgets/TabDiveEquipment.cpp +++ b/desktop-widgets/tab-widgets/TabDiveEquipment.cpp @@ -118,7 +118,7 @@ void TabDiveEquipment::divesChanged(const QVector &dives, DiveField fiel return; if (field.suit) - ui.suit->setText(QString(parent.currentDive->suit)); + ui.suit->setText(QString::fromStdString(parent.currentDive->suit)); } void TabDiveEquipment::toggleTriggeredColumn() @@ -138,15 +138,15 @@ void TabDiveEquipment::toggleTriggeredColumn() void TabDiveEquipment::updateData(const std::vector &, dive *currentDive, int currentDC) { - divecomputer *dc = get_dive_dc(currentDive, currentDC); + divecomputer *dc = currentDive->get_dc(currentDC); cylindersModel->updateDive(currentDive, currentDC); weightModel->updateDive(currentDive); sensorDelegate.setCurrentDC(dc); - tankUseDelegate.setCurrentDC(dc); + tankUseDelegate.setDiveDc(*currentDive, currentDC); - if (currentDive && currentDive->suit) - ui.suit->setText(QString(currentDive->suit)); + if (currentDive && !currentDive->suit.empty()) + ui.suit->setText(QString::fromStdString(currentDive->suit)); else ui.suit->clear(); } diff --git a/desktop-widgets/tab-widgets/TabDiveExtraInfo.cpp b/desktop-widgets/tab-widgets/TabDiveExtraInfo.cpp index edc8455d8..d2c866d22 100644 --- a/desktop-widgets/tab-widgets/TabDiveExtraInfo.cpp +++ b/desktop-widgets/tab-widgets/TabDiveExtraInfo.cpp @@ -21,9 +21,8 @@ TabDiveExtraInfo::~TabDiveExtraInfo() void TabDiveExtraInfo::updateData(const std::vector &, dive *currentDive, int currentDC) { - const struct divecomputer *currentdc = get_dive_dc(currentDive, currentDC); - if (currentdc) - extraDataModel->updateDiveComputer(currentdc); + if (currentDive) + extraDataModel->updateDiveComputer(currentDive->get_dc(currentDC)); ui->extraData->setVisible(false); // This will cause the resize to include rows outside the current viewport ui->extraData->resizeColumnsToContents(); diff --git a/desktop-widgets/tab-widgets/TabDiveInformation.cpp b/desktop-widgets/tab-widgets/TabDiveInformation.cpp index 1b34eb242..1662dbfa7 100644 --- a/desktop-widgets/tab-widgets/TabDiveInformation.cpp +++ b/desktop-widgets/tab-widgets/TabDiveInformation.cpp @@ -124,38 +124,37 @@ void TabDiveInformation::updateProfile() ui->maximumDepthText->setText(get_depth_string(currentDive->maxdepth, true)); ui->averageDepthText->setText(get_depth_string(currentDive->meandepth, true)); - volume_t *gases = get_gas_used(currentDive); + std::vector gases = get_gas_used(currentDive); QString volumes; - std::vector mean(currentDive->cylinders.nr), duration(currentDive->cylinders.nr); + std::vector mean(currentDive->cylinders.size()), duration(currentDive->cylinders.size()); struct divecomputer *currentdc = parent.getCurrentDC(); - if (currentdc && currentDive->cylinders.nr >= 0) + if (currentdc && !currentDive->cylinders.empty()) per_cylinder_mean_depth(currentDive, currentdc, mean.data(), duration.data()); volume_t sac; QString gaslist, SACs, separator; - for (int i = 0; i < currentDive->cylinders.nr; i++) { - if (!is_cylinder_used(currentDive, i)) + for (size_t i = 0; i < currentDive->cylinders.size(); i++) { + if (!currentDive->is_cylinder_used(i)) continue; gaslist.append(separator); volumes.append(separator); SACs.append(separator); separator = "\n"; - gaslist.append(gasname(get_cylinder(currentDive, i)->gasmix)); + gaslist.append(QString::fromStdString(currentDive->get_cylinder(i)->gasmix.name())); if (!gases[i].mliter) continue; volumes.append(get_volume_string(gases[i], true)); if (duration[i]) { - sac.mliter = lrint(gases[i].mliter / (depth_to_atm(mean[i], currentDive) * duration[i] / 60)); + sac.mliter = lrint(gases[i].mliter / (currentDive->depth_to_atm(mean[i]) * duration[i] / 60)); SACs.append(get_volume_string(sac, true).append(tr("/min"))); } } - free(gases); ui->gasUsedText->setText(volumes); ui->oxygenHeliumText->setText(gaslist); ui->diveTimeText->setText(get_dive_duration_string(currentDive->duration.seconds, tr("h"), tr("min"), tr("sec"), - " ", currentDive->dc.divemode == FREEDIVE)); + " ", currentDive->dcs[0].divemode == FREEDIVE)); - ui->sacText->setText(currentDive->cylinders.nr > 0 && mean[0] && currentDive->dc.divemode != CCR ? std::move(SACs) : QString()); + ui->sacText->setText(!currentDive->cylinders.empty() && mean[0] && currentDive->dcs[0].divemode != CCR ? std::move(SACs) : QString()); if (currentDive->surface_pressure.mbar == 0) { ui->atmPressVal->clear(); // If no atm pressure for dive then clear text box @@ -168,7 +167,7 @@ void TabDiveInformation::updateProfile() // Update fields that depend on start of dive void TabDiveInformation::updateWhen() { - timestamp_t surface_interval = get_surface_interval(parent.currentDive->when); + timestamp_t surface_interval = divelog.dives.get_surface_interval(parent.currentDive->when); if (surface_interval >= 0) ui->surfaceIntervalText->setText(get_dive_surfint_string(surface_interval, tr("d"), tr("h"), tr("min"))); else @@ -222,7 +221,7 @@ void TabDiveInformation::updateData(const std::vector &, dive *currentDi ui->airtemp->setText(get_temperature_string(currentDive->airtemp, true)); ui->atmPressType->setItemText(1, get_depth_unit()); // Check for changes in depth unit (imperial/metric) setIndexNoSignal(ui->atmPressType, 0); // Set the atmospheric pressure combo box to mbar - salinity_value = get_dive_salinity(currentDive); + salinity_value = currentDive->get_salinity(); if (salinity_value) { // Set water type indicator (EN13319 = 1.020 g/l) setIndexNoSignal(ui->waterTypeCombo, updateSalinityComboIndex(salinity_value)); ui->waterTypeText->setText(get_water_type_string(salinity_value)); @@ -260,9 +259,8 @@ void TabDiveInformation::updateUi(QString titleColor) } // From the index of the water type combo box, set the dive->salinity to an appropriate value -void TabDiveInformation::on_waterTypeCombo_activated(int index) +void TabDiveInformation::on_waterTypeCombo_activated(int) { - Q_UNUSED(index) int combobox_salinity = 0; int dc_salinity = parent.currentDive->salinity; switch(ui->waterTypeCombo->currentIndex()) { @@ -411,9 +409,8 @@ void TabDiveInformation::on_watertemp_editingFinished() divesEdited(Command::editWaterTemp(parseTemperatureToMkelvin(ui->watertemp->text()), false)); } -void TabDiveInformation::on_atmPressType_currentIndexChanged(int index) +void TabDiveInformation::on_atmPressType_currentIndexChanged(int) { - Q_UNUSED(index) updateTextBox(COMBO_CHANGED); } @@ -424,7 +421,7 @@ void TabDiveInformation::on_atmPressVal_editingFinished() void TabDiveInformation::updateTextBox(int event) // Either the text box has been edited or the pressure type has changed. { // Either way this gets a numeric value and puts it on the text box atmPressVal, - pressure_t atmpress = { 0 }; // then stores it in dive->surface_pressure.The undo stack for the text box content is + pressure_t atmpress; // then stores it in dive->surface_pressure.The undo stack for the text box content is double altitudeVal; // maintained even though two independent events trigger saving the text box contents. dive *currentDive = parent.currentDive; if (currentDive) { @@ -442,28 +439,28 @@ void TabDiveInformation::updateTextBox(int event) // Either the text box has bee altitudeVal = feet_to_mm(altitudeVal); // imperial: convert altitude from feet to mm else altitudeVal = altitudeVal * 1000; // metric: convert altitude from meters to mm - atmpress.mbar = altitude_to_pressure((int32_t) altitudeVal); // convert altitude (mm) to pressure (mbar) + atmpress = altitude_to_pressure((int32_t) altitudeVal); // convert altitude (mm) to pressure (mbar) ui->atmPressVal->setText(QString::number(atmpress.mbar)); setIndexNoSignal(ui->atmPressType, 0); // reset combobox to mbar } else { // i.e. event == COMBO_CHANGED, that is, "m" or "ft" was selected from combobox // Show estimated altitude bool ok; double convertVal = 0.0010; // Metric conversion fro mm to m - int pressure_as_integer = ui->atmPressVal->text().toInt(&ok,10); + pressure_t pressure = { .mbar = ui->atmPressVal->text().toInt(&ok,10) }; if (ok && ui->atmPressVal->text().length()) { // Show existing atm press as an altitude: if (prefs.units.length == units::FEET) // For imperial units convertVal = mm_to_feet(1); // convert from mm to ft - ui->atmPressVal->setText(QString::number((int)(pressure_to_altitude(pressure_as_integer) * convertVal))); + ui->atmPressVal->setText(QString::number((int)(pressure_to_altitude(pressure) * convertVal))); } } break; case 2: // i.e. event = COMBO_CHANGED, that is, the option "Use dc" was selected from combobox - atmpress = calculate_surface_pressure(currentDive); // re-calculate air pressure from dc data + atmpress = currentDive->calculate_surface_pressure(); // re-calculate air pressure from dc data ui->atmPressVal->setText(QString::number(atmpress.mbar)); // display it in text box setIndexNoSignal(ui->atmPressType, 0); // reset combobox to mbar break; default: - atmpress.mbar = 1013; // This line should never execute + atmpress = 1_atm; // This line should never execute break; } if (atmpress.mbar) diff --git a/desktop-widgets/tab-widgets/TabDiveNotes.cpp b/desktop-widgets/tab-widgets/TabDiveNotes.cpp index 8135cd8af..de34a3856 100644 --- a/desktop-widgets/tab-widgets/TabDiveNotes.cpp +++ b/desktop-widgets/tab-widgets/TabDiveNotes.cpp @@ -85,8 +85,8 @@ TabDiveNotes::TabDiveNotes(MainTab *parent) : TabBase(parent), void TabDiveNotes::updateDateTimeFields() { - ui.dateEdit->setDisplayFormat(prefs.date_format); - ui.timeEdit->setDisplayFormat(prefs.time_format); + ui.dateEdit->setDisplayFormat(QString::fromStdString(prefs.date_format)); + ui.timeEdit->setDisplayFormat(QString::fromStdString(prefs.time_format)); } void TabDiveNotes::closeWarning() @@ -118,11 +118,11 @@ void TabDiveNotes::divesChanged(const QVector &dives, DiveField field) if (field.divesite) updateDiveSite(currentDive); if (field.tags) - ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tag_list))); + ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tags))); if (field.buddy) - ui.buddy->setText(currentDive->buddy); + ui.buddy->setText(QString::fromStdString(currentDive->buddy)); if (field.diveguide) - ui.diveguide->setText(currentDive->diveguide); + ui.diveguide->setText(QString::fromStdString(currentDive->diveguide)); } void TabDiveNotes::diveSiteEdited(dive_site *ds, int) @@ -140,9 +140,9 @@ void TabDiveNotes::tripChanged(dive_trip *trip, TripField field) return; if (field.notes) - ui.notes->setText(currentTrip->notes); + ui.notes->setText(QString::fromStdString(currentTrip->notes)); if (field.location) - ui.diveTripLocation->setText(currentTrip->location); + ui.diveTripLocation->setText(QString::fromStdString(currentTrip->location)); } static bool isHtml(const QString &s) @@ -152,7 +152,7 @@ static bool isHtml(const QString &s) void TabDiveNotes::updateNotes(const struct dive *d) { - QString tmp(d->notes); + QString tmp = QString::fromStdString(d->notes); if (isHtml(tmp)) { ui.notes->setHtml(tmp); } else { @@ -169,7 +169,7 @@ void TabDiveNotes::updateDateTime(const struct dive *d) void TabDiveNotes::updateTripDate(const struct dive_trip *t) { - QDateTime localTime = timestampToDateTime(trip_date(t)); + QDateTime localTime = timestampToDateTime(t->date()); ui.dateEdit->setDate(localTime.date()); } @@ -178,7 +178,7 @@ void TabDiveNotes::updateDiveSite(struct dive *d) struct dive_site *ds = d->dive_site; ui.location->setCurrentDiveSite(d); if (ds) { - ui.locationTags->setText(constructLocationTags(&ds->taxonomy, true)); + ui.locationTags->setText(QString::fromStdString(taxonomy_get_location_tags(ds->taxonomy, true))); if (ui.locationTags->text().isEmpty() && has_location(&ds->location)) ui.locationTags->setText(printGPSCoords(&ds->location)); @@ -220,13 +220,13 @@ void TabDiveNotes::updateData(const std::vector &, dive *currentDive, in ui.editDiveSiteButton->hide(); // rename the remaining fields and fill data from selected trip ui.LocationLabel->setText(tr("Trip location")); - ui.diveTripLocation->setText(currentTrip->location); + ui.diveTripLocation->setText(QString::fromStdString(currentTrip->location)); updateTripDate(currentTrip); ui.locationTags->clear(); //TODO: Fix this. - //ui.location->setText(currentTrip->location); + //ui.location->setText(QString::fromStdSTring(currentTrip->location)); ui.NotesLabel->setText(tr("Trip notes")); - ui.notes->setText(currentTrip->notes); + ui.notes->setText(QString::fromStdString(currentTrip->notes)); ui.depth->setVisible(false); ui.depthLabel->setVisible(false); ui.duration->setVisible(false); @@ -253,8 +253,8 @@ void TabDiveNotes::updateData(const std::vector &, dive *currentDive, in // reset labels in case we last displayed trip notes ui.LocationLabel->setText(tr("Location")); ui.NotesLabel->setText(tr("Notes")); - ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tag_list))); - bool isManual = is_dc_manually_added_dive(¤tDive->dc); + ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tags))); + bool isManual = is_dc_manually_added_dive(¤tDive->dcs[0]); ui.depth->setVisible(isManual); ui.depthLabel->setVisible(isManual); ui.duration->setVisible(isManual); @@ -263,8 +263,8 @@ void TabDiveNotes::updateData(const std::vector &, dive *currentDive, in updateNotes(currentDive); updateDiveSite(currentDive); updateDateTime(currentDive); - ui.diveguide->setText(currentDive->diveguide); - ui.buddy->setText(currentDive->buddy); + ui.diveguide->setText(QString::fromStdString(currentDive->diveguide)); + ui.buddy->setText(QString::fromStdString(currentDive->buddy)); } ui.duration->setText(render_seconds_to_string(currentDive->duration.seconds)); ui.depth->setText(get_depth_string(currentDive->maxdepth, true)); diff --git a/desktop-widgets/tab-widgets/TabDivePhotos.cpp b/desktop-widgets/tab-widgets/TabDivePhotos.cpp index 38ec05e69..fb4955889 100644 --- a/desktop-widgets/tab-widgets/TabDivePhotos.cpp +++ b/desktop-widgets/tab-widgets/TabDivePhotos.cpp @@ -12,7 +12,6 @@ #include #include #include -#include "core/membuffer.h" #include "core/save-profiledata.h" #include "core/selection.h" @@ -131,11 +130,9 @@ void TabDivePhotos::saveSubtitles() // Only videos have non-zero duration if (!duration) continue; - struct membufferpp b; - save_subtitles_buffer(&b, parent.currentDive, offset, duration); - const char *data = mb_cstring(&b); + std::string buffer = save_subtitles_buffer(parent.currentDive, offset, duration); subtitlefile.open(QIODevice::WriteOnly); - subtitlefile.write(data, strlen(data)); + subtitlefile.write(buffer.c_str(), buffer.size()); subtitlefile.close(); } } diff --git a/desktop-widgets/tab-widgets/TabDiveStatistics.cpp b/desktop-widgets/tab-widgets/TabDiveStatistics.cpp index c52701328..08f38357f 100644 --- a/desktop-widgets/tab-widgets/TabDiveStatistics.cpp +++ b/desktop-widgets/tab-widgets/TabDiveStatistics.cpp @@ -72,8 +72,7 @@ void TabDiveStatistics::cylinderChanged(dive *d) void TabDiveStatistics::updateData(const std::vector &, dive *currentDive, int) { - stats_t stats_selection; - calculate_stats_selected(&stats_selection); + stats_t stats_selection = calculate_stats_selected(); clear(); if (amount_selected > 1 && stats_selection.selection_size >= 1) { ui->depthLimits->setMaximum(get_depth_string(stats_selection.max_depth, true)); @@ -109,7 +108,7 @@ void TabDiveStatistics::updateData(const std::vector &, dive *currentDiv } - bool is_freedive = currentDive && currentDive->dc.divemode == FREEDIVE; + bool is_freedive = currentDive && currentDive->dcs[0].divemode == FREEDIVE; ui->divesAllText->setText(QString::number(stats_selection.selection_size)); ui->totalTimeAllText->setText(get_dive_duration_string(stats_selection.total_time.seconds, tr("h"), tr("min"), tr("sec"), " ", is_freedive)); @@ -135,13 +134,12 @@ void TabDiveStatistics::updateData(const std::vector &, dive *currentDiv vol.mliter = gasPair.second; gasUsedString.append(gasPair.first).append(": ").append(get_volume_string(vol, true)).append("\n"); } - volume_t o2_tot = {}, he_tot = {}; - selected_dives_gas_parts(&o2_tot, &he_tot); + auto [o2_tot, he_tot] = selected_dives_gas_parts(); /* No need to show the gas mixing information if diving - * with pure air, and only display the he / O2 part when - * it is used. - */ + * with pure air, and only display the he / O2 part when + * it is used. + */ if (he_tot.mliter || o2_tot.mliter) { gasUsedString.append(tr("These gases could be\nmixed from Air and using:\n")); if (he_tot.mliter) { diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 818c41638..78f74fa87 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -188,5 +188,5 @@ bool MainTab::includesCurrentDive(const QVector &dives) const divecomputer *MainTab::getCurrentDC() const { - return get_dive_dc(currentDive, currentDC); + return currentDive ? currentDive->get_dc(currentDC) : nullptr; } diff --git a/desktop-widgets/templatelayout.cpp b/desktop-widgets/templatelayout.cpp index 5bae08e3b..51460ecea 100644 --- a/desktop-widgets/templatelayout.cpp +++ b/desktop-widgets/templatelayout.cpp @@ -123,10 +123,9 @@ QString TemplateLayout::generateStatistics() State state; int i = 0; - stats_summary_auto_free stats; - calculate_stats_summary(&stats, false); - while (stats.stats_yearly != NULL && stats.stats_yearly[i].period) { - state.years.append(&stats.stats_yearly[i]); + stats_summary stats = calculate_stats_summary(false); + for (auto &s: stats.stats_yearly) { + state.years.append(&s); i++; } @@ -304,9 +303,9 @@ static int findEnd(const QList &tokenList, int from, int to, token_t star static std::vector cylinderList(const dive *d) { std::vector res; - res.reserve(d->cylinders.nr); - for (int i = 0; i < d->cylinders.nr; ++i) - res.push_back(&d->cylinders.cylinders[i]); + res.reserve(d->cylinders.size()); + for (auto &cyl: d->cylinders) + res.push_back(&cyl); return res; } @@ -482,7 +481,7 @@ QVariant TemplateLayout::getValue(QString list, QString property, const State &s return QVariant(); const cylinder_t *cylinder = *state.currentCylinderObject; if (property == "description") { - return cylinder->type.description; + return QString::fromStdString(cylinder->type.description); } else if (property == "size") { return get_volume_string(cylinder->type.size, true); } else if (property == "workingPressure") { @@ -527,7 +526,7 @@ QVariant TemplateLayout::getValue(QString list, QString property, const State &s } else if (property == "timestamp") { return QVariant::fromValue(d->when); } else if (property == "location") { - return get_dive_location(d); + return QString::fromStdString(d->get_location()); } else if (property == "gps") { return formatDiveGPS(d); } else if (property == "gps_decimal") { @@ -535,17 +534,17 @@ QVariant TemplateLayout::getValue(QString list, QString property, const State &s } else if (property == "duration") { return formatDiveDuration(d); } else if (property == "noDive") { - return d->duration.seconds == 0 && d->dc.duration.seconds == 0; + return d->duration.seconds == 0 && d->dcs[0].duration.seconds == 0; } else if (property == "depth") { - return get_depth_string(d->dc.maxdepth.mm, true, true); + return get_depth_string(d->dcs[0].maxdepth.mm, true, true); } else if (property == "meandepth") { - return get_depth_string(d->dc.meandepth.mm, true, true); + return get_depth_string(d->dcs[0].meandepth.mm, true, true); } else if (property == "divemaster") { - return d->diveguide; + return QString::fromStdString(d->diveguide); } else if (property == "diveguide") { - return d->diveguide; + return QString::fromStdString(d->diveguide); } else if (property == "buddy") { - return d->buddy; + return QString::fromStdString(d->buddy); } else if (property == "airTemp") { return get_temperature_string(d->airtemp, true); } else if (property == "waterTemp") { @@ -553,7 +552,7 @@ QVariant TemplateLayout::getValue(QString list, QString property, const State &s } else if (property == "notes") { return formatNotes(d); } else if (property == "tags") { - return QString::fromStdString(taglist_get_tagstring(d->tag_list)); + return QString::fromStdString(taglist_get_tagstring(d->tags)); } else if (property == "gas") { return formatGas(d); } else if (property == "sac") { @@ -563,9 +562,9 @@ QVariant TemplateLayout::getValue(QString list, QString property, const State &s } else if (property == "weights") { return formatWeights(d); } else if (property == "singleWeight") { - return d->weightsystems.nr <= 1; + return d->weightsystems.size() <= 1; } else if (property == "suit") { - return d->suit; + return QString::fromStdString(d->suit); } else if (property == "cylinderList") { return formatFullCylinderList(); } else if (property == "cylinders") { diff --git a/desktop-widgets/tripselectiondialog.cpp b/desktop-widgets/tripselectiondialog.cpp index e4eef72aa..ab98fe366 100644 --- a/desktop-widgets/tripselectiondialog.cpp +++ b/desktop-widgets/tripselectiondialog.cpp @@ -18,9 +18,9 @@ TripSelectionDialog::TripSelectionDialog(QWidget *parent) : QDialog(parent) // We could use a model, but it seems barely worth the hassle. QStringList list; - list.reserve(divelog.trips->nr); - for (int i = 0; i < divelog.trips->nr; ++i) - list.push_back(formatTripTitleWithDives(divelog.trips->trips[i])); + list.reserve(divelog.trips.size()); + for (auto &trip: divelog.trips) + list.push_back(formatTripTitleWithDives(*trip)); ui.trips->addItems(list); } @@ -37,9 +37,9 @@ dive_trip *TripSelectionDialog::selectedTrip() const if (rows.size() != 1) return nullptr; int idx = rows[0].row(); - if (idx < 0 || idx >= divelog.trips->nr) + if (idx < 0 || static_cast(idx) >= divelog.trips.size()) return nullptr; - return divelog.trips->trips[idx]; + return divelog.trips[idx].get(); } dive_trip *TripSelectionDialog::getTrip() diff --git a/dives/TestDiveSeabearNewFormat.xml b/dives/TestDiveSeabearNewFormat.xml index 93f6e07d6..b3e529426 100644 --- a/dives/TestDiveSeabearNewFormat.xml +++ b/dives/TestDiveSeabearNewFormat.xml @@ -834,7 +834,7 @@ - + @@ -1974,7 +1974,7 @@ - + @@ -3113,7 +3113,7 @@ - + diff --git a/export-html.cpp b/export-html.cpp index 724745a40..4179e1a1b 100644 --- a/export-html.cpp +++ b/export-html.cpp @@ -23,7 +23,7 @@ int main(int argc, char **argv) { QApplication *application = new QApplication(argc, argv); git_libgit2_init(); - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; init_qt_late(); QCommandLineParser parser; diff --git a/libdivecomputer b/libdivecomputer index 73e3b1944..a2a5bb53d 160000 --- a/libdivecomputer +++ b/libdivecomputer @@ -1 +1 @@ -Subproject commit 73e3b194463db6750110ff343d5c2464b82ad5da +Subproject commit a2a5bb53d670dc520bf8035b18681dfdd9b18bfa diff --git a/map-widget/qmlmapwidgethelper.cpp b/map-widget/qmlmapwidgethelper.cpp index e5c4ed9b5..f957fac49 100644 --- a/map-widget/qmlmapwidgethelper.cpp +++ b/map-widget/qmlmapwidgethelper.cpp @@ -5,9 +5,12 @@ #include #include "qmlmapwidgethelper.h" +#include "core/divefilter.h" +#include "core/divelist.h" +#include "core/divelog.h" #include "core/divesite.h" #include "core/qthelper.h" -#include "core/divefilter.h" +#include "core/range.h" #include "qt-models/maplocationmodel.h" #include "qt-models/divelocationmodel.h" #ifndef SUBSURFACE_MOBILE @@ -27,14 +30,14 @@ MapWidgetHelper::MapWidgetHelper(QObject *parent) : QObject(parent) QGeoCoordinate MapWidgetHelper::getCoordinates(struct dive_site *ds) { - if (!dive_site_has_gps_location(ds)) + if (!ds || !ds->has_gps_location()) return QGeoCoordinate(0.0, 0.0); return QGeoCoordinate(ds->location.lat.udeg * 0.000001, ds->location.lon.udeg * 0.000001); } void MapWidgetHelper::centerOnDiveSite(struct dive_site *ds) { - if (!dive_site_has_gps_location(ds)) { + if (!ds || !ds->has_gps_location()) { // dive site with no GPS m_mapLocationModel->setSelected(ds); QMetaObject::invokeMethod(m_map, "deselectMapLocation"); @@ -46,18 +49,18 @@ void MapWidgetHelper::centerOnDiveSite(struct dive_site *ds) } } -void MapWidgetHelper::setSelected(const QVector &divesites) +void MapWidgetHelper::setSelected(const std::vector divesites) { - m_mapLocationModel->setSelected(divesites); + m_mapLocationModel->setSelected(std::move(divesites)); m_mapLocationModel->selectionChanged(); updateEditMode(); } void MapWidgetHelper::centerOnSelectedDiveSite() { - QVector selDS = m_mapLocationModel->selectedDs(); + std::vector selDS = m_mapLocationModel->selectedDs(); - if (selDS.isEmpty()) { + if (selDS.empty()) { // no selected dives with GPS coordinates QMetaObject::invokeMethod(m_map, "deselectMapLocation"); return; @@ -122,20 +125,18 @@ void MapWidgetHelper::reloadMapLocations() void MapWidgetHelper::selectedLocationChanged(struct dive_site *ds_in) { - int idx; - struct dive *dive; QList selectedDiveIds; if (!ds_in) return; - MapLocation *location = m_mapLocationModel->getMapLocation(ds_in); + const MapLocation *location = m_mapLocationModel->getMapLocation(ds_in); if (!location) return; QGeoCoordinate locationCoord = location->coordinate; - for_each_dive (idx, dive) { - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!dive_site_has_gps_location(ds)) + for (auto [idx, dive]: enumerated_range(divelog.dives)) { + struct dive_site *ds = dive->dive_site; + if (!ds || !ds->has_gps_location()) continue; #ifndef SUBSURFACE_MOBILE const qreal latitude = ds->location.lat.udeg * 0.000001; @@ -160,12 +161,10 @@ void MapWidgetHelper::selectedLocationChanged(struct dive_site *ds_in) void MapWidgetHelper::selectVisibleLocations() { - int idx; - struct dive *dive; QList selectedDiveIds; - for_each_dive (idx, dive) { - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!dive_site_has_gps_location(ds)) + for (auto [idx, dive]: enumerated_range(divelog.dives)) { + struct dive_site *ds = dive->dive_site; + if (!ds || ds->has_gps_location()) continue; const qreal latitude = ds->location.lat.udeg * 0.000001; const qreal longitude = ds->location.lon.udeg * 0.000001; @@ -251,7 +250,7 @@ bool MapWidgetHelper::editMode() const QString MapWidgetHelper::pluginObject() { QString lang = getUiLanguage().replace('_', '-'); - QString cacheFolder = QString(system_default_directory()).append("/googlemaps").replace("\\", "/"); + QString cacheFolder = QString::fromStdString(system_default_directory() + "/googlemaps").replace("\\", "/"); return QStringLiteral("import QtQuick 2.0;" "import QtLocation 5.3;" "Plugin {" diff --git a/map-widget/qmlmapwidgethelper.h b/map-widget/qmlmapwidgethelper.h index 9aa1d8723..5a92d4daa 100644 --- a/map-widget/qmlmapwidgethelper.h +++ b/map-widget/qmlmapwidgethelper.h @@ -36,7 +36,7 @@ public: Q_INVOKABLE void updateCurrentDiveSiteCoordinatesFromMap(struct dive_site *ds, QGeoCoordinate coord); Q_INVOKABLE void selectVisibleLocations(); Q_INVOKABLE void selectedLocationChanged(struct dive_site *ds); - void setSelected(const QVector &divesites); + void setSelected(const std::vector divesites); QString pluginObject(); bool editMode() const; diff --git a/mobile-widgets/qml/CopySettings.qml b/mobile-widgets/qml/CopySettings.qml index 17bf8dbf0..910803e36 100644 --- a/mobile-widgets/qml/CopySettings.qml +++ b/mobile-widgets/qml/CopySettings.qml @@ -38,11 +38,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleDiveSite(false) + checked: manager.pasteDiveSite Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleDiveSite(true) - } } Controls.Label { text: qsTr("Notes") @@ -50,11 +47,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleNotes(false) + checked: manager.pasteNotes Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleNotes(true) - } } Controls.Label { text: qsTr("Dive guide") @@ -62,11 +56,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleDiveGuide(false) + checked: manager.pasteDiveGuide Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleDiveGuide(true) - } } Controls.Label { text: qsTr("Buddy") @@ -74,11 +65,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleBuddy(false) + checked: manager.pasteBuddy Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleBuddy(true) - } } Controls.Label { text: qsTr("Suit") @@ -86,11 +74,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleSuit(false) + checked: manager.pasteSuit Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleSuit(true) - } } Controls.Label { text: qsTr("Rating") @@ -98,11 +83,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleRating(false) + checked: manager.pasteRating Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleRating(true) - } } Controls.Label { text: qsTr("Visibility") @@ -110,11 +92,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleVisibility(false) + checked: manager.pasteVisibility Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleVisibility(true) - } } Controls.Label { text: qsTr("Tags") @@ -122,11 +101,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleTags(false) + checked: manager.pasteTags Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleTags(true) - } } Controls.Label { text: qsTr("Cylinders") @@ -134,11 +110,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleCylinders(false) + checked: manager.pasteCylinders Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleCylinders(true) - } } Controls.Label { text: qsTr("Weights") @@ -146,11 +119,8 @@ Kirigami.ScrollablePage { Layout.preferredWidth: gridWidth * 0.75 } SsrfSwitch { - checked: manager.toggleWeights(false) + checked: manager.pasteWeights Layout.preferredWidth: gridWidth * 0.25 - onClicked: { - manager.toggleWeights(true) - } } } diff --git a/mobile-widgets/qml/DiveDetails.qml b/mobile-widgets/qml/DiveDetails.qml index a9a7c4e45..bf495be8d 100644 --- a/mobile-widgets/qml/DiveDetails.qml +++ b/mobile-widgets/qml/DiveDetails.qml @@ -399,6 +399,8 @@ Kirigami.Page { delegate: Flickable { id: internalScrollView width: diveDetailsListView.width + height: diveDetailsListView.height + contentHeight: diveDetails.height boundsBehavior: Flickable.StopAtBounds property var modelData: model DiveDetailsView { @@ -423,6 +425,7 @@ Kirigami.Page { anchors.fill: parent leftMargin: Kirigami.Units.smallSpacing rightMargin: Kirigami.Units.smallSpacing + contentHeight: detailsEdit.height // start invisible and scaled down, to get the transition // off to the right start visible: false diff --git a/mobile-widgets/qmlinterface.h b/mobile-widgets/qmlinterface.h index ed09b2fb7..23b895eb4 100644 --- a/mobile-widgets/qmlinterface.h +++ b/mobile-widgets/qmlinterface.h @@ -235,7 +235,10 @@ public slots: void set_ascrate75(int value) { DivePlannerPointsModel::instance()->setAscrate75Display(value); } void set_descrate(int value) { DivePlannerPointsModel::instance()->setDescrateDisplay(value); } - void set_dive_mode(DIVE_MODE value) { DivePlannerPointsModel::instance()->setRebreatherMode((int)value); } + void set_dive_mode(DIVE_MODE value) { + //TODO: Actually change the dive mode in the dive here - this will need untangling to enable access to the dive + DivePlannerPointsModel::instance()->cylindersChanged(); + } void set_planner_deco_mode(DECO_MODE value) { PlannerShared::set_planner_deco_mode((deco_mode)value); } void set_reserve_gas(int value) { PlannerShared::set_reserve_gas(value); } void set_safetystop(bool value) { DivePlannerPointsModel::instance()->setSafetyStop(value); } diff --git a/mobile-widgets/qmlmanager.cpp b/mobile-widgets/qmlmanager.cpp index 1476b6b75..b7f7f597f 100644 --- a/mobile-widgets/qmlmanager.cpp +++ b/mobile-widgets/qmlmanager.cpp @@ -38,8 +38,8 @@ #include "core/subsurface-string.h" #include "core/string-format.h" #include "core/pref.h" +#include "core/sample.h" #include "core/selection.h" -#include "core/ssrf.h" #include "core/save-profiledata.h" #include "core/settings/qPrefLog.h" #include "core/settings/qPrefTechnicalDetails.h" @@ -66,22 +66,22 @@ bool noCloudToCloud = false; #define RED_FONT QLatin1String("") #define END_FONT QLatin1String("") -extern "C" void showErrorFromC(char *buf) +static void showError(std::string s) { - QString error(buf); - free(buf); + QString error = QString::fromStdString(s); // By using invokeMethod with Qt:AutoConnection, the error string is safely // transported across thread boundaries, if not called from the UI thread. QMetaObject::invokeMethod(QMLManager::instance(), "registerError", Qt::AutoConnection, Q_ARG(QString, error)); } // this gets called from libdivecomputer -static void progressCallback(const char *text) +static void progressCallback(const std::string &text) { QMLManager *self = QMLManager::instance(); if (self) { - self->appendTextToLog(QString(text)); - self->setProgressMessage(QString(text)); + QString s = QString::fromStdString(text); + self->appendTextToLog(s); + self->setProgressMessage(s); } } @@ -124,7 +124,7 @@ static void showProgress(QString msg) } // show the git progress in the passive notification area -extern "C" int gitProgressCB(const char *text) +int gitProgressCB(const char *text) { // regular users, during regular operation, likely really don't // care at all about the git progress @@ -270,7 +270,7 @@ QMLManager::QMLManager() : appendTextToLog("No writeable location found, in-memory log only and no libdivecomputer log"); } #endif - set_error_cb(&showErrorFromC); + set_error_cb(&showError); uiNotificationCallback = showProgress; appendTextToLog("Starting " + getUserAgent()); appendTextToLog(QStringLiteral("built with libdivecomputer v%1").arg(dc_version(NULL))); @@ -279,7 +279,7 @@ QMLManager::QMLManager() : git_libgit2_version(&git_maj, &git_min, &git_rev); appendTextToLog(QStringLiteral("built with libgit2 %1.%2.%3").arg(git_maj).arg(git_min).arg(git_rev)); appendTextToLog(QStringLiteral("Running on %1").arg(QSysInfo::prettyProductName())); - appendTextToLog(QStringLiteral("Locale Languages offered %1, picked %2").arg(QLocale().uiLanguages().join(", ")).arg(prefs.locale.lang_locale)); + appendTextToLog(QStringLiteral("Locale Languages offered %1, picked %2").arg(QLocale().uiLanguages().join(", ")).arg(prefs.locale.lang_locale.c_str())); #if defined(Q_OS_ANDROID) extern QString getAndroidHWInfo(); appendTextToLog(getAndroidHWInfo()); @@ -303,17 +303,18 @@ QMLManager::QMLManager() : // make sure we know if the current cloud repo has been successfully synced syncLoadFromCloud(); - memset(&m_copyPasteDive, 0, sizeof(m_copyPasteDive)); - memset(&what, 0, sizeof(what)); - // Let's set some defaults to be copied so users don't necessarily need // to know how to configure this - what.diveguide = true; - what.buddy = true; - what.suit = true; - what.tags = true; - what.cylinders = true; - what.weights = true; + m_pasteDiveSite = false; + m_pasteNotes = false; + m_pasteDiveGuide = true; + m_pasteBuddy = true; + m_pasteSuit = true; + m_pasteRating = false; + m_pasteVisibility = false; + m_pasteTags = true; + m_pasteCylinders = true; + m_pasteWeights = true; // monitor when dives changed - but only in verbose mode // careful - changing verbose at runtime isn't enough (of course that could be added if we want it) @@ -413,9 +414,9 @@ void QMLManager::openLocalThenRemote(QString url) qPrefTechnicalDetails::set_show_ccr_sensors(git_prefs.show_ccr_sensors); qPrefPartialPressureGas::set_po2(git_prefs.pp_graphs.po2); // the following steps can take a long time, so provide updates - setNotificationText(tr("Processing %1 dives").arg(divelog.dives->nr)); - process_loaded_dives(); - setNotificationText(tr("%1 dives loaded from local dive data file").arg(divelog.dives->nr)); + setNotificationText(tr("Processing %1 dives").arg(divelog.dives.size())); + divelog.process_loaded_dives(); + setNotificationText(tr("%1 dives loaded from local dive data file").arg(divelog.dives.size())); } if (qPrefCloudStorage::cloud_verification_status() == qPrefCloudStorage::CS_NEED_TO_VERIFY) { appendTextToLog(QStringLiteral("have cloud credentials, but still needs PIN")); @@ -471,14 +472,14 @@ void QMLManager::updateAllGlobalLists() static QString nocloud_localstorage() { - return QString(system_default_directory()) + "/cloudstorage/localrepo[master]"; + return QString::fromStdString(system_default_directory() + "/cloudstorage/localrepo[master]"); } void QMLManager::mergeLocalRepo() { struct divelog log; parse_file(qPrintable(nocloud_localstorage()), &log); - add_imported_dives(&log, IMPORT_MERGE_ALL_TRIPS); + divelog.add_imported_dives(log, import_flags::merge_all_trips); mark_divelist_changed(true); } @@ -588,7 +589,7 @@ void QMLManager::finishSetup() // successfully opened the local file, now add thigs to the dive list consumeFinishedLoad(); updateHaveLocalChanges(true); - appendTextToLog(QString("working in no-cloud mode, finished loading %1 dives from %2").arg(divelog.dives->nr).arg(existing_filename.c_str())); + appendTextToLog(QString("working in no-cloud mode, finished loading %1 dives from %2").arg(divelog.dives.size()).arg(existing_filename.c_str())); } } else { qPrefCloudStorage::set_cloud_verification_status(qPrefCloudStorage::CS_UNKNOWN); @@ -650,11 +651,11 @@ void QMLManager::saveCloudCredentials(const QString &newEmail, const QString &ne return; } } - if (!same_string(prefs.cloud_storage_email, qPrintable(email))) { + if (prefs.cloud_storage_email != email.toStdString()) { cloudCredentialsChanged = true; } - if (!same_string(prefs.cloud_storage_password, qPrintable(newPassword))) { + if (prefs.cloud_storage_password != newPassword.toStdString()) { cloudCredentialsChanged = true; } @@ -672,7 +673,7 @@ void QMLManager::saveCloudCredentials(const QString &newEmail, const QString &ne qPrefCloudStorage::set_cloud_storage_email(email); qPrefCloudStorage::set_cloud_storage_password(newPassword); - if (m_oldStatus == qPrefCloudStorage::CS_NOCLOUD && cloudCredentialsChanged && divelog.dives->nr) { + if (m_oldStatus == qPrefCloudStorage::CS_NOCLOUD && cloudCredentialsChanged && divelog.dives.size()) { // we came from NOCLOUD and are connecting to a cloud account; // since we already have dives in the table, let's remember that so we can keep them noCloudToCloud = true; @@ -743,8 +744,8 @@ bool QMLManager::verifyCredentials(QString email, QString password, QString pin) void QMLManager::deleteAccount() { - QString email(prefs.cloud_storage_email); - QString passwd(prefs.cloud_storage_password); + QString email = QString::fromStdString(prefs.cloud_storage_email); + QString passwd = QString::fromStdString(prefs.cloud_storage_password); if (email.isEmpty() || passwd.isEmpty()) return; @@ -830,7 +831,7 @@ void QMLManager::loadDivesWithValidCredentials() if (noCloudToCloud) { git_storage_update_progress(qPrintable(tr("Loading dives from local storage ('no cloud' mode)"))); mergeLocalRepo(); - appendTextToLog(QStringLiteral("%1 dives loaded after importing nocloud local storage").arg(divelog.dives->nr)); + appendTextToLog(QStringLiteral("%1 dives loaded after importing nocloud local storage").arg(divelog.dives.size())); noCloudToCloud = false; mark_divelist_changed(true); emit syncStateChanged(); @@ -867,10 +868,8 @@ void QMLManager::revertToNoCloudIfNeeded() appendTextToLog(QStringLiteral("taking things back offline since sync with cloud failed")); git_local_only = false; } - free((void *)prefs.cloud_storage_email); - prefs.cloud_storage_email = NULL; - free((void *)prefs.cloud_storage_password); - prefs.cloud_storage_password = NULL; + prefs.cloud_storage_email.clear(); + prefs.cloud_storage_password.clear(); qPrefCloudStorage::set_cloud_storage_email(""); qPrefCloudStorage::set_cloud_storage_password(""); rememberOldStatus(); @@ -893,9 +892,9 @@ void QMLManager::consumeFinishedLoad() prefs.show_ccr_setpoint = git_prefs.show_ccr_setpoint; prefs.show_ccr_sensors = git_prefs.show_ccr_sensors; prefs.pp_graphs.po2 = git_prefs.pp_graphs.po2; - process_loaded_dives(); - appendTextToLog(QStringLiteral("%1 dives loaded").arg(divelog.dives->nr)); - if (divelog.dives->nr == 0) + divelog.process_loaded_dives(); + appendTextToLog(QStringLiteral("%1 dives loaded").arg(divelog.dives.size())); + if (divelog.dives.empty()) setStartPageText(tr("Cloud storage open successfully. No dives in dive list.")); } @@ -908,10 +907,10 @@ void QMLManager::refreshDiveList() // The following structure describes such a change caused by a dive edit. // Hopefully, we can remove this in due course by using finer-grained undo-commands. struct DiveSiteChange { - OwningDiveSitePtr createdDs; // not-null if we created a dive site. + std::unique_ptr createdDs; // not-null if we created a dive site. dive_site *editDs = nullptr; // not-null if we are supposed to edit an existing dive site. - location_t location = zero_location; // new value of the location if we edit an existing dive site. + location_t location; // new value of the location if we edit an existing dive site. bool changed = false; // true if either a dive site or the dive was changed. }; @@ -923,7 +922,7 @@ static void setupDivesite(DiveSiteChange &res, struct dive *d, struct dive_site res.editDs = ds; res.location = location; } else { - res.createdDs.reset(alloc_dive_site_with_name(locationtext)); + res.createdDs = std::make_unique(locationtext); res.createdDs->location = location; d->dive_site = res.createdDs.get(); } @@ -938,7 +937,7 @@ bool QMLManager::checkDate(struct dive *d, QString date) // what a pain - Qt will not parse dates if the day of the week is incorrect // so if the user changed the date but didn't update the day of the week (most likely behavior, actually), // we need to make sure we don't try to parse that - QString format(QString(prefs.date_format_short) + QChar(' ') + prefs.time_format); + QString format = QString::fromStdString(prefs.date_format_short + ' ' + prefs.time_format); if (format.contains(QLatin1String("ddd")) || format.contains(QLatin1String("dddd"))) { QString dateFormatToDrop = format.contains(QLatin1String("ddd")) ? QStringLiteral("ddd") : QStringLiteral("dddd"); QDateTime ts; @@ -1056,7 +1055,7 @@ parsed: // add a hundred years. if (newDate.addYears(100) < QDateTime::currentDateTime().addYears(1)) newDate = newDate.addYears(100); - d->dc.when = d->when = dateTimeToTimestamp(newDate); + d->dcs[0].when = d->when = dateTimeToTimestamp(newDate); return true; } appendTextToLog("none of our parsing attempts worked for the date string"); @@ -1066,13 +1065,13 @@ parsed: bool QMLManager::checkLocation(DiveSiteChange &res, struct dive *d, QString location, QString gps) { - struct dive_site *ds = get_dive_site_for_dive(d); + struct dive_site *ds = d->dive_site; bool changed = false; - QString oldLocation = get_dive_location(d); + QString oldLocation = QString::fromStdString(d->get_location()); if (oldLocation != location) { - ds = get_dive_site_by_name(qPrintable(location), divelog.sites); + ds = divelog.sites.get_by_name(location.toStdString()); if (!ds && !location.isEmpty()) { - res.createdDs.reset(alloc_dive_site_with_name(qPrintable(location))); + res.createdDs = std::make_unique(qPrintable(location)); res.changed = true; ds = res.createdDs.get(); } @@ -1134,9 +1133,9 @@ bool QMLManager::checkDuration(struct dive *d, QString duration) } else if (m6.hasMatch()) { m = m6.captured(1).toInt(); } - d->dc.duration.seconds = d->duration.seconds = h * 3600 + m * 60 + s; - if (is_dc_manually_added_dive(&d->dc)) - free_samples(&d->dc); + d->dcs[0].duration = d->duration = duration_t { .seconds = h * 3600 + m * 60 + s }; + if (is_dc_manually_added_dive(&d->dcs[0])) + d->dcs[0].samples.clear(); else appendTextToLog("Cannot change the duration on a dive that wasn't manually added"); return true; @@ -1146,16 +1145,16 @@ bool QMLManager::checkDuration(struct dive *d, QString duration) bool QMLManager::checkDepth(dive *d, QString depth) { - if (get_depth_string(d->dc.maxdepth.mm, true, true) != depth) { + if (get_depth_string(d->dcs[0].maxdepth.mm, true, true) != depth) { int depthValue = parseLengthToMm(depth); // the QML code should stop negative depth, but massively huge depth can make // the profile extremely slow or even run out of memory and crash, so keep // the depth <= 500m if (0 <= depthValue && depthValue <= 500000) { d->maxdepth.mm = depthValue; - if (is_dc_manually_added_dive(&d->dc)) { - d->dc.maxdepth.mm = d->maxdepth.mm; - free_samples(&d->dc); + if (is_dc_manually_added_dive(&d->dcs[0])) { + d->dcs[0].maxdepth.mm = d->maxdepth.mm; + d->dcs[0].samples.clear(); } return true; } @@ -1163,12 +1162,35 @@ bool QMLManager::checkDepth(dive *d, QString depth) return false; } +static weight_t parseWeight(const QString &text) +{ + QString numOnly = text; + numOnly.replace(",", ".").remove(QRegularExpression("[^0-9.]")); + if (numOnly.isEmpty()) + return {}; + double number = numOnly.toDouble(); + if (text.contains(gettextFromC::tr("kg"), Qt::CaseInsensitive)) { + return { .grams = int_cast(number * 1000) }; + } else if (text.contains(gettextFromC::tr("lbs"), Qt::CaseInsensitive)) { + return { .grams = lbs_to_grams(number) }; + } else { + switch (prefs.units.weight) { + case units::KG: + return { .grams = int_cast(number * 1000) }; + case units::LBS: + return { .grams = lbs_to_grams(number) }; + default: + return {}; + } + } +} + // update the dive and return the notes field, stripped of the HTML junk void QMLManager::commitChanges(QString diveId, QString number, QString date, QString location, QString gps, QString duration, QString depth, QString airtemp, QString watertemp, QString suit, QString buddy, QString diveGuide, QString tags, QString weight, QString notes, QStringList startpressure, QStringList endpressure, QStringList gasmix, QStringList usedCylinder, int rating, int visibility, QString state) { - struct dive *orig = get_dive_by_uniq_id(diveId.toInt()); + struct dive *orig = divelog.dives.get_by_uniq_id(diveId.toInt()); if (!orig) { appendTextToLog("cannot commit changes: no dive"); @@ -1192,7 +1214,7 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt report_info("state :'%s'", qPrintable(state)); } - OwningDivePtr d_ptr(alloc_dive()); // Automatically delete dive if we exit early! + auto d_ptr = std::make_unique(); // Automatically delete dive if we exit early! dive *d = d_ptr.get(); copy_dive(orig, d); @@ -1228,11 +1250,11 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt diveChanged = true; // not sure what we'd do if there was more than one weight system // defined - for now just ignore that case - if (d->weightsystems.nr == 0) { - weightsystem_t ws = { { parseWeightToGrams(weight) } , strdup(qPrintable(tr("weight"))), false }; - add_to_weightsystem_table(&d->weightsystems, 0, ws); // takes ownership of the string - } else if (d->weightsystems.nr == 1) { - d->weightsystems.weightsystems[0].weight.grams = parseWeightToGrams(weight); + if (d->weightsystems.size() == 0) { + weightsystem_t ws = { parseWeight(weight), tr("weight").toStdString(), false }; + d->weightsystems.add(0, std::move(ws)); + } else if (d->weightsystems.size() == 1) { + d->weightsystems[0].weight = parseWeight(weight); } } // start and end pressures @@ -1244,10 +1266,10 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt if (formatStartPressure(d) != startpressure || formatEndPressure(d) != endpressure) { diveChanged = true; for ( int i = 0, j = 0 ; j < startpressure.length() && j < endpressure.length() ; i++ ) { - if (state != "add" && !is_cylinder_used(d, i)) + if (state != "add" && !d->is_cylinder_used(i)) continue; - cylinder_t *cyl = get_or_create_cylinder(d, i); + cylinder_t *cyl = d->get_or_create_cylinder(i); cyl->start.mbar = parsePressureToMbar(startpressure[j]); cyl->end.mbar = parsePressureToMbar(endpressure[j]); if (cyl->end.mbar > cyl->start.mbar) @@ -1259,7 +1281,7 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt // gasmix for first cylinder if (formatFirstGas(d) != gasmix) { for ( int i = 0, j = 0 ; j < gasmix.length() ; i++ ) { - if (state != "add" && !is_cylinder_used(d, i)) + if (state != "add" && !d->is_cylinder_used(i)) continue; int o2 = parseGasMixO2(gasmix[j]); @@ -1269,8 +1291,8 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt he >= 0 && he <= 1000 && o2 + he <= 1000) { diveChanged = true; - get_or_create_cylinder(d, i)->gasmix.o2.permille = o2; - get_cylinder(d, i)->gasmix.he.permille = he; + d->get_or_create_cylinder(i)->gasmix.o2.permille = o2; + d->get_cylinder(i)->gasmix.he.permille = he; } j++; } @@ -1280,12 +1302,11 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt diveChanged = true; int size = 0, wp = 0, j = 0, k = 0; for (j = 0; k < usedCylinder.length(); j++) { - if (state != "add" && !is_cylinder_used(d, j)) + if (state != "add" && !d->is_cylinder_used(j)) continue; - for (int i = 0; i < tank_info_table.nr; i++) { - const tank_info &ti = tank_info_table.infos[i]; - if (ti.name == usedCylinder[k] ) { + for (const tank_info &ti: tank_info_table) { + if (ti.name == usedCylinder[k].toStdString()) { if (ti.ml > 0){ size = ti.ml; wp = ti.bar * 1000; @@ -1296,36 +1317,31 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt break; } } - get_or_create_cylinder(d, j)->type.description = copy_qstring(usedCylinder[k]); - get_cylinder(d, j)->type.size.mliter = size; - get_cylinder(d, j)->type.workingpressure.mbar = wp; + d->get_or_create_cylinder(j)->type.description = usedCylinder[k].toStdString(); + d->get_cylinder(j)->type.size.mliter = size; + d->get_cylinder(j)->type.workingpressure.mbar = wp; k++; } } - if (d->suit != suit) { + if (d->suit != suit.toStdString()) { diveChanged = true; - free(d->suit); - d->suit = copy_qstring(suit); + d->suit = suit.toStdString(); } - if (d->buddy != buddy) { - if (buddy.contains(",")){ + if (d->buddy != buddy.toStdString()) { + if (buddy.contains(",")) buddy = buddy.replace(QRegularExpression("\\s*,\\s*"), ", "); - } diveChanged = true; - free(d->buddy); - d->buddy = copy_qstring(buddy); + d->buddy = buddy.toStdString(); } - if (d->diveguide != diveGuide) { - if (diveGuide.contains(",")){ + if (d->diveguide != diveGuide.toStdString()) { + if (diveGuide.contains(",")) diveGuide = diveGuide.replace(QRegularExpression("\\s*,\\s*"), ", "); - } diveChanged = true; - free(d->diveguide); - d->diveguide = copy_qstring(diveGuide); + d->diveguide = diveGuide.toStdString(); } // normalize the tag list we have and the one we get from the UI // try hard to deal with accidental white space issues - QStringList existingTagList = QString::fromStdString(taglist_get_tagstring(d->tag_list)).split(",", SKIP_EMPTY); + QStringList existingTagList = QString::fromStdString(taglist_get_tagstring(d->tags)).split(",", SKIP_EMPTY); QStringList newTagList = tags.split(",", SKIP_EMPTY); QStringList newCleanTagList; for (QString s: newTagList) { @@ -1336,10 +1352,9 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt existingTagList.sort(); if (newCleanTagList.join(",") != existingTagList.join(",")) { diveChanged = true; - taglist_free(d->tag_list); - d->tag_list = nullptr; + d->tags.clear(); for (QString tag: newCleanTagList) - taglist_add_tag(&d->tag_list, qPrintable(tag)); + taglist_add_tag(d->tags, qPrintable(tag)); } if (d->rating != rating) { diveChanged = true; @@ -1351,24 +1366,23 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt } if (formatNotes(d) != notes) { diveChanged = true; - free(d->notes); - d->notes = copy_qstring(notes); + d->notes = notes.toStdString(); } // now that we have it all figured out, let's see what we need // to update if (diveChanged) { - if (d->maxdepth.mm == d->dc.maxdepth.mm && + if (d->maxdepth.mm == d->dcs[0].maxdepth.mm && d->maxdepth.mm > 0 && - is_dc_manually_added_dive(&d->dc) && - d->dc.samples == 0) { + is_dc_manually_added_dive(&d->dcs[0]) && + d->dcs[0].samples.empty()) { // so we have depth > 0, a manually added dive and no samples // let's create an actual profile so the desktop version can work it // first clear out the mean depth (or the fake_dc() function tries // to be too clever) - d->meandepth.mm = d->dc.meandepth.mm = 0; - fake_dc(&d->dc); + d->meandepth = d->dcs[0].meandepth = 0_m; + fake_dc(&d->dcs[0]); } - fixup_dive(d); + divelog.dives.fixup_dive(*d); Command::editDive(orig, d_ptr.release(), dsChange.createdDs.release(), dsChange.editDs, dsChange.location); // With release() we're giving up ownership changesNeedSaving(); } @@ -1377,17 +1391,17 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt void QMLManager::updateTripDetails(QString tripIdString, QString tripLocation, QString tripNotes) { int tripId = tripIdString.toInt(); - dive_trip_t *trip = get_trip_by_uniq_id(tripId); + dive_trip *trip = divelog.trips.get_by_uniq_id(tripId); if (!trip) { report_info("updateTripData: cannot find trip for tripId %s", qPrintable(tripIdString)); return; } bool changed = false; - if (tripLocation != trip->location) { + if (tripLocation != trip->location.c_str()) { changed = true; Command::editTripLocation(trip, tripLocation); } - if (tripNotes != trip->notes) { + if (tripNotes != trip->notes.c_str()) { changed = true; Command::editTripNotes(trip, tripNotes); } @@ -1397,7 +1411,7 @@ void QMLManager::updateTripDetails(QString tripIdString, QString tripLocation, Q void QMLManager::removeDiveFromTrip(int id) { - struct dive *d = get_dive_by_uniq_id(id); + struct dive *d = divelog.dives.get_by_uniq_id(id); if (!d) { appendTextToLog(QString("Asked to remove non-existing dive with id %1 from its trip.").arg(id)); return; @@ -1414,13 +1428,13 @@ void QMLManager::removeDiveFromTrip(int id) void QMLManager::addTripForDive(int id) { - struct dive *d = get_dive_by_uniq_id(id); + struct dive *d = divelog.dives.get_by_uniq_id(id); if (!d) { appendTextToLog(QString("Asked to create trip for non-existing dive with id %1").arg(id)); return; } if (d->divetrip) { - appendTextToLog(QString("Asked to create trip for dive %1 with id %2 but it's already part of a trip with location %3.").arg(d->number).arg(id).arg(d->divetrip->location)); + appendTextToLog(QString("Asked to create trip for dive %1 with id %2 but it's already part of a trip with location %3.").arg(d->number).arg(id).arg(d->divetrip->location.c_str())); return; } QVector dives; @@ -1431,12 +1445,12 @@ void QMLManager::addTripForDive(int id) void QMLManager::addDiveToTrip(int id, int tripId) { - struct dive *d = get_dive_by_uniq_id(id); + struct dive *d = divelog.dives.get_by_uniq_id(id); if (!d) { appendTextToLog(QString("Asked to add non-existing dive with id %1 to trip %2.").arg(id).arg(tripId)); return; } - struct dive_trip *dt = get_trip_by_uniq_id(tripId); + struct dive_trip *dt = divelog.trips.get_by_uniq_id(tripId); if (!dt) { appendTextToLog(QString("Asked to add dive with id %1 to trip with id %2 which cannot be found.").arg(id).arg(tripId)); return; @@ -1583,12 +1597,10 @@ void QMLManager::redo() void QMLManager::selectDive(int id) { - int i; extern int amount_selected; - struct dive *dive = NULL; amount_selected = 0; - for_each_dive (i, dive) { + for (auto &dive: divelog.dives) { dive->selected = (dive->id == id); if (dive->selected) amount_selected++; @@ -1599,7 +1611,7 @@ void QMLManager::selectDive(int id) void QMLManager::deleteDive(int id) { - struct dive *d = get_dive_by_uniq_id(id); + struct dive *d = divelog.dives.get_by_uniq_id(id); if (!d) { appendTextToLog("trying to delete non-existing dive"); return; @@ -1610,7 +1622,7 @@ void QMLManager::deleteDive(int id) void QMLManager::toggleDiveInvalid(int id) { - struct dive *d = get_dive_by_uniq_id(id); + struct dive *d = divelog.dives.get_by_uniq_id(id); if (!d) { appendTextToLog("trying to toggle invalid flag of non-existing dive"); return; @@ -1619,104 +1631,40 @@ void QMLManager::toggleDiveInvalid(int id) changesNeedSaving(); } -bool QMLManager::toggleDiveSite(bool toggle) +template +static void assign_paste_data(bool copy, const T &src, std::optional &dest) { - if (toggle) - what.divesite = what.divesite ? false : true; - - return what.divesite; -} - -bool QMLManager::toggleNotes(bool toggle) -{ - if (toggle) - what.notes = what.notes ? false : true; - - return what.notes; -} - -bool QMLManager::toggleDiveGuide(bool toggle) -{ - if (toggle) - what.diveguide = what.diveguide ? false : true; - - return what.diveguide; -} - -bool QMLManager::toggleBuddy(bool toggle) -{ - if (toggle) - what.buddy = what.buddy ? false : true; - - return what.buddy; -} - -bool QMLManager::toggleSuit(bool toggle) -{ - if (toggle) - what.suit = what.suit ? false : true; - - return what.suit; -} - -bool QMLManager::toggleRating(bool toggle) -{ - if (toggle) - what.rating = what.rating ? false : true; - - return what.rating; -} - -bool QMLManager::toggleVisibility(bool toggle) -{ - if (toggle) - what.visibility = what.visibility ? false : true; - - return what.visibility; -} - -bool QMLManager::toggleTags(bool toggle) -{ - if (toggle) - what.tags = what.tags ? false : true; - - return what.tags; -} - -bool QMLManager::toggleCylinders(bool toggle) -{ - if (toggle) - what.cylinders = what.cylinders ? false : true; - - return what.cylinders; -} - -bool QMLManager::toggleWeights(bool toggle) -{ - if (toggle) - what.weights = what.weights ? false : true; - - return what.weights; + if (copy) + dest = src; + else + dest = {}; } void QMLManager::copyDiveData(int id) { - m_copyPasteDive = get_dive_by_uniq_id(id); - if (!m_copyPasteDive) { + const dive *d = divelog.dives.get_by_uniq_id(id); + if (!d) { appendTextToLog("trying to copy non-existing dive"); return; } + assign_paste_data(m_pasteDiveSite, d->dive_site ? d->dive_site->uuid : uint32_t(), paste_data.divesite); + assign_paste_data(m_pasteNotes, d->notes, paste_data.notes); + assign_paste_data(m_pasteDiveGuide, d->diveguide, paste_data.diveguide); + assign_paste_data(m_pasteBuddy, d->buddy, paste_data.buddy); + assign_paste_data(m_pasteSuit, d->suit, paste_data.suit); + assign_paste_data(m_pasteRating, d->rating, paste_data.rating); + assign_paste_data(m_pasteVisibility, d->visibility, paste_data.visibility); + assign_paste_data(m_pasteTags, d->tags, paste_data.tags); + assign_paste_data(m_pasteCylinders, d->cylinders, paste_data.cylinders); + assign_paste_data(m_pasteWeights, d->weightsystems, paste_data.weights); + setNotificationText("Copy"); } void QMLManager::pasteDiveData(int id) { - if (!m_copyPasteDive) { - appendTextToLog("dive to paste is not selected"); - return; - } - Command::pasteDives(m_copyPasteDive, what); + Command::pasteDives(paste_data); changesNeedSaving(); } @@ -1727,23 +1675,11 @@ void QMLManager::cancelDownloadDC() int QMLManager::addDive() { - // TODO: Duplicate code with desktop-widgets/mainwindow.cpp - // create a dive an hour from now with a default depth (15m/45ft) and duration (40 minutes) - // as a starting point for the user to edit - struct dive d = { 0 }; - int diveId = d.id = dive_getUniqID(); - d.when = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset() + 3600; - d.dc.duration.seconds = 40 * 60; - d.dc.maxdepth.mm = M_OR_FT(15, 45); - d.dc.meandepth.mm = M_OR_FT(13, 39); // this creates a resonable looking safety stop - make_manually_added_dive_dc(&d.dc); - fake_dc(&d.dc); - fixup_dive(&d); - - // addDive takes over the dive and clears out the structure passed in // we do NOT save the modified data at this stage because of the UI flow here... this will // be saved once the user finishes editing the newly added dive - Command::addDive(&d, divelog.autogroup, true); + auto d = divelog.dives.default_dive(); + int diveId = d->id; + Command::addDive(std::move(d), divelog.autogroup, true); if (verbose) appendTextToLog(QString("Adding new dive with id '%1'").arg(diveId)); @@ -1767,14 +1703,14 @@ void QMLManager::setVerboseEnabled(bool verboseMode) void QMLManager::syncLoadFromCloud() { QSettings s; - QString cloudMarker = QLatin1String("loadFromCloud") + QString(prefs.cloud_storage_email); + QString cloudMarker = QLatin1String("loadFromCloud") + QString::fromStdString(prefs.cloud_storage_email); m_loadFromCloud = s.contains(cloudMarker) && s.value(cloudMarker).toBool(); } void QMLManager::setLoadFromCloud(bool done) { QSettings s; - QString cloudMarker = QLatin1String("loadFromCloud") + QString(prefs.cloud_storage_email); + QString cloudMarker = QLatin1String("loadFromCloud") + QString::fromStdString(prefs.cloud_storage_email); s.setValue(cloudMarker, done); m_loadFromCloud = done; emit loadFromCloudChanged(); @@ -1789,7 +1725,7 @@ void QMLManager::setStartPageText(const QString& text) QString QMLManager::getNumber(const QString& diveId) { int dive_id = diveId.toInt(); - struct dive *d = get_dive_by_uniq_id(dive_id); + struct dive *d = divelog.dives.get_by_uniq_id(dive_id); QString number; if (d) number = QString::number(d->number); @@ -1799,7 +1735,7 @@ QString QMLManager::getNumber(const QString& diveId) QString QMLManager::getDate(const QString& diveId) { int dive_id = diveId.toInt(); - struct dive *d = get_dive_by_uniq_id(dive_id); + struct dive *d = divelog.dives.get_by_uniq_id(dive_id); QString datestring; if (d) datestring = get_short_dive_date_string(d->when); @@ -1818,9 +1754,7 @@ QString QMLManager::getVersion() const QString QMLManager::getGpsFromSiteName(const QString &siteName) { - struct dive_site *ds; - - ds = get_dive_site_by_name(qPrintable(siteName), divelog.sites); + struct dive_site *ds = divelog.sites.get_by_name(siteName.toStdString()); if (!ds) return QString(); return printGPSCoords(&ds->location); @@ -2227,7 +2161,7 @@ void QMLManager::exportToFile(export_types type, QString dir, bool anonymize) break; case EX_DIVE_SITES_XML: { - std::vector sites = getDiveSitesToExport(false); + auto sites = getDiveSitesToExport(false); save_dive_sites_logic(qPrintable(fileName + ".xml"), sites.data(), (int)sites.size(), anonymize); break; } @@ -2259,7 +2193,7 @@ void QMLManager::shareViaEmail(export_types type, bool anonymize) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) QString fileName = appLogFileName; #else - QString fileName = system_default_directory(); + QString fileName = QString::fromStdString(system_default_directory()); #endif QString body; switch (type) { @@ -2275,7 +2209,7 @@ void QMLManager::shareViaEmail(export_types type, bool anonymize) case EX_DIVE_SITES_XML: fileName.replace("subsurface.log", "subsurface_divesites.xml"); { // need a block so the compiler doesn't complain about creating the sites variable here - std::vector sites = getDiveSitesToExport(false); + auto sites = getDiveSitesToExport(false); if (save_dive_sites_logic(qPrintable(fileName), sites.data(), (int)sites.size(), anonymize) == 0) { // ok, we have a file, let's send it body = "Subsurface dive site data"; @@ -2305,7 +2239,7 @@ void QMLManager::shareViaEmail(export_types type, bool anonymize) #elif defined(Q_OS_IOS) // call into objC++ code to share on iOS QString subject("Subsurface export"); - QString emptyString(""); + QString emptyString; iosshare.shareViaEmail(subject, emptyString, body, fileName, emptyString); #else appendTextToLog("on a mobile platform this would send" + fileName + "via email with body" + body); @@ -2334,13 +2268,12 @@ void QMLManager::rememberOldStatus() setOldStatus((qPrefCloudStorage::cloud_status)qPrefCloudStorage::cloud_verification_status()); } -void QMLManager::divesChanged(const QVector &dives, DiveField field) +void QMLManager::divesChanged(const QVector &dives, DiveField) { - Q_UNUSED(field) for (struct dive *d: dives) { - report_info("dive #%d changed, cache is %s", d->number, dive_cache_is_valid(d) ? "valid" : "invalidated"); + report_info("dive #%d changed, cache is %s", d->number, d->cache_is_valid() ? "valid" : "invalidated"); // a brute force way to deal with that would of course be to call - // invalidate_dive_cache(d); + // d->invalidate_cache(); } } @@ -2368,20 +2301,20 @@ void QMLManager::setDiveListProcessing(bool value) void QMLManager::importCacheRepo(QString repo) { struct divelog log; - QString repoPath = QString("%1/cloudstorage/%2").arg(system_default_directory()).arg(repo); + QString repoPath = QString::fromStdString(system_default_directory() + "/cloudstorage/") + repo; appendTextToLog(QString("importing %1").arg(repoPath)); parse_file(qPrintable(repoPath), &log); - add_imported_dives(&log, IMPORT_MERGE_ALL_TRIPS); + divelog.add_imported_dives(log, import_flags::merge_all_trips); changesNeedSaving(); } QStringList QMLManager::cloudCacheList() const { - QDir localCacheDir(QString("%1/cloudstorage/").arg(system_default_directory())); + QDir localCacheDir(QString("%1/cloudstorage/").arg(system_default_directory().c_str())); QStringList dirs = localCacheDir.entryList(); QStringList result; for (const QString &dir: dirs) { - QString originsDir = QString("%1/cloudstorage/%2/.git/refs/remotes/origin/").arg(system_default_directory()).arg(dir); + QString originsDir = QString::fromStdString(system_default_directory() + "/cloudstorage/%1/.git/refs/remotes/origin/").arg(dir); QDir remote(originsDir); if (dir == "localrepo") { result << QString("localrepo[master]"); diff --git a/mobile-widgets/qmlmanager.h b/mobile-widgets/qmlmanager.h index 9a24b5399..b93bf6b80 100644 --- a/mobile-widgets/qmlmanager.h +++ b/mobile-widgets/qmlmanager.h @@ -43,6 +43,17 @@ class QMLManager : public QObject { Q_PROPERTY(QString progressMessage MEMBER m_progressMessage WRITE setProgressMessage NOTIFY progressMessageChanged) Q_PROPERTY(bool btEnabled MEMBER m_btEnabled WRITE setBtEnabled NOTIFY btEnabledChanged) + Q_PROPERTY(bool pasteDiveSite MEMBER m_pasteDiveSite) + Q_PROPERTY(bool pasteNotes MEMBER m_pasteNotes) + Q_PROPERTY(bool pasteDiveGuide MEMBER m_pasteDiveGuide) + Q_PROPERTY(bool pasteBuddy MEMBER m_pasteBuddy) + Q_PROPERTY(bool pasteSuit MEMBER m_pasteSuit) + Q_PROPERTY(bool pasteRating MEMBER m_pasteRating) + Q_PROPERTY(bool pasteVisibility MEMBER m_pasteVisibility) + Q_PROPERTY(bool pasteTags MEMBER m_pasteTags) + Q_PROPERTY(bool pasteCylinders MEMBER m_pasteCylinders) + Q_PROPERTY(bool pasteWeights MEMBER m_pasteWeights) + Q_PROPERTY(QString DC_vendor READ DC_vendor WRITE DC_setVendor) Q_PROPERTY(QString DC_product READ DC_product WRITE DC_setProduct) Q_PROPERTY(QString DC_devName READ DC_devName WRITE DC_setDevName) @@ -187,16 +198,6 @@ public slots: void toggleDiveInvalid(int id); void copyDiveData(int id); void pasteDiveData(int id); - bool toggleDiveSite(bool toggle); - bool toggleNotes(bool toggle); - bool toggleDiveGuide(bool toggle); - bool toggleBuddy(bool toggle); - bool toggleSuit(bool toggle); - bool toggleRating(bool toggle); - bool toggleVisibility(bool toggle); - bool toggleTags(bool toggle); - bool toggleCylinders(bool toggle); - bool toggleWeights(bool toggle); void undo(); void redo(); int addDive(); @@ -250,11 +251,21 @@ private: void updateAllGlobalLists(); void updateHaveLocalChanges(bool status); + bool m_pasteDiveSite; + bool m_pasteNotes; + bool m_pasteDiveGuide; + bool m_pasteBuddy; + bool m_pasteSuit; + bool m_pasteRating; + bool m_pasteVisibility; + bool m_pasteTags; + bool m_pasteCylinders; + bool m_pasteWeights; + location_t getGps(QString &gps); QString m_pluggedInDeviceName; bool m_showNonDiveComputers; - struct dive *m_copyPasteDive = NULL; - struct dive_components what; + struct dive_paste_data paste_data; QAction *undoAction; bool verifyCredentials(QString email, QString password, QString pin); diff --git a/packaging/OBS/subsurfacedaily.spec b/packaging/OBS/subsurfacedaily.spec index 5a861c85e..ed8f24bd9 100644 --- a/packaging/OBS/subsurfacedaily.spec +++ b/packaging/OBS/subsurfacedaily.spec @@ -38,6 +38,7 @@ BuildRequires: libssh2-devel BuildRequires: libcurl-devel BuildRequires: libgit2-devel BuildRequires: libmtp-devel +BuildRequires: libraw-devel %if 0%{?fedora_version} || 0%{?rhel_version} || 0%{?centos_version} BuildRequires: netpbm-devel BuildRequires: openssl-devel diff --git a/packaging/android/docker-build.sh b/packaging/android/docker-build.sh index 9a496551d..a082951b4 100755 --- a/packaging/android/docker-build.sh +++ b/packaging/android/docker-build.sh @@ -34,7 +34,7 @@ if [[ -z "${CONTAINER_ID}" ]]; then croak "Please make sure GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL are set for the first run of this script." fi - docker create -v ${SUBSURFACE_ROOT}:${CONTAINER_SUBSURFACE_DIR} --name=${CONTAINER_NAME} subsurface/android-build:5.15.2 sleep infinity + docker create -v ${SUBSURFACE_ROOT}:${CONTAINER_SUBSURFACE_DIR} --name=${CONTAINER_NAME} subsurface/android-build:5.15.3 sleep infinity fi diff --git a/packaging/copr/subsurface.spec b/packaging/copr/subsurface.spec index 3064e1071..5e74bcaa9 100644 --- a/packaging/copr/subsurface.spec +++ b/packaging/copr/subsurface.spec @@ -35,6 +35,7 @@ BuildRequires: libssh2-devel BuildRequires: libcurl-devel BuildRequires: libgit2-devel BuildRequires: libmtp-devel +BuildRequires: LibRaw-devel BuildRequires: netpbm-devel BuildRequires: openssl-devel BuildRequires: libsqlite3x-devel diff --git a/packaging/macosx/resign.sh b/packaging/macosx/resign.sh index 25d761dda..27c3a38fb 100755 --- a/packaging/macosx/resign.sh +++ b/packaging/macosx/resign.sh @@ -6,8 +6,9 @@ # resign.sh path-where-DMG-is-mounted temp-dir-where-output-happens version croak() { - echo "$0: $*" >&2 - exit 1 + echo "$0: $*" >&2 + echo "usage: $0 " >&2 + exit 1 } if [[ "$1" == "" || ! -d "$1" || ! -d "$1/Subsurface.app/Contents/MacOS" ]] ; then @@ -15,6 +16,7 @@ if [[ "$1" == "" || ! -d "$1" || ! -d "$1/Subsurface.app/Contents/MacOS" ]] ; th fi if [[ "$2" == "" || ! -d "$2" ]] ; then mkdir -p "$2" || croak "can't create $2 as output directory" + WORKING=$( cd "$2" && pwd ) fi [[ "$3" == "" ]] && croak "missing a version argument" VERSION="$3" @@ -23,8 +25,9 @@ DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd ../../.. && pwd ) DMGCREATE=create-dmg -mkdir "$2"/staging -cd "$2"/staging +mkdir "$WORKING"/staging +cd "$WORKING" +pushd staging cp -a "$1/Subsurface.app" . @@ -40,14 +43,14 @@ codesign --options runtime --keychain "$HOME/Library/Keychains/login.keychain" - # ok, now the app is signed. let's notarize it # first create a apple appropriate zip file; # regular zip command isn't good enough, need to use "ditto" -ditto -c -k --sequesterRsrc --keepParent Subsurface.app "Subsurface-$VERSION.zip" +ditto -c -k --sequesterRsrc --keepParent Subsurface.app "$WORKING/Subsurface-$VERSION.zip" # this assumes that you have setup the notary tool and have the credentials stored # in your keychain -xcrun notarytool submit "./Subsurface-$VERSION.zip" --keychain-profile "notarytool-password" --wait +xcrun notarytool submit "$WORKING/Subsurface-$VERSION.zip" --keychain-profile "notarytool-password" --wait xcrun stapler staple Subsurface.app -cd "$2" +popd # it's not entirely clear if signing / stapling the DMG is required as well # all I can say is that when I do both, it appears to work diff --git a/packaging/ubuntu/debian/control b/packaging/ubuntu/debian/control index 1ab1a4eff..eaf4f9293 100644 --- a/packaging/ubuntu/debian/control +++ b/packaging/ubuntu/debian/control @@ -20,6 +20,7 @@ Build-Depends: asciidoc, libusb-1.0-0-dev, libbluetooth-dev, libmtp-dev, + libraw-dev, dh-autoreconf, libz-dev, libssl-dev, diff --git a/packaging/windows/docker-build.sh b/packaging/windows/docker-build.sh index 003d6c48c..5cca646c2 100755 --- a/packaging/windows/docker-build.sh +++ b/packaging/windows/docker-build.sh @@ -34,7 +34,7 @@ if [[ -z "${CONTAINER_ID}" ]]; then croak "Please make sure GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL are set for the first run of this script." fi - docker create -v ${SUBSURFACE_ROOT}:${CONTAINER_SUBSURFACE_DIR} --name=${CONTAINER_NAME} subsurface/mxe-build:3.1.0 sleep infinity + docker create -v ${SUBSURFACE_ROOT}:${CONTAINER_SUBSURFACE_DIR} --name=${CONTAINER_NAME} subsurface/mxe-build:3.2.0 sleep infinity fi # Start the container diff --git a/packaging/windows/in-container-build.sh b/packaging/windows/in-container-build.sh index f11c2a582..c4601f118 100755 --- a/packaging/windows/in-container-build.sh +++ b/packaging/windows/in-container-build.sh @@ -13,7 +13,7 @@ set -e mkdir -p win32 cd win32 -# build Subsurface and then smtk2ssrf +# build Subsurface export MXEBUILDTYPE=x86_64-w64-mingw32.shared bash -ex ../subsurface/packaging/windows/mxe-based-build.sh installer @@ -23,6 +23,10 @@ mv subsurface/subsurface.exe* ${OUTPUT_DIR}/ fullname=$(cd subsurface ; ls subsurface-*.exe) mv subsurface/"$fullname" ${OUTPUT_DIR}/"${fullname%.exe}-installer.exe" +# build Subsurface for smtk2ssrf + +bash -ex ../subsurface/packaging/windows/mxe-based-build.sh -noftdi -nolibraw subsurface + bash -ex ../subsurface/packaging/windows/smtk2ssrf-mxe-build.sh -a -i # the strange two step move is in order to get predictable names to use diff --git a/packaging/windows/mxe-based-build.sh b/packaging/windows/mxe-based-build.sh index f55519006..9da78dec6 100755 --- a/packaging/windows/mxe-based-build.sh +++ b/packaging/windows/mxe-based-build.sh @@ -27,12 +27,16 @@ # # now you can start the build # -# make libxml2 libxslt libusb1 libzip libssh2 libftdi1 curl qt5 nsis +# make libxml2 libxslt libusb1 libzip libssh2 libftdi1 libraw curl qt5 nsis # # (if you intend to build Subsurface without user space FTDI support # you can drop libftdi1 from that list and start this script with # -noftdi ) # +# (if you intend to build Subsurface without libraw support +# you can drop libraw from that list and start this script with +# -nolibraw ) +# # After quite a while (depending on your machine anywhere from 15-20 # minutes to several hours) you should have a working MXE install in # ~/src/mxe @@ -93,6 +97,13 @@ else FTDI="ON" fi +if [[ "$1" == "-nolibraw" ]] ; then + shift + LIBRAW="OFF" +else + LIBRAW="ON" +fi + # this is run on a rather powerful machine - if you want less # build parallelism, please change this variable JOBS="-j4" @@ -292,6 +303,7 @@ cd "$BUILDDIR"/subsurface -DMAKE_TESTS=OFF \ -DBTSUPPORT=ON -DBLESUPPORT=ON \ -DFTDISUPPORT=$FTDI \ + -DLIBRAW_SUPPORT=$LIBRAW \ -DLIBGIT2_FROM_PKGCONFIG=ON \ "$BASEDIR"/subsurface diff --git a/profile-widget/diveeventitem.cpp b/profile-widget/diveeventitem.cpp index c5fc5f975..c5848c9ea 100644 --- a/profile-widget/diveeventitem.cpp +++ b/profile-widget/diveeventitem.cpp @@ -3,6 +3,7 @@ #include "profile-widget/divecartesianaxis.h" #include "profile-widget/divepixmapcache.h" #include "profile-widget/animationfunctions.h" +#include "core/dive.h" #include "core/event.h" #include "core/eventtype.h" #include "core/format.h" @@ -15,14 +16,15 @@ static int depthAtTime(const plot_info &pi, duration_t time); -DiveEventItem::DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix, +DiveEventItem::DiveEventItem(const struct dive *d, int idx, const struct event &ev, struct gasmix lastgasmix, const plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent) : DivePixmapItem(parent), vAxis(vAxis), hAxis(hAxis), + idx(idx), ev(ev), dive(d), - depth(depthAtTime(pi, ev->time)) + depth(depthAtTime(pi, ev.time)) { setFlag(ItemIgnoresTransformations); @@ -35,31 +37,21 @@ DiveEventItem::~DiveEventItem() { } -const struct event *DiveEventItem::getEvent() const -{ - return ev; -} - -struct event *DiveEventItem::getEventMutable() -{ - return ev; -} - void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps) { - event_severity severity = get_event_severity(ev); - if (empty_string(ev->name)) { + event_severity severity = ev.get_severity(); + if (ev.name.empty()) { setPixmap(pixmaps.warning); - } else if (same_string_caseinsensitive(ev->name, "modechange")) { - if (ev->value == 0) + } else if (same_string_caseinsensitive(ev.name.c_str(), "modechange")) { + if (ev.value == 0) setPixmap(pixmaps.bailout); else setPixmap(pixmaps.onCCRLoop); - } else if (ev->type == SAMPLE_EVENT_BOOKMARK) { + } else if (ev.type == SAMPLE_EVENT_BOOKMARK) { setPixmap(pixmaps.bookmark); setOffset(QPointF(0.0, -pixmap().height())); - } else if (event_is_gaschange(ev)) { - struct gasmix mix = get_gasmix_from_event(dive, ev); + } else if (ev.is_gaschange()) { + struct gasmix mix = dive->get_gasmix_from_event(ev); struct icd_data icd_data; bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data); if (mix.he.permille) { @@ -83,10 +75,10 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix else setPixmap(pixmaps.gaschangeEAN); } - } else if ((((ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 1) || + } else if ((((ev.flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 1) || // those are useless internals of the dive computer - same_string_caseinsensitive(ev->name, "heading") || - (same_string_caseinsensitive(ev->name, "SP change") && ev->time.seconds == 0)) { + same_string_caseinsensitive(ev.name.c_str(), "heading") || + (same_string_caseinsensitive(ev.name.c_str(), "SP change") && ev.time.seconds == 0)) { // 2 cases: // a) some dive computers have heading in every sample // b) at t=0 we might have an "SP change" to indicate dive type @@ -101,19 +93,19 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix setPixmap(pixmaps.warning); } else if (severity == EVENT_SEVERITY_ALARM) { setPixmap(pixmaps.violation); - } else if (same_string_caseinsensitive(ev->name, "violation") || // generic libdivecomputer - same_string_caseinsensitive(ev->name, "Safety stop violation") || // the rest are from the Uemis downloader - same_string_caseinsensitive(ev->name, "pO₂ ascend alarm") || - same_string_caseinsensitive(ev->name, "RGT alert") || - same_string_caseinsensitive(ev->name, "Dive time alert") || - same_string_caseinsensitive(ev->name, "Low battery alert") || - same_string_caseinsensitive(ev->name, "Speed alarm")) { + } else if (same_string_caseinsensitive(ev.name.c_str(), "violation") || // generic libdivecomputer + same_string_caseinsensitive(ev.name.c_str(), "Safety stop violation") || // the rest are from the Uemis downloader + same_string_caseinsensitive(ev.name.c_str(), "pO₂ ascend alarm") || + same_string_caseinsensitive(ev.name.c_str(), "RGT alert") || + same_string_caseinsensitive(ev.name.c_str(), "Dive time alert") || + same_string_caseinsensitive(ev.name.c_str(), "Low battery alert") || + same_string_caseinsensitive(ev.name.c_str(), "Speed alarm")) { setPixmap(pixmaps.violation); - } else if (same_string_caseinsensitive(ev->name, "non stop time") || // generic libdivecomputer - same_string_caseinsensitive(ev->name, "safety stop") || - same_string_caseinsensitive(ev->name, "safety stop (voluntary)") || - same_string_caseinsensitive(ev->name, "Tank change suggested") || // Uemis downloader - same_string_caseinsensitive(ev->name, "Marker")) { + } else if (same_string_caseinsensitive(ev.name.c_str(), "non stop time") || // generic libdivecomputer + same_string_caseinsensitive(ev.name.c_str(), "safety stop") || + same_string_caseinsensitive(ev.name.c_str(), "safety stop (voluntary)") || + same_string_caseinsensitive(ev.name.c_str(), "Tank change suggested") || // Uemis downloader + same_string_caseinsensitive(ev.name.c_str(), "Marker")) { setPixmap(pixmaps.info); } else { // we should do some guessing based on the type / name of the event; @@ -125,19 +117,19 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix void DiveEventItem::setupToolTipString(struct gasmix lastgasmix) { // we display the event on screen - so translate - QString name = gettextFromC::tr(ev->name); - int value = ev->value; - int type = ev->type; + QString name = gettextFromC::tr(ev.name.c_str()); + int value = ev.value; + int type = ev.type; - if (event_is_gaschange(ev)) { + if (ev.is_gaschange()) { struct icd_data icd_data; - struct gasmix mix = get_gasmix_from_event(dive, ev); + struct gasmix mix = dive->get_gasmix_from_event(ev); name += ": "; - name += gasname(mix); + name += QString::fromStdString(mix.name()); /* Do we have an explicit cylinder index? Show it. */ - if (ev->gas.index >= 0) - name += tr(" (cyl. %1)").arg(ev->gas.index + 1); + if (ev.gas.index >= 0) + name += tr(" (cyl. %1)").arg(ev.gas.index + 1); bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data); if (icd_data.dHe < 0) { name += qasprintf_loc("\n%s %s:%+.3g%% %s:%+.3g%%%s%+.3g%%", @@ -146,25 +138,25 @@ void DiveEventItem::setupToolTipString(struct gasmix lastgasmix) qPrintable(tr("ΔN₂")), icd_data.dN2 / 10.0, icd ? ">" : "<", lrint(-icd_data.dHe / 5.0) / 10.0); } - } else if (same_string(ev->name, "modechange")) { - name += QString(": %1").arg(gettextFromC::tr(divemode_text_ui[ev->value])); + } else if (ev.name == "modechange") { + name += QString(": %1").arg(gettextFromC::tr(divemode_text_ui[ev.value])); } else if (value) { - if (type == SAMPLE_EVENT_PO2 && same_string(ev->name, "SP change")) { + if (type == SAMPLE_EVENT_PO2 && ev.name == "SP change") { name += QString(": %1bar").arg((double)value / 1000, 0, 'f', 1); - } else if (type == SAMPLE_EVENT_CEILING && same_string(ev->name, "planned waypoint above ceiling")) { + } else if (type == SAMPLE_EVENT_CEILING && ev.name == "planned waypoint above ceiling") { const char *depth_unit; double depth_value = get_depth_units(value*1000, NULL, &depth_unit); name += QString(": %1%2").arg((int) round(depth_value)).arg(depth_unit); } else { name += QString(": %1").arg(value); } - } else if (type == SAMPLE_EVENT_PO2 && same_string(ev->name, "SP change")) { + } else if (type == SAMPLE_EVENT_PO2 && ev.name == "SP change") { // this is a bad idea - we are abusing an existing event type that is supposed to // warn of high or low pO₂ and are turning it into a setpoint change event name += ":\n" + tr("Manual switch to OC"); } else { - name += ev->flags & SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") : - ev->flags & SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : ""; + name += ev.flags & SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") : + ev.flags & SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : ""; } setToolTip(QString("  ") + name); } @@ -177,9 +169,9 @@ void DiveEventItem::eventVisibilityChanged(const QString&, bool) static int depthAtTime(const plot_info &pi, duration_t time) { // Do a binary search for the timestamp - auto it = std::lower_bound(pi.entry, pi.entry + pi.nr, time, + auto it = std::lower_bound(pi.entry.begin(), pi.entry.end(), time, [](const plot_data &d1, duration_t t) { return d1.sec < t.seconds; }); - if (it == pi.entry + pi.nr || it->sec != time.seconds) { + if (it == pi.entry.end() || it->sec != time.seconds) { qWarning("can't find a spot in the dataModel"); return DEPTH_NOT_FOUND; } @@ -187,32 +179,31 @@ static int depthAtTime(const plot_info &pi, duration_t time) } bool DiveEventItem::isInteresting(const struct dive *d, const struct divecomputer *dc, - const struct event *ev, const plot_info &pi, + const struct event &ev, const plot_info &pi, int firstSecond, int lastSecond) { /* * Ignore items outside of plot range */ - if (ev->time.seconds < firstSecond || ev->time.seconds >= lastSecond) + if (ev.time.seconds < firstSecond || ev.time.seconds >= lastSecond) return false; /* * Some gas change events are special. Some dive computers just tell us the initial gas this way. * Don't bother showing those */ - const struct sample *first_sample = &dc->sample[0]; - if (!strcmp(ev->name, "gaschange") && - (ev->time.seconds == 0 || - (first_sample && ev->time.seconds == first_sample->time.seconds) || - depthAtTime(pi, ev->time) < SURFACE_THRESHOLD)) + if (ev.name == "gaschange" && + (ev.time.seconds == 0 || + (!dc->samples.empty() && ev.time.seconds == dc->samples[0].time.seconds) || + depthAtTime(pi, ev.time) < SURFACE_THRESHOLD)) return false; /* * Some divecomputers give "surface" events that just aren't interesting. * Like at the beginning or very end of a dive. Well, duh. */ - if (!strcmp(ev->name, "surface")) { - int time = ev->time.seconds; + if (ev.name == "surface") { + int time = ev.time.seconds; if (time <= 30 || time + 30 >= (int)dc->duration.seconds) return false; } @@ -221,15 +212,12 @@ bool DiveEventItem::isInteresting(const struct dive *d, const struct divecompute void DiveEventItem::recalculatePos() { - if (!ev) - return; - if (depth == DEPTH_NOT_FOUND) { hide(); return; } - setVisible(!ev->hidden && !is_event_type_hidden(ev)); - double x = hAxis->posAtValue(ev->time.seconds); + setVisible(!ev.hidden && !is_event_type_hidden(&ev)); + double x = hAxis->posAtValue(ev.time.seconds); double y = vAxis->posAtValue(depth); setPos(x, y); } diff --git a/profile-widget/diveeventitem.h b/profile-widget/diveeventitem.h index c2a453a8f..e09ae925d 100644 --- a/profile-widget/diveeventitem.h +++ b/profile-widget/diveeventitem.h @@ -3,26 +3,25 @@ #define DIVEEVENTITEM_H #include "divepixmapitem.h" +#include "core/event.h" class DiveCartesianAxis; -class DivePixmaps; +struct DivePixmaps; struct event; struct plot_info; class DiveEventItem : public DivePixmapItem { Q_OBJECT public: - DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix, + DiveEventItem(const struct dive *d, int idx, const struct event &ev, struct gasmix lastgasmix, const struct plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent = nullptr); ~DiveEventItem(); - const struct event *getEvent() const; - struct event *getEventMutable(); void eventVisibilityChanged(const QString &eventName, bool visible); void setVerticalAxis(DiveCartesianAxis *axis, int speed); void setHorizontalAxis(DiveCartesianAxis *axis); static bool isInteresting(const struct dive *d, const struct divecomputer *dc, - const struct event *ev, const struct plot_info &pi, + const struct event &ev, const struct plot_info &pi, int firstSecond, int lastSecond); private: @@ -31,7 +30,9 @@ private: void recalculatePos(); DiveCartesianAxis *vAxis; DiveCartesianAxis *hAxis; - struct event *ev; +public: + int idx; + struct event ev; const struct dive *dive; int depth; }; diff --git a/profile-widget/divehandler.cpp b/profile-widget/divehandler.cpp index 86a35d30d..90c138014 100644 --- a/profile-widget/divehandler.cpp +++ b/profile-widget/divehandler.cpp @@ -11,7 +11,7 @@ #include #include -DiveHandler::DiveHandler(const struct dive *d) : dive(d) +DiveHandler::DiveHandler(const struct dive *d, int currentDcNr) : dive(d), dcNr(currentDcNr) { setRect(-5, -5, 10, 10); setFlags(ItemIgnoresTransformations | ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges); @@ -31,14 +31,15 @@ void DiveHandler::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) QMenu m; // Don't have a gas selection for the last point emit released(); + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS); if (index.sibling(index.row() + 1, index.column()).isValid()) { - QStringList gases = get_dive_gas_list(dive); - for (int i = 0; i < gases.size(); i++) { + std::vector> gases = get_dive_gas_list(dive, dcNr, true); + for (unsigned i = 0; i < gases.size(); i++) { QAction *action = new QAction(&m); - action->setText(gases[i]); - action->setData(i); + action->setText(gases[i].second); + action->setData(gases[i].first); connect(action, &QAction::triggered, this, &DiveHandler::changeGas); m.addAction(action); } diff --git a/profile-widget/divehandler.h b/profile-widget/divehandler.h index 2bf3527ac..d56f3cc28 100644 --- a/profile-widget/divehandler.h +++ b/profile-widget/divehandler.h @@ -10,7 +10,7 @@ struct dive; class DiveHandler : public QObject, public QGraphicsEllipseItem { Q_OBJECT public: - DiveHandler(const struct dive *d); + DiveHandler(const struct dive *d, int currentDcNr); protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); @@ -29,6 +29,7 @@ slots: void changeGas(); private: const struct dive *dive; + int dcNr; QElapsedTimer t; }; diff --git a/profile-widget/divepercentageitem.cpp b/profile-widget/divepercentageitem.cpp index 8ddfaf6fd..84ed13925 100644 --- a/profile-widget/divepercentageitem.cpp +++ b/profile-widget/divepercentageitem.cpp @@ -2,6 +2,7 @@ #include "divepercentageitem.h" #include "divecartesianaxis.h" #include "core/dive.h" +#include "core/event.h" #include "core/profile.h" #include @@ -105,7 +106,7 @@ void DivePercentageItem::replot(const dive *d, const struct divecomputer *dc, co int x = 0; QRgb *scanline = (QRgb *)img.scanLine(line); QRgb color = 0; - const struct event *ev = NULL; + gasmix_loop loop(*d, *dc); for (int i = 0; i < pi.nr; i++) { const plot_data &item = pi.entry[i]; int sec = item.sec; @@ -114,7 +115,7 @@ void DivePercentageItem::replot(const dive *d, const struct divecomputer *dc, co continue; double value = item.percentages[tissue]; - struct gasmix gasmix = get_gasmix(d, dc, sec, &ev, gasmix_air); + struct gasmix gasmix = loop.at(sec).first; int inert = get_n2(gasmix) + get_he(gasmix); color = colorScale(value, inert); if (nextX >= width) diff --git a/profile-widget/diveprofileitem.cpp b/profile-widget/diveprofileitem.cpp index f4fd29a54..92f9f1bab 100644 --- a/profile-widget/diveprofileitem.cpp +++ b/profile-widget/diveprofileitem.cpp @@ -49,7 +49,7 @@ void AbstractProfilePolygonItem::clipStop(double &x, double &y, double prev_x, d std::pair AbstractProfilePolygonItem::getPoint(int i) const { - const struct plot_data *data = pInfo.entry; + const auto &data = pInfo.entry; double x = data[i].sec; double y = accessor(data[i]); @@ -121,7 +121,7 @@ void DiveProfileItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o pen.setCosmetic(true); pen.setWidth(2); QPolygonF poly = polygon(); - const struct plot_data *data = pInfo.entry; + const auto &data = pInfo.entry; // This paints the colors of the velocities. for (int i = from + 1; i < to; i++) { QColor color = getColor((color_index_t)(VELOCITY_COLORS_START_IDX + data[i].velocity)); @@ -150,7 +150,7 @@ void DiveProfileItem::replot(const dive *d, int from, int to, bool in_planner) /* Show any ceiling we may have encountered */ if (prefs.dcceiling && !prefs.redceiling) { QPolygonF p = polygon(); - plot_data *entry = pInfo.entry + to - 1; + auto entry = pInfo.entry.begin() + (to - 1); for (int i = to - 1; i >= from; i--, entry--) { if (!entry->in_deco) { /* not in deco implies this is a safety stop, no ceiling */ @@ -176,7 +176,7 @@ void DiveProfileItem::replot(const dive *d, int from, int to, bool in_planner) const int half_interval = vAxis.getMinLabelDistance(hAxis); const int min_depth = 2000; // in mm const int min_prominence = 2000; // in mm (should this adapt to depth range?) - const plot_data *data = pInfo.entry; + const auto &data = pInfo.entry; const int max_peaks = (data[to - 1].sec - data[from].sec) / half_interval + 1; struct Peak { int range_from; @@ -185,7 +185,7 @@ void DiveProfileItem::replot(const dive *d, int from, int to, bool in_planner) }; std::vector stack; stack.reserve(max_peaks); - int highest_peak = std::max_element(data + from, data + to, comp_depth) - data; + int highest_peak = std::max_element(data.begin() + from, data.begin() + to, comp_depth) - data.begin(); if (data[highest_peak].depth < min_depth) return; stack.push_back(Peak{ from, to, highest_peak }); @@ -210,7 +210,7 @@ void DiveProfileItem::replot(const dive *d, int from, int to, bool in_planner) // Continue search until peaks reach the minimum prominence (height from valley). for ( ; new_from + 3 < act_peak.range_to; ++new_from) { if (data[new_from].depth >= data[valley].depth + min_prominence) { - int new_peak = std::max_element(data + new_from, data + act_peak.range_to, comp_depth) - data; + int new_peak = std::max_element(data.begin() + new_from, data.begin() + act_peak.range_to, comp_depth) - data.begin(); if (data[new_peak].depth < min_depth) break; stack.push_back(Peak{ new_from, act_peak.range_to, new_peak }); @@ -236,7 +236,7 @@ void DiveProfileItem::replot(const dive *d, int from, int to, bool in_planner) // Continue search until peaks reach the minimum prominence (height from valley). for ( ; new_to >= act_peak.range_from + 3; --new_to) { if (data[new_to].depth >= data[valley].depth + min_prominence) { - int new_peak = std::max_element(data + act_peak.range_from, data + new_to, comp_depth) - data; + int new_peak = std::max_element(data.begin() + act_peak.range_from, data.begin() + new_to, comp_depth) - data.begin(); if (data[new_peak].depth < min_depth) break; stack.push_back(Peak{ act_peak.range_from, new_to, new_peak }); @@ -533,17 +533,17 @@ void DiveGasPressureItem::replot(const dive *d, int fromIn, int toIn, bool in_pl segments.clear(); for (int i = from; i < to; i++) { - const struct plot_data *entry = pInfo.entry + i; + auto entry = pInfo.entry.begin() + i; for (int cyl = 0; cyl < pInfo.nr_cylinders; cyl++) { - double mbar = static_cast(get_plot_pressure(&pInfo, i, cyl)); + double mbar = static_cast(get_plot_pressure(pInfo, i, cyl)); double time = static_cast(entry->sec); if (mbar < 1.0) continue; if (i == from && i < to - 1) { - double mbar2 = static_cast(get_plot_pressure(&pInfo, i+1, cyl)); + double mbar2 = static_cast(get_plot_pressure(pInfo, i+1, cyl)); double time2 = static_cast(entry[1].sec); if (mbar2 < 1.0) continue; @@ -551,7 +551,7 @@ void DiveGasPressureItem::replot(const dive *d, int fromIn, int toIn, bool in_pl } if (i == to - 1 && i > from) { - double mbar2 = static_cast(get_plot_pressure(&pInfo, i-1, cyl)); + double mbar2 = static_cast(get_plot_pressure(pInfo, i-1, cyl)); double time2 = static_cast(entry[-1].sec); if (mbar2 < 1.0) continue; @@ -602,7 +602,8 @@ void DiveGasPressureItem::replot(const dive *d, int fromIn, int toIn, bool in_pl bool showDescriptions = false; for (int cyl = 0; cyl < pInfo.nr_cylinders; cyl++) { - showDescriptions = showDescriptions || same_gasmix_cylinder(get_cylinder(d, cyl), cyl, d, true) != -1; + const cylinder_t *c = d->get_cylinder(cyl); + showDescriptions = showDescriptions || (c && same_gasmix_cylinder(*c, cyl, d, true) != -1); if (act_segments[cyl].polygon.empty()) continue; act_segments[cyl].cyl = cyl; @@ -631,7 +632,7 @@ void DiveGasPressureItem::replot(const dive *d, int fromIn, int toIn, bool in_pl // For each cylinder, on right hand side of the curve, write cylinder pressure double x_offset = plotPressureValue(segment.last.pressure, segment.last.time, Qt::AlignTop | Qt::AlignLeft, y_offset) + 2; - plotGasValue(segment.last.pressure, segment.last.time, get_cylinder(d, segment.cyl), Qt::AlignTop | Qt::AlignLeft, x_offset, y_offset, showDescriptions); + plotGasValue(segment.last.pressure, segment.last.time, d->get_cylinder(segment.cyl), Qt::AlignTop | Qt::AlignLeft, x_offset, y_offset, showDescriptions); /* Alternate alignment as we see cylinder use.. */ startAlignVar ^= Qt::AlignTop | Qt::AlignBottom; @@ -655,7 +656,7 @@ void DiveGasPressureItem::plotGasValue(double mbar, double sec, const cylinder_t QString gas = get_gas_string(cylinder->gasmix); QString label; if (showDescription) - label = QStringLiteral("(%1) %2").arg(cylinder->type.description, gas); + label = QStringLiteral("(%1) %2").arg(QString::fromStdString(cylinder->type.description), std::move(gas)); else label = gas; auto text = std::make_unique(dpr, 1.0, align, this); diff --git a/profile-widget/divetooltipitem.cpp b/profile-widget/divetooltipitem.cpp index 403e252d7..1b6b22b2d 100644 --- a/profile-widget/divetooltipitem.cpp +++ b/profile-widget/divetooltipitem.cpp @@ -212,7 +212,7 @@ void ToolTipItem::setPlotInfo(const plot_info &plot) void ToolTipItem::clearPlotInfo() { - memset(&pInfo, 0, sizeof(pInfo)); + pInfo = plot_info(); } void ToolTipItem::setTimeAxis(DiveCartesianAxis *axis) @@ -231,7 +231,7 @@ void ToolTipItem::refresh(const dive *d, const QPointF &pos, bool inPlanner) lastTime = time; clear(); - auto [idx, lines] = get_plot_details_new(d, &pInfo, time); + auto [idx, lines] = get_plot_details_new(d, pInfo, time); tissues.fill(); painter.setPen(QColor(0, 0, 0, 0)); diff --git a/profile-widget/profilescene.cpp b/profile-widget/profilescene.cpp index b1e19ed1c..7d77f28ad 100644 --- a/profile-widget/profilescene.cpp +++ b/profile-widget/profilescene.cpp @@ -7,12 +7,14 @@ #include "diveprofileitem.h" #include "divetextitem.h" #include "tankitem.h" +#include "core/dive.h" #include "core/device.h" #include "core/divecomputer.h" #include "core/event.h" #include "core/pref.h" #include "core/profile.h" #include "core/qthelper.h" // for decoMode() +#include "core/range.h" #include "core/subsurface-float.h" #include "core/subsurface-string.h" #include "core/settings/qPrefDisplay.h" @@ -151,8 +153,6 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) : tankItem(new TankItem(*timeAxis, dpr)), pixmaps(getDivePixmaps(dpr)) { - init_plot_info(&plotInfo); - setSceneRect(0, 0, 100, 100); setItemIndexMethod(QGraphicsScene::NoIndex); @@ -188,7 +188,6 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) : ProfileScene::~ProfileScene() { - free_plot_info_data(&plotInfo); } void ProfileScene::clear() @@ -201,7 +200,7 @@ void ProfileScene::clear() // the DiveEventItems qDeleteAll(eventItems); eventItems.clear(); - free_plot_info_data(&plotInfo); + plotInfo = plot_info(); empty = true; } @@ -214,9 +213,9 @@ static bool ppGraphsEnabled(const struct divecomputer *dc, bool simplified) // Update visibility of non-interactive chart features according to preferences void ProfileScene::updateVisibility(bool diveHasHeartBeat, bool simplified) { - const struct divecomputer *currentdc = get_dive_dc_const(d, dc); - if (!currentdc) + if (!d) return; + const struct divecomputer *currentdc = d->get_dc(dc); bool ppGraphs = ppGraphsEnabled(currentdc, simplified); diveCeiling->setVisible(prefs.calcceiling); @@ -292,9 +291,9 @@ struct VerticalAxisLayout { void ProfileScene::updateAxes(bool diveHasHeartBeat, bool simplified) { - const struct divecomputer *currentdc = get_dive_dc_const(d, dc); - if (!currentdc) + if (!d) return; + const struct divecomputer *currentdc = d->get_dc(dc); // Calculate left and right border needed for the axes and other chart items. double leftBorder = profileYAxis->width(); @@ -429,8 +428,8 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM decoModelParameters->set(QString("GF %1/%2").arg(diveplan.gflow).arg(diveplan.gfhigh), getColor(PRESSURE_TEXT)); } - const struct divecomputer *currentdc = get_dive_dc_const(d, dc); - if (!currentdc || !currentdc->samples) { + const struct divecomputer *currentdc = d->get_dc(dc); + if (!currentdc || currentdc->samples.empty()) { clear(); return; } @@ -454,7 +453,7 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM * create_plot_info_new() automatically frees old plot data. */ if (!keepPlotInfo) - create_plot_info_new(d, currentdc, &plotInfo, planner_ds); + plotInfo = create_plot_info_new(d, currentdc, planner_ds); bool hasHeartBeat = plotInfo.maxhr; // For mobile we might want to turn of some features that are normally shown. @@ -466,7 +465,7 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM updateVisibility(hasHeartBeat, simplified); updateAxes(hasHeartBeat, simplified); - int newMaxtime = get_maxtime(&plotInfo); + int newMaxtime = get_maxtime(plotInfo); if (calcMax || newMaxtime > maxtime) maxtime = newMaxtime; @@ -474,7 +473,7 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM * when we are dragging the handler to plan / add dive. * otherwhise, update normally. */ - int newMaxDepth = get_maxdepth(&plotInfo); + int newMaxDepth = get_maxdepth(plotInfo); if (!calcMax) { if (maxdepth < newMaxDepth) maxdepth = newMaxDepth; @@ -509,18 +508,18 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM // Find first and last plotInfo entry int firstSecond = lrint(timeAxis->minimum()); int lastSecond = lrint(timeAxis->maximum()); - auto it1 = std::lower_bound(plotInfo.entry, plotInfo.entry + plotInfo.nr, firstSecond, + auto it1 = std::lower_bound(plotInfo.entry.begin(), plotInfo.entry.end(), firstSecond, [](const plot_data &d, int s) { return d.sec < s; }); - auto it2 = std::lower_bound(it1, plotInfo.entry + plotInfo.nr, lastSecond, + auto it2 = std::lower_bound(it1, plotInfo.entry.end(), lastSecond, [](const plot_data &d, int s) { return d.sec < s; }); - if (it1 > plotInfo.entry && it1->sec > firstSecond) + if (it1 > plotInfo.entry.begin() && it1->sec > firstSecond) --it1; - if (it2 < plotInfo.entry + plotInfo.nr) + if (it2 < plotInfo.entry.end()) ++it2; - int from = it1 - plotInfo.entry; - int to = it2 - plotInfo.entry; + int from = it1 - plotInfo.entry.begin(); + int to = it2 - plotInfo.entry.begin(); timeAxis->updateTicks(animSpeed); animatedAxes.push_back(timeAxis); @@ -552,41 +551,37 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM // while all other items are up there on the constructor. qDeleteAll(eventItems); eventItems.clear(); - struct event *event = currentdc->events; - struct gasmix lastgasmix = get_gasmix_at_time(d, currentdc, duration_t{1}); + struct gasmix lastgasmix = d->get_gasmix_at_time(*currentdc, 1_sec); - while (event) { + for (auto [idx, event]: enumerated_range(currentdc->events)) { // if print mode is selected only draw headings, SP change, gas events or bookmark event if (printMode) { - if (empty_string(event->name) || - !(strcmp(event->name, "heading") == 0 || - (same_string(event->name, "SP change") && event->time.seconds == 0) || - event_is_gaschange(event) || - event->type == SAMPLE_EVENT_BOOKMARK)) { - event = event->next; + if (event.name.empty() || + !(event.name == "heading" || + (event.name == "SP change" && event.time.seconds == 0) || + event.is_gaschange() || + event.type == SAMPLE_EVENT_BOOKMARK)) continue; - } } if (DiveEventItem::isInteresting(d, currentdc, event, plotInfo, firstSecond, lastSecond)) { - auto item = new DiveEventItem(d, event, lastgasmix, plotInfo, + auto item = new DiveEventItem(d, idx, event, lastgasmix, plotInfo, timeAxis, profileYAxis, animSpeed, *pixmaps); item->setZValue(2); addItem(item); eventItems.push_back(item); } - if (event_is_gaschange(event)) - lastgasmix = get_gasmix_from_event(d, event); - event = event->next; + if (event.is_gaschange()) + lastgasmix = d->get_gasmix_from_event(event); } - QString dcText = get_dc_nickname(currentdc); + QString dcText = QString::fromStdString(get_dc_nickname(currentdc)); if (is_dc_planner(currentdc)) dcText = tr("Planned dive"); else if (is_dc_manually_added_dive(currentdc)) dcText = tr("Manually added dive"); else if (dcText.isEmpty()) dcText = tr("Unknown dive computer"); - int nr = number_of_computers(d); + int nr = d->number_of_computers(); if (nr > 1) dcText += tr(" (#%1 of %2)").arg(dc + 1).arg(nr); diveComputerText->set(dcText, getColor(TIME_TEXT, isGrayscale)); diff --git a/profile-widget/profilescene.h b/profile-widget/profilescene.h index 5b1fbc93d..faddccb75 100644 --- a/profile-widget/profilescene.h +++ b/profile-widget/profilescene.h @@ -20,7 +20,7 @@ class DiveGasPressureItem; class DiveHeartrateItem; class DiveMeanDepthItem; class DivePercentageItem; -class DivePixmaps; +struct DivePixmaps; class DivePlannerPointsModel; class DiveProfileItem; class DiveReportedCeiling; diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 268b80203..8094fc71b 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -500,18 +500,6 @@ struct int ProfileWidget2::getEntryFromPos(QPointF pos) #endif #ifndef SUBSURFACE_MOBILE -/// Prints cylinder information for display. -/// eg : "Cyl 1 (AL80 EAN32)" -static QString printCylinderDescription(int i, const cylinder_t *cylinder) -{ - QString label = gettextFromC::tr("Cyl") + QString(" %1").arg(i+1); - if( cylinder != NULL ) { - QString mix = get_gas_string(cylinder->gasmix); - label += QString(" (%2 %3)").arg(cylinder->type.description).arg(mix); - } - return label; -} - static bool isDiveTextItem(const QGraphicsItem *item, const DiveTextItem *textItem) { while (item) { @@ -522,6 +510,16 @@ static bool isDiveTextItem(const QGraphicsItem *item, const DiveTextItem *textIt return false; } +void ProfileWidget2::addGasChangeMenu(QMenu &m, QString menuTitle, const struct dive &d, int dcNr, int changeTime) +{ + QMenu *gasChange = m.addMenu(menuTitle); + std::vector> gases = get_dive_gas_list(&d, dcNr, true); + for (unsigned i = 0; i < gases.size(); i++) { + int cylinderIndex = gases[i].first; + gasChange->addAction(gases[i].second, [this, cylinderIndex, changeTime] { addGasSwitch(cylinderIndex, changeTime); }); + } +} + void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) { if (currentState == EDIT || currentState == PLAN) { @@ -534,14 +532,14 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) // figure out if we are ontop of the dive computer name in the profile QGraphicsItem *sceneItem = itemAt(mapFromGlobal(event->globalPos())); if (isDiveTextItem(sceneItem, profileScene->diveComputerText)) { - const struct divecomputer *currentdc = get_dive_dc_const(d, dc); - if (!currentdc->deviceid && dc == 0 && number_of_computers(d) == 1) + const struct divecomputer *currentdc = d->get_dc(dc); + if (!currentdc->deviceid && dc == 0 && d->number_of_computers() == 1) // nothing to do, can't rename, delete or reorder return; // create menu to show when right clicking on dive computer name if (dc > 0) m.addAction(tr("Make first dive computer"), this, &ProfileWidget2::makeFirstDC); - if (number_of_computers(d) > 1) { + if (d->number_of_computers() > 1) { m.addAction(tr("Delete this dive computer"), this, &ProfileWidget2::deleteCurrentDC); m.addAction(tr("Split this dive computer into own dive"), this, &ProfileWidget2::splitCurrentDC); } @@ -559,30 +557,22 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) DiveEventItem *item = dynamic_cast(sceneItem); // Add or edit Gas Change - if (d && item && event_is_gaschange(item->getEvent())) { - int eventTime = item->getEvent()->time.seconds; - QMenu *gasChange = m.addMenu(tr("Edit Gas Change")); - for (int i = 0; i < d->cylinders.nr; i++) { - const cylinder_t *cylinder = get_cylinder(d, i); - QString label = printCylinderDescription(i, cylinder); - gasChange->addAction(label, [this, i, eventTime] { addGasSwitch(i, eventTime); }); - } - } else if (d && d->cylinders.nr > 1) { + if (d && item && item->ev.is_gaschange()) { + addGasChangeMenu(m, tr("Edit gas change"), *d, dc, item->ev.time.seconds); + } else if (d && d->cylinders.size() > 1) { // if we have more than one gas, offer to switch to another one - QMenu *gasChange = m.addMenu(tr("Add gas change")); - for (int i = 0; i < d->cylinders.nr; i++) { - const cylinder_t *cylinder = get_cylinder(d, i); - QString label = printCylinderDescription(i, cylinder); - gasChange->addAction(label, [this, i, seconds] { addGasSwitch(i, seconds); }); - } + const struct divecomputer *currentdc = d->get_dc(dc); + if (seconds == 0 || (!currentdc->samples.empty() && seconds <= currentdc->samples[0].time.seconds)) + addGasChangeMenu(m, tr("Set initial gas"), *d, dc, 0); + else + addGasChangeMenu(m, tr("Add gas change"), *d, dc, seconds); } m.addAction(tr("Add setpoint change"), [this, seconds]() { ProfileWidget2::addSetpointChange(seconds); }); m.addAction(tr("Add bookmark"), [this, seconds]() { addBookmark(seconds); }); m.addAction(tr("Split dive into two"), [this, seconds]() { splitDive(seconds); }); - const struct event *ev = NULL; - enum divemode_t divemode = UNDEF_COMP_TYPE; - get_current_divemode(get_dive_dc_const(d, dc), seconds, &ev, &divemode); + divemode_loop loop(*d->get_dc(dc)); + divemode_t divemode = loop.at(seconds); QMenu *changeMode = m.addMenu(tr("Change divemode")); if (divemode != OC) changeMode->addAction(gettextFromC::tr(divemode_text_ui[OC]), @@ -595,23 +585,22 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) [this, seconds](){ addDivemodeSwitch(seconds, PSCR); }); if (DiveEventItem *item = dynamic_cast(sceneItem)) { - const struct event *dcEvent = item->getEvent(); m.addAction(tr("Remove event"), [this,item] { removeEvent(item); }); - m.addAction(tr("Hide event"), [this, item] { hideEvent(item); }); - m.addAction(tr("Hide events of type '%1'").arg(event_type_name(dcEvent)), + m.addAction(tr("Hide event"), [this, item] { hideOneEvent(item); }); + m.addAction(tr("Hide events of type '%1'").arg(event_type_name(item->ev)), [this, item] { hideEventType(item); }); - if (dcEvent->type == SAMPLE_EVENT_BOOKMARK) + if (item->ev.type == SAMPLE_EVENT_BOOKMARK) m.addAction(tr("Edit name"), [this, item] { editName(item); }); #if 0 // TODO::: FINISH OR DISABLE QPointF scenePos = mapToScene(event->pos()); int idx = getEntryFromPos(scenePos); // this shows how to figure out if we should ask the user if they want adjust interpolated pressures // at either side of a gas change - if (dcEvent->type == SAMPLE_EVENT_GASCHANGE || dcEvent->type == SAMPLE_EVENT_GASCHANGE2) { + if (item->ev->type == SAMPLE_EVENT_GASCHANGE || item->ev->type == SAMPLE_EVENT_GASCHANGE2) { int gasChangeIdx = idx; while (gasChangeIdx > 0) { --gasChangeIdx; - if (plotInfo.entry[gasChangeIdx].sec <= dcEvent->time.seconds) + if (plotInfo.entry[gasChangeIdx].sec <= item->ev->time.seconds) break; } const struct plot_data &gasChangeEntry = plotInfo.entry[newGasIdx]; @@ -620,7 +609,7 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) if (gasChangeIdx < plotInfo.nr - 1) { int newGasIdx = gasChangeIdx + 1; const struct plot_data &newGasEntry = plotInfo.entry[newGasIdx]; - if (get_plot_sensor_pressure(&plotInfo, gasChangeIdx) == 0 || get_cylinder(d, gasChangeEntry->sensor[0])->sample_start.mbar == 0) { + if (get_plot_sensor_pressure(&plotInfo, gasChangeIdx) == 0 || d->get_cylinder(gasChangeEntry->sensor[0])->sample_start.mbar == 0) { // if we have no sensorpressure or if we have no pressure from samples we can assume that // we only have interpolated pressure (the pressure in the entry may be stored in the sensor // pressure field if this is the first or last entry for this tank... see details in gaspressures.c @@ -629,7 +618,7 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) QAction *adjustOldPressure = m.addAction(tr("Adjust pressure of cyl. %1 (currently interpolated as %2)") .arg(gasChangeEntry->sensor[0] + 1).arg(get_pressure_string(pressure))); } - if (get_plot_sensor_pressure(&plotInfo, newGasIdx) == 0 || get_cylinder(d, newGasEntry->sensor[0])->sample_start.mbar == 0) { + if (get_plot_sensor_pressure(&plotInfo, newGasIdx) == 0 || d->get_cylinder(newGasEntry->sensor[0])->sample_start.mbar == 0) { // we only have interpolated press -- see commend above pressure_t pressure; pressure.mbar = get_plot_interpolated_pressure(&plotInfo, newGasIdx) ? : get_plot_sensor_pressure(&plotInfo, newGasIdx); @@ -650,8 +639,9 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) } m2->addAction(tr("All event types"), this, &ProfileWidget2::unhideEventTypes); } - if (std::any_of(profileScene->eventItems.begin(), profileScene->eventItems.end(), - [] (const DiveEventItem *item) { return item->getEvent()->hidden; })) + const struct divecomputer *currentdc = d->get_dc(dc); + if (currentdc && std::any_of(currentdc->events.begin(), currentdc->events.end(), + [] (auto &ev) { return ev.hidden; })) m.addAction(tr("Unhide individually hidden events of this dive"), this, &ProfileWidget2::unhideEvents); m.exec(event->globalPos()); } @@ -676,29 +666,34 @@ void ProfileWidget2::makeFirstDC() void ProfileWidget2::renameCurrentDC() { - bool ok; - struct divecomputer *currentdc = get_dive_dc(mutable_dive(), dc); - if (!currentdc) + if (!d) return; + bool ok; + struct divecomputer *currentdc = mutable_dive()->get_dc(dc); QString newName = QInputDialog::getText(this, tr("Edit nickname"), - tr("Set new nickname for %1 (serial %2):").arg(currentdc->model).arg(currentdc->serial), - QLineEdit::Normal, get_dc_nickname(currentdc), &ok); + tr("Set new nickname for %1 (serial %2):").arg(QString::fromStdString(currentdc->model)). + arg(QString::fromStdString(currentdc->serial)), + QLineEdit::Normal, QString::fromStdString(get_dc_nickname(currentdc)), &ok); if (ok) Command::editDeviceNickname(currentdc, newName); } -void ProfileWidget2::hideEvent(DiveEventItem *item) +void ProfileWidget2::hideOneEvent(DiveEventItem *item) { - item->getEventMutable()->hidden = true; + if (!d) + return; + struct divecomputer *currentdc = mutable_dive()->get_dc(dc); + int idx = item->idx; + if (!currentdc || idx < 0 || static_cast(idx) >= currentdc->events.size()) + return; + currentdc->events[idx].hidden = true; item->hide(); } void ProfileWidget2::hideEventType(DiveEventItem *item) { - const struct event *event = item->getEvent(); - - if (!empty_string(event->name)) { - hide_event_type(event); + if (!item->ev.name.empty()) { + hide_event_type(&item->ev); replot(); } @@ -706,10 +701,15 @@ void ProfileWidget2::hideEventType(DiveEventItem *item) void ProfileWidget2::unhideEvents() { - for (DiveEventItem *item: profileScene->eventItems) { - item->getEventMutable()->hidden = false; + if (!d) + return; + struct divecomputer *currentdc = mutable_dive()->get_dc(dc); + if (!currentdc) + return; + for (auto &ev: currentdc->events) + ev.hidden = false; + for (DiveEventItem *item: profileScene->eventItems) item->show(); - } } void ProfileWidget2::unhideEventTypes() @@ -721,15 +721,12 @@ void ProfileWidget2::unhideEventTypes() void ProfileWidget2::removeEvent(DiveEventItem *item) { - struct event *event = item->getEventMutable(); - if (!event || !d) - return; - + const struct event &ev = item->ev; if (QMessageBox::question(this, TITLE_OR_TEXT( tr("Remove the selected event?"), - tr("%1 @ %2:%3").arg(event->name).arg(event->time.seconds / 60).arg(event->time.seconds % 60, 2, 10, QChar('0'))), + tr("%1 @ %2:%3").arg(QString::fromStdString(ev.name)).arg(ev.time.seconds / 60).arg(ev.time.seconds % 60, 2, 10, QChar('0'))), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) - Command::removeEvent(mutable_dive(), dc, event); + Command::removeEvent(mutable_dive(), dc, item->idx); } void ProfileWidget2::addBookmark(int seconds) @@ -756,12 +753,12 @@ void ProfileWidget2::splitDive(int seconds) { if (!d) return; - Command::splitDives(mutable_dive(), duration_t{ seconds }); + Command::splitDives(mutable_dive(), duration_t{ .seconds = seconds }); } void ProfileWidget2::addGasSwitch(int tank, int seconds) { - if (!d || tank < 0 || tank >= d->cylinders.nr) + if (!d || tank < 0 || static_cast(tank) >= d->cylinders.size()) return; Command::addGasSwitch(mutable_dive(), dc, seconds, tank); @@ -780,13 +777,12 @@ void ProfileWidget2::changeGas(int index, int newCylinderId) void ProfileWidget2::editName(DiveEventItem *item) { - struct event *event = item->getEventMutable(); - if (!event || !d) + if (!d) return; bool ok; QString newName = QInputDialog::getText(this, tr("Edit name of bookmark"), tr("Custom name:"), QLineEdit::Normal, - event->name, &ok); + item->ev.name.c_str(), &ok); if (ok && !newName.isEmpty()) { if (newName.length() > 22) { //longer names will display as garbage. QMessageBox lengthWarning; @@ -794,7 +790,7 @@ void ProfileWidget2::editName(DiveEventItem *item) lengthWarning.exec(); return; } - Command::renameEvent(mutable_dive(), dc, event, qPrintable(newName)); + Command::renameEvent(mutable_dive(), dc, item->idx, newName.toStdString()); } } @@ -836,7 +832,7 @@ int ProfileWidget2::handleIndex(const DiveHandler *h) const DiveHandler *ProfileWidget2::createHandle() { - DiveHandler *item = new DiveHandler(d); + DiveHandler *item = new DiveHandler(d, dc); scene()->addItem(item); connect(item, &DiveHandler::moved, this, &ProfileWidget2::divePlannerHandlerMoved); connect(item, &DiveHandler::clicked, this, &ProfileWidget2::divePlannerHandlerClicked); @@ -922,8 +918,8 @@ void ProfileWidget2::repositionDiveHandlers() QLineF line(p1, p2); QPointF pos = line.pointAt(0.5); gases[i]->setPos(pos); - if (datapoint.cylinderid >= 0 && datapoint.cylinderid < d->cylinders.nr) - gases[i]->setText(get_gas_string(get_cylinder(d, datapoint.cylinderid)->gasmix)); + if (datapoint.cylinderid >= 0 && datapoint.cylinderid < static_cast(d->cylinders.size())) + gases[i]->setText(get_gas_string(d->get_cylinder(datapoint.cylinderid)->gasmix)); else gases[i]->setText(QString()); gases[i]->setVisible(datapoint.entered && @@ -1080,8 +1076,10 @@ void ProfileWidget2::updateDurationLine(PictureEntry &e) // This function is called asynchronously by the thumbnailer if a thumbnail // was fetched from disk or freshly calculated. -void ProfileWidget2::updateThumbnail(QString filename, QImage thumbnail, duration_t duration) +void ProfileWidget2::updateThumbnail(QString filenameIn, QImage thumbnail, duration_t duration) { + std::string filename = filenameIn.toStdString(); + // Find the picture with the given filename auto it = std::find_if(pictures.begin(), pictures.end(), [&filename](const PictureEntry &e) { return e.filename == filename; }); @@ -1104,8 +1102,7 @@ void ProfileWidget2::updateThumbnail(QString filename, QImage thumbnail, duratio } // Create a PictureEntry object and add its thumbnail to the scene if profile pictures are shown. -ProfileWidget2::PictureEntry::PictureEntry(offset_t offsetIn, const QString &filenameIn, ProfileWidget2 *profile, bool synchronous) : offset(offsetIn), - duration(duration_t {0}), +ProfileWidget2::PictureEntry::PictureEntry(offset_t offsetIn, const std::string &filenameIn, ProfileWidget2 *profile, bool synchronous) : offset(offsetIn), filename(filenameIn), thumbnail(new DivePictureItem) { @@ -1113,9 +1110,9 @@ ProfileWidget2::PictureEntry::PictureEntry(offset_t offsetIn, const QString &fil int size = Thumbnailer::defaultThumbnailSize(); scene->addItem(thumbnail.get()); thumbnail->setVisible(prefs.show_pictures_in_profile); - QImage img = Thumbnailer::instance()->fetchThumbnail(filename, synchronous).scaled(size, size, Qt::KeepAspectRatio); + QImage img = Thumbnailer::instance()->fetchThumbnail(QString::fromStdString(filename), synchronous).scaled(size, size, Qt::KeepAspectRatio); thumbnail->setPixmap(QPixmap::fromImage(img)); - thumbnail->setFileUrl(filename); + thumbnail->setFileUrl(QString::fromStdString(filename)); connect(thumbnail.get(), &DivePictureItem::removePicture, profile, &ProfileWidget2::removePicture); } @@ -1208,13 +1205,15 @@ void ProfileWidget2::plotPicturesInternal(const struct dive *d, bool synchronous if (currentState == EDIT || currentState == PLAN) return; + if (!d) + return; + // Fetch all pictures of the dive, but consider only those that are within the dive time. // For each picture, create a PictureEntry object in the pictures-vector. // emplace_back() constructs an object at the end of the vector. The parameters are passed directly to the constructor. - // Note that FOR_EACH_PICTURE handles d being null gracefully. - FOR_EACH_PICTURE(d) { - if (picture->offset.seconds > 0 && picture->offset.seconds <= d->duration.seconds) - pictures.emplace_back(picture->offset, QString(picture->filename), this, synchronous); + for (auto &picture: d->pictures) { + if (picture.offset.seconds > 0 && picture.offset.seconds <= d->duration.seconds) + pictures.emplace_back(picture.offset, picture.filename, this, synchronous); } if (pictures.empty()) return; @@ -1243,16 +1242,16 @@ void ProfileWidget2::picturesRemoved(dive *d, QVector fileUrls) // (c.f. erase-remove idiom: https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom) auto it = std::remove_if(pictures.begin(), pictures.end(), [&fileUrls](const PictureEntry &e) // Check whether filename of entry is in list of provided filenames - { return std::find(fileUrls.begin(), fileUrls.end(), e.filename) != fileUrls.end(); }); + { return std::find(fileUrls.begin(), fileUrls.end(), QString::fromStdString(e.filename)) != fileUrls.end(); }); pictures.erase(it, pictures.end()); calculatePictureYPositions(); } -void ProfileWidget2::picturesAdded(dive *d, QVector pics) +void ProfileWidget2::picturesAdded(dive *d, QVector pics) { - for (const PictureObj &pic: pics) { + for (const picture &pic: pics) { if (pic.offset.seconds > 0 && pic.offset.seconds <= d->duration.seconds) { - pictures.emplace_back(pic.offset, QString::fromStdString(pic.filename), this, false); + pictures.emplace_back(pic.offset, pic.filename, this, false); updateThumbnailXPos(pictures.back()); } } @@ -1289,7 +1288,7 @@ void ProfileWidget2::dropEvent(QDropEvent *event) QString filename; dataStream >> filename; QPointF mappedPos = mapToScene(event->pos()); - offset_t offset { (int32_t)lrint(profileScene->timeAxis->valueAt(mappedPos)) }; + offset_t offset { .seconds = (int32_t)lrint(profileScene->timeAxis->valueAt(mappedPos)) }; Command::setPictureOffset(mutable_dive(), filename, offset); if (event->source() == this) { @@ -1305,11 +1304,13 @@ void ProfileWidget2::dropEvent(QDropEvent *event) } #ifndef SUBSURFACE_MOBILE -void ProfileWidget2::pictureOffsetChanged(dive *dIn, QString filename, offset_t offset) +void ProfileWidget2::pictureOffsetChanged(dive *dIn, QString filenameIn, offset_t offset) { if (dIn != d) return; // Picture of a different dive than the one shown changed. + std::string filename = filenameIn.toStdString(); // TODO: can we move std::string through Qt's signal/slot system? + // Calculate time in dive where picture was dropped and whether the new position is during the dive. bool duringDive = d && offset.seconds > 0 && offset.seconds < d->duration.seconds; @@ -1333,7 +1334,7 @@ void ProfileWidget2::pictureOffsetChanged(dive *dIn, QString filename, offset_t auto newPos = std::find_if(pictures.begin(), pictures.end(), [offset, &filename](const PictureEntry &e) { return std::tie(e.offset.seconds, e.filename) > std::tie(offset.seconds, filename); }); // Set new offset - oldPos->offset.seconds = offset.seconds; + oldPos->offset = offset; updateThumbnailXPos(*oldPos); // Move image from old to new position diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index 7b727b040..8d32a2230 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -17,7 +17,6 @@ // * It needs to be dynamic, things should *flow* on it, not just appear / disappear. // */ #include "profile-widget/divelineitem.h" -#include "core/pictureobj.h" #include "core/units.h" #include "core/subsurface-qt/divelistnotifier.h" @@ -78,7 +77,7 @@ slots: // Necessary to call from QAction's signals. #ifndef SUBSURFACE_MOBILE void plotPictures(); void picturesRemoved(dive *d, QVector filenames); - void picturesAdded(dive *d, QVector pics); + void picturesAdded(dive *d, QVector pics); void pointsReset(); void pointInserted(const QModelIndex &parent, int start, int end); void pointsRemoved(const QModelIndex &, int start, int end); @@ -105,6 +104,7 @@ private: void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void keyPressEvent(QKeyEvent *e) override; + void addGasChangeMenu(QMenu &m, QString menuTitle, const struct dive &d, int dcNr, int changeTime); #endif void dropEvent(QDropEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; @@ -126,7 +126,7 @@ private: void splitDive(int seconds); void addSetpointChange(int seconds); void removeEvent(DiveEventItem *item); - void hideEvent(DiveEventItem *item); + void hideOneEvent(DiveEventItem *item); void hideEventType(DiveEventItem *item); void editName(DiveEventItem *item); void unhideEvents(); @@ -163,11 +163,11 @@ private: struct PictureEntry { offset_t offset; duration_t duration; - QString filename; + std::string filename; std::unique_ptr thumbnail; // For videos with known duration, we represent the duration of the video by a line std::unique_ptr durationLine; - PictureEntry (offset_t offsetIn, const QString &filenameIn, ProfileWidget2 *profile, bool synchronous); + PictureEntry (offset_t offsetIn, const std::string &filenameIn, ProfileWidget2 *profile, bool synchronous); bool operator< (const PictureEntry &e) const; }; void updateThumbnailXPos(PictureEntry &e); diff --git a/profile-widget/qmlprofile.cpp b/profile-widget/qmlprofile.cpp index e085fe5bb..f82ba188f 100644 --- a/profile-widget/qmlprofile.cpp +++ b/profile-widget/qmlprofile.cpp @@ -2,6 +2,7 @@ #include "qmlprofile.h" #include "profilescene.h" #include "mobile-widgets/qmlmanager.h" +#include "core/divelist.h" #include "core/errorhelper.h" #include "core/subsurface-float.h" #include "core/metrics.h" @@ -60,7 +61,7 @@ void QMLProfile::paint(QPainter *painter) painter->resetTransform(); if (m_diveId < 0) return; - struct dive *d = get_dive_by_uniq_id(m_diveId); + struct dive *d = divelog.dives.get_by_uniq_id(m_diveId); if (!d) return; m_profileWidget->draw(painter, painterRect, d, m_dc, nullptr, false); @@ -143,10 +144,10 @@ void QMLProfile::prevDC() void QMLProfile::rotateDC(int dir) { - struct dive *d = get_dive_by_uniq_id(m_diveId); + struct dive *d = divelog.dives.get_by_uniq_id(m_diveId); if (!d) return; - int numDC = number_of_computers(d); + int numDC = d->number_of_computers(); if (numDC == 1) return; m_dc = (m_dc + dir) % numDC; @@ -157,6 +158,6 @@ void QMLProfile::rotateDC(int dir) int QMLProfile::numDC() const { - struct dive *d = get_dive_by_uniq_id(m_diveId); - return d ? number_of_computers(d) : 0; + struct dive *d = divelog.dives.get_by_uniq_id(m_diveId); + return d ? d->number_of_computers() : 0; } diff --git a/profile-widget/ruleritem.cpp b/profile-widget/ruleritem.cpp index b431749c6..755d24b12 100644 --- a/profile-widget/ruleritem.cpp +++ b/profile-widget/ruleritem.cpp @@ -113,7 +113,7 @@ void RulerItem2::recalculate() setLine(line); QString text; - for (const std::string &s: compare_samples(dive, pInfo, source->idx, dest->idx, 1)) { + for (const std::string &s: compare_samples(dive, *pInfo, source->idx, dest->idx, 1)) { if (!text.isEmpty()) text += '\n'; text += QString::fromStdString(s); diff --git a/profile-widget/tankitem.cpp b/profile-widget/tankitem.cpp index 1955ef52e..b4d280d4c 100644 --- a/profile-widget/tankitem.cpp +++ b/profile-widget/tankitem.cpp @@ -57,7 +57,7 @@ void TankItem::createBar(int startTime, int stopTime, struct gasmix gas) rect->setPen(QPen(QBrush(), 0.0)); // get rid of the thick line around the rectangle rects.push_back(rect); DiveTextItem *label = new DiveTextItem(dpr, 1.0, Qt::AlignVCenter | Qt::AlignRight, rect); - label->set(gasname(gas), Qt::black); + label->set(QString::fromStdString(gas.name()), Qt::black); label->setPos(x + 2.0 * dpr, height() / 2.0); label->setZValue(101); } @@ -76,21 +76,20 @@ void TankItem::setData(const struct dive *d, const struct divecomputer *dc, int return; // Bail if there are no cylinders - if (d->cylinders.nr <= 0) + if (d->cylinders.empty()) return; // start with the first gasmix and at the start of the plotted range - const struct event *ev = NULL; - struct gasmix gasmix = gasmix_air; - gasmix = get_gasmix(d, dc, plotStartTime, &ev, gasmix); - - // work through all the gas changes and add the rectangle for each gas while it was used - int startTime = plotStartTime; - while (ev && (int)ev->time.seconds < plotEndTime) { - createBar(startTime, ev->time.seconds, gasmix); - startTime = ev->time.seconds; - gasmix = get_gasmix_from_event(d, ev); - ev = get_next_event(ev->next, "gaschange"); + // and work through all the gas changes and add the rectangle for each gas while it was used + gasmix_loop loop(*d, *dc); + struct gasmix next_gasmix = loop.at(plotStartTime).first; + int next_startTime = plotStartTime; + while (loop.has_next()) { + auto [gasmix, time] = loop.next(); + createBar(next_startTime, time, next_gasmix); + next_startTime = time; + next_gasmix = gasmix; } - createBar(startTime, plotEndTime, gasmix); + + createBar(next_startTime, plotEndTime, next_gasmix); } diff --git a/qt-models/completionmodels.cpp b/qt-models/completionmodels.cpp index 3f5210b73..b50e26da6 100644 --- a/qt-models/completionmodels.cpp +++ b/qt-models/completionmodels.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 #include "qt-models/completionmodels.h" #include "core/dive.h" +#include "core/divelist.h" +#include "core/divelog.h" #include "core/tag.h" #include @@ -28,13 +30,11 @@ void CompletionModelBase::divesChanged(const QVector &, DiveField field) updateModel(); } -static QStringList getCSVList(char *dive::*item) +static QStringList getCSVList(const std::string dive::*item) { QSet set; - struct dive *dive; - int i = 0; - for_each_dive (i, dive) { - QString str(dive->*item); + for (auto &dive: divelog.dives) { + QString str = QString::fromStdString(dive.get()->*item); for (const QString &value: str.split(",", SKIP_EMPTY)) set.insert(value.trimmed()); } @@ -66,10 +66,8 @@ bool DiveGuideCompletionModel::relevantDiveField(const DiveField &f) QStringList SuitCompletionModel::getStrings() { QStringList list; - struct dive *dive; - int i = 0; - for_each_dive (i, dive) { - QString suit(dive->suit); + for (auto &dive: divelog.dives) { + QString suit = QString::fromStdString(dive->suit); if (!list.contains(suit)) list.append(suit); } diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index caefc82a0..1d3513838 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -6,6 +6,7 @@ #include "core/color.h" #include "qt-models/diveplannermodel.h" #include "core/gettextfromc.h" +#include "core/range.h" #include "core/sample.h" #include "core/selection.h" #include "core/subsurface-qt/divelistnotifier.h" @@ -17,8 +18,7 @@ CylindersModel::CylindersModel(bool planner, QObject *parent) : CleanerTableMode dcNr(-1), inPlanner(planner), numRows(0), - tempRow(-1), - tempCyl(empty_cylinder) + tempRow(-1) { // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH, MOD, MND, USE, WORKINGPRESS_INT, SIZE_INT, SENSORS}; setHeaderDataStrings(QStringList() << "" << tr("Type") << tr("Size") << tr("Work press.") << tr("Start press.") << tr("End press.") << tr("O₂%") << tr("He%") @@ -64,13 +64,13 @@ static QString get_cylinder_string(const cylinder_t *cyl) return QString("%L1").arg(value, 0, 'f', decimals) + unit; } -static QString gas_volume_string(int ml, const char *tail) +static QString gas_volume_string(volume_t volume, const char *tail) { double vol; const char *unit; int decimals; - vol = get_volume_units(ml, NULL, &unit); + vol = get_volume_units(volume.mliter, NULL, &unit); decimals = (vol > 20.0) ? 0 : (vol > 2.0) ? 1 : 2; return QString("%L1 %2 %3").arg(vol, 0, 'f', decimals).arg(unit).arg(tail); @@ -83,13 +83,12 @@ static QVariant gas_usage_tooltip(const cylinder_t *cyl) pressure_t startp = cyl->start.mbar ? cyl->start : cyl->sample_start; pressure_t endp = cyl->end.mbar ? cyl->end : cyl->sample_end; - int start, end, used; + volume_t start = cyl->gas_volume(startp); + volume_t end = cyl->gas_volume(endp); + // TOOO: implement comparison and subtraction on units.h types. + volume_t used = (end.mliter && start.mliter > end.mliter) ? volume_t { .mliter = start.mliter - end.mliter } : volume_t(); - start = gas_volume(cyl, startp); - end = gas_volume(cyl, endp); - used = (end && start > end) ? start - end : 0; - - if (!used) + if (!used.mliter) return gas_wp_tooltip(cyl); return gas_volume_string(used, "(") + @@ -99,10 +98,10 @@ static QVariant gas_usage_tooltip(const cylinder_t *cyl) static QVariant gas_volume_tooltip(const cylinder_t *cyl, pressure_t p) { - int vol = gas_volume(cyl, p); + volume_t vol = cyl->gas_volume(p); double Z; - if (!vol) + if (!vol.mliter) return QVariant(); Z = gas_compressibility_factor(cyl->gasmix, p.mbar / 1000.0); @@ -141,7 +140,7 @@ int CylindersModel::calcNumRows() const if (!d) return 0; if (inPlanner || prefs.include_unused_tanks) - return d->cylinders.nr; + return static_cast(d->cylinders.size()); return first_hidden_cylinder(d); } @@ -155,27 +154,36 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const return QVariant(); } - const cylinder_t *cyl = index.row() == tempRow ? &tempCyl : get_cylinder(d, index.row()); + const cylinder_t *cyl = index.row() == tempRow ? &tempCyl : d->get_cylinder(index.row()); + bool isInappropriateUse = !is_cylinder_use_appropriate(*d->get_dc(dcNr), *cyl, true); switch (role) { - case Qt::BackgroundRole: { + case Qt::BackgroundRole: switch (index.column()) { // mark the cylinder start / end pressure in red if the values // seem implausible case START: case END: - pressure_t startp, endp; - startp = cyl->start.mbar ? cyl->start : cyl->sample_start; - endp = cyl->end.mbar ? cyl->end : cyl->sample_end; - if ((startp.mbar && !endp.mbar) || - (endp.mbar && startp.mbar <= endp.mbar)) + { + pressure_t startp = cyl->start.mbar ? cyl->start : cyl->sample_start; + pressure_t endp = cyl->end.mbar ? cyl->end : cyl->sample_end; + if ((startp.mbar && !endp.mbar) || + (endp.mbar && startp.mbar <= endp.mbar)) + return REDORANGE1_HIGH_TRANS; + } + + break; + case USE: + if (isInappropriateUse) return REDORANGE1_HIGH_TRANS; + break; } + break; - } case Qt::FontRole: { QFont font = defaultModelFont(); + switch (index.column()) { // if we don't have manually set pressure data use italic font case START: @@ -185,6 +193,9 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const font.setItalic(!cyl->end.mbar); break; } + + font.setItalic(isInappropriateUse); + return font; } case Qt::TextAlignmentRole: @@ -193,7 +204,7 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const case Qt::EditRole: switch (index.column()) { case TYPE: - return QString(cyl->type.description); + return QString::fromStdString(cyl->type.description); case SIZE: if (cyl->type.size.mliter) return get_cylinder_string(cyl); @@ -226,13 +237,13 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const } else { pressure_t modpO2; modpO2.mbar = inPlanner ? prefs.bottompo2 : (int)(prefs.modpO2 * 1000.0); - return get_depth_string(gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(1,1)), true); + return get_depth_string(d->gas_mod(cyl->gasmix, modpO2, M_OR_FT(1,1)), true); } case MND: if (cyl->bestmix_he) return QStringLiteral("*"); else - return get_depth_string(gas_mnd(cyl->gasmix, prefs.bestmixend, d, M_OR_FT(1,1)), true); + return get_depth_string(d->gas_mnd(cyl->gasmix, prefs.bestmixend, M_OR_FT(1,1)), true); break; case USE: return gettextFromC::tr(cylinderuse_text[cyl->cylinder_use]); @@ -242,9 +253,8 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const return static_cast(cyl->type.size.mliter); case SENSORS: { std::vector sensors; - const struct divecomputer *currentdc = get_dive_dc(d, dcNr); - for (int i = 0; i < currentdc->samples; ++i) { - auto &sample = currentdc->sample[i]; + const struct divecomputer *currentdc = d->get_dc(dcNr); + for (const auto &sample: currentdc->samples) { for (int s = 0; s < MAX_SENSORS; ++s) { if (sample.pressure[s].mbar) { if (sample.sensor[s] == index.row()) @@ -260,7 +270,7 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const case Qt::SizeHintRole: if (index.column() == REMOVE) { if ((inPlanner && DivePlannerPointsModel::instance()->tankInUse(index.row())) || - (!inPlanner && is_cylinder_prot(d, index.row()))) { + (!inPlanner && d->is_cylinder_prot(index.row()))) { return trashForbiddenIcon(); } return trashIcon(); @@ -270,7 +280,7 @@ QVariant CylindersModel::data(const QModelIndex &index, int role) const switch (index.column()) { case REMOVE: if ((inPlanner && DivePlannerPointsModel::instance()->tankInUse(index.row())) || - (!inPlanner && is_cylinder_prot(d, index.row()))) { + (!inPlanner && d->is_cylinder_prot(index.row()))) { return tr("This gas is in use. Only cylinders that are not used in the dive can be removed."); } return tr("Clicking here will remove this cylinder."); @@ -302,7 +312,7 @@ cylinder_t *CylindersModel::cylinderAt(const QModelIndex &index) { if (!d) return nullptr; - return get_cylinder(d, index.row()); + return d->get_cylinder(index.row()); } bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, int role) @@ -328,10 +338,9 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in switch (index.column()) { case TYPE: { - QString type = value.toString(); - if (!same_string(qPrintable(type), tempCyl.type.description)) { - free((void *)tempCyl.type.description); - tempCyl.type.description = strdup(qPrintable(type)); + std::string type = value.toString().toStdString(); + if (type != tempCyl.type.description) { + tempCyl.type.description = type; dataChanged(index, index); } return true; @@ -363,12 +372,10 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in QString vString = value.toString(); bool changed = vString != data(index, role).toString(); - std::string newType; // If we allocate a new type string, this makes sure that it is freed at the end of the function - // First, we make a shallow copy of the old cylinder. Then we modify the fields inside that copy. // At the end, we either place an EditCylinder undo command (EquipmentTab) or copy the cylinder back (planner). // Yes, this is not ideal, but the pragmatic thing to do for now. - cylinder_t cyl = *get_cylinder(d, row); + cylinder_t cyl = *d->get_cylinder(row); if (index.column() != TYPE && !changed) return false; @@ -376,8 +383,7 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in Command::EditCylinderType type = Command::EditCylinderType::TYPE; switch (index.column()) { case TYPE: - newType = qPrintable(vString); - cyl.type.description = newType.c_str(); + cyl.type.description = vString.toStdString(); type = Command::EditCylinderType::TYPE; break; case SIZE: @@ -403,12 +409,12 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (get_o2(cyl.gasmix) + get_he(cyl.gasmix) > 1000) cyl.gasmix.he.permille = 1000 - get_o2(cyl.gasmix); pressure_t modpO2; - if (d->dc.divemode == PSCR) - modpO2.mbar = prefs.decopo2 + (1000 - get_o2(cyl.gasmix)) * SURFACE_PRESSURE * + if (d->dcs[0].divemode == PSCR) + modpO2.mbar = prefs.decopo2 + (1000 - get_o2(cyl.gasmix)) * (1_atm).mbar * prefs.o2consumption / prefs.decosac / prefs.pscr_ratio; else modpO2.mbar = prefs.decopo2; - cyl.depth = gas_mod(cyl.gasmix, modpO2, d, M_OR_FT(3, 10)); + cyl.depth = d->gas_mod(cyl.gasmix, modpO2, M_OR_FT(3, 10)); cyl.bestmix_o2 = false; } type = Command::EditCylinderType::GASMIX; @@ -429,15 +435,15 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (QString::compare(qPrintable(vString), "*") == 0) { cyl.bestmix_o2 = true; // Calculate fO2 for max. depth - cyl.gasmix.o2 = best_o2(d->maxdepth, d, inPlanner); + cyl.gasmix.o2 = d->best_o2(d->maxdepth, inPlanner); } else { cyl.bestmix_o2 = false; // Calculate fO2 for input depth - cyl.gasmix.o2 = best_o2(string_to_depth(qPrintable(vString)), d, inPlanner); + cyl.gasmix.o2 = d->best_o2(string_to_depth(qPrintable(vString)), inPlanner); } pressure_t modpO2; modpO2.mbar = prefs.decopo2; - cyl.depth = gas_mod(cyl.gasmix, modpO2, d, M_OR_FT(3, 10)); + cyl.depth = d->gas_mod(cyl.gasmix, modpO2, M_OR_FT(3, 10)); } type = Command::EditCylinderType::GASMIX; break; @@ -445,11 +451,11 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (QString::compare(qPrintable(vString), "*") == 0) { cyl.bestmix_he = true; // Calculate fO2 for max. depth - cyl.gasmix.he = best_he(d->maxdepth, d, prefs.o2narcotic, make_fraction(get_o2(cyl.gasmix))); + cyl.gasmix.he = d->best_he(d->maxdepth, prefs.o2narcotic, make_fraction(get_o2(cyl.gasmix))); } else { cyl.bestmix_he = false; // Calculate fHe for input depth - cyl.gasmix.he = best_he(string_to_depth(qPrintable(vString)), d, prefs.o2narcotic, make_fraction(get_o2(cyl.gasmix))); + cyl.gasmix.he = d->best_he(string_to_depth(qPrintable(vString)), prefs.o2narcotic, make_fraction(get_o2(cyl.gasmix))); } type = Command::EditCylinderType::GASMIX; break; @@ -475,14 +481,11 @@ bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, in if (inPlanner) { // In the planner - simply overwrite the cylinder in the dive with the modified cylinder. - // We have only made a shallow copy, therefore copy the new cylinder first. - cylinder_t copy = clone_cylinder(cyl); - std::swap(copy, *get_cylinder(d, row)); - free_cylinder(copy); + *d->get_cylinder(row) = std::move(cyl); dataChanged(index, index); } else { // On the EquipmentTab - place an editCylinder command. - int count = Command::editCylinder(index.row(), cyl, type, false); + int count = Command::editCylinder(index.row(), std::move(cyl), type, false); emit divesEdited(count); } return true; @@ -498,10 +501,10 @@ void CylindersModel::add() { if (!d) return; - int row = d->cylinders.nr; + int row = static_cast(d->cylinders.size()); cylinder_t cyl = create_new_manual_cylinder(d); beginInsertRows(QModelIndex(), row, row); - add_cylinder(&d->cylinders, row, cyl); + d->cylinders.add(row, std::move(cyl)); ++numRows; endInsertRows(); emit dataChanged(createIndex(row, 0), createIndex(row, COLUMNS - 1)); @@ -554,8 +557,8 @@ void CylindersModel::remove(QModelIndex index) --numRows; endRemoveRows(); - std::vector mapping = get_cylinder_map_for_remove(d->cylinders.nr + 1, index.row()); - cylinder_renumber(d, mapping.data()); + std::vector mapping = get_cylinder_map_for_remove(static_cast(d->cylinders.size() + 1), index.row()); + cylinder_renumber(*d, mapping.data()); DivePlannerPointsModel::instance()->cylinderRenumber(mapping.data()); } @@ -614,25 +617,22 @@ void CylindersModel::updateNumRows() // Only invoked from planner. void CylindersModel::moveAtFirst(int cylid) { - if (!d || cylid <= 0 || cylid >= d->cylinders.nr) + if (!d || cylid <= 0 || cylid >= static_cast(d->cylinders.size())) return; cylinder_t temp_cyl; beginMoveRows(QModelIndex(), cylid, cylid, QModelIndex(), 0); - memmove(&temp_cyl, get_cylinder(d, cylid), sizeof(temp_cyl)); - for (int i = cylid - 1; i >= 0; i--) - memmove(get_cylinder(d, i + 1), get_cylinder(d, i), sizeof(temp_cyl)); - memmove(get_cylinder(d, 0), &temp_cyl, sizeof(temp_cyl)); + move_in_range(d->cylinders, cylid, cylid + 1, 0); // Create a mapping of cylinder indices: // 1) Fill mapping[0]..mapping[cyl] with 0..index // 2) Set mapping[cyl] to 0 // 3) Fill mapping[cyl+1]..mapping[end] with cyl.. - std::vector mapping(d->cylinders.nr); + std::vector mapping(d->cylinders.size()); std::iota(mapping.begin(), mapping.begin() + cylid, 1); mapping[cylid] = 0; std::iota(mapping.begin() + (cylid + 1), mapping.end(), cylid); - cylinder_renumber(d, mapping.data()); + cylinder_renumber(*d, mapping.data()); if (inPlanner) DivePlannerPointsModel::instance()->cylinderRenumber(mapping.data()); endMoveRows(); @@ -646,12 +646,11 @@ void CylindersModel::updateDecoDepths(pressure_t olddecopo2) pressure_t decopo2; decopo2.mbar = prefs.decopo2; - for (int i = 0; i < d->cylinders.nr; i++) { - cylinder_t *cyl = get_cylinder(d, i); + for (auto &cyl: d->cylinders) { /* If the gas's deco MOD matches the old pO2, it will have been automatically calculated and should be updated. * If they don't match, we should leave the user entered depth as it is */ - if (cyl->depth.mm == gas_mod(cyl->gasmix, olddecopo2, d, M_OR_FT(3, 10)).mm) { - cyl->depth = gas_mod(cyl->gasmix, decopo2, d, M_OR_FT(3, 10)); + if (cyl.depth.mm == d->gas_mod(cyl.gasmix, olddecopo2, M_OR_FT(3, 10)).mm) { + cyl.depth = d->gas_mod(cyl.gasmix, decopo2, M_OR_FT(3, 10)); } } emit dataChanged(createIndex(0, 0), createIndex(numRows - 1, COLUMNS - 1)); @@ -673,23 +672,22 @@ bool CylindersModel::updateBestMixes() // Check if any of the cylinders are best mixes, update if needed bool gasUpdated = false; - for (int i = 0; i < d->cylinders.nr; i++) { - cylinder_t *cyl = get_cylinder(d, i); - if (cyl->bestmix_o2) { - cyl->gasmix.o2 = best_o2(d->maxdepth, d, inPlanner); + for (auto &cyl: d->cylinders) { + if (cyl.bestmix_o2) { + cyl.gasmix.o2 = d->best_o2(d->maxdepth, inPlanner); // fO2 + fHe must not be greater than 1 - if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) - cyl->gasmix.he.permille = 1000 - get_o2(cyl->gasmix); + if (get_o2(cyl.gasmix) + get_he(cyl.gasmix) > 1000) + cyl.gasmix.he.permille = 1000 - get_o2(cyl.gasmix); pressure_t modpO2; modpO2.mbar = prefs.decopo2; - cyl->depth = gas_mod(cyl->gasmix, modpO2, d, M_OR_FT(3, 10)); + cyl.depth = d->gas_mod(cyl.gasmix, modpO2, M_OR_FT(3, 10)); gasUpdated = true; } - if (cyl->bestmix_he) { - cyl->gasmix.he = best_he(d->maxdepth, d, prefs.o2narcotic, cyl->gasmix.o2); + if (cyl.bestmix_he) { + cyl.gasmix.he = d->best_he(d->maxdepth, prefs.o2narcotic, cyl.gasmix.o2); // fO2 + fHe must not be greater than 1 - if (get_o2(cyl->gasmix) + get_he(cyl->gasmix) > 1000) - cyl->gasmix.o2.permille = 1000 - get_he(cyl->gasmix); + if (get_o2(cyl.gasmix) + get_he(cyl.gasmix) > 1000) + cyl.gasmix.o2.permille = 1000 - get_he(cyl.gasmix); gasUpdated = true; } } @@ -724,12 +722,12 @@ void CylindersModel::initTempCyl(int row) if (!d || tempRow == row) return; clearTempCyl(); - const cylinder_t *cyl = get_cylinder(d, row); + const cylinder_t *cyl = d->get_cylinder(row); if (!cyl) return; tempRow = row; - tempCyl = clone_cylinder(*cyl); + tempCyl = *cyl; dataChanged(index(row, TYPE), index(row, USE)); } @@ -740,7 +738,7 @@ void CylindersModel::clearTempCyl() return; int oldRow = tempRow; tempRow = -1; - free_cylinder(tempCyl); + tempCyl = cylinder_t(); dataChanged(index(oldRow, TYPE), index(oldRow, USE)); } @@ -750,11 +748,11 @@ void CylindersModel::commitTempCyl(int row) return; if (row != tempRow) return clearTempCyl(); // Huh? We are supposed to commit a different row than the one we stored? - cylinder_t *cyl = get_cylinder(d, tempRow); + cylinder_t *cyl = d->get_cylinder(tempRow); if (!cyl) return; // Only submit a command if the type changed - if (!same_string(cyl->type.description, tempCyl.type.description) || gettextFromC::tr(cyl->type.description) != QString(tempCyl.type.description)) { + if (cyl->type.description != tempCyl.type.description || gettextFromC::tr(cyl->type.description.c_str()) != QString::fromStdString(tempCyl.type.description)) { if (inPlanner) { std::swap(*cyl, tempCyl); } else { @@ -762,6 +760,6 @@ void CylindersModel::commitTempCyl(int row) emit divesEdited(count); } } - free_cylinder(tempCyl); + tempCyl = cylinder_t(); tempRow = -1; } diff --git a/qt-models/divecomputerextradatamodel.cpp b/qt-models/divecomputerextradatamodel.cpp index d0cf2d98d..a47e6af82 100644 --- a/qt-models/divecomputerextradatamodel.cpp +++ b/qt-models/divecomputerextradatamodel.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include "qt-models/divecomputerextradatamodel.h" #include "core/divecomputer.h" -#include "core/extradata.h" #include "core/metrics.h" ExtraDataModel::ExtraDataModel(QObject *parent) : CleanerTableModel(parent) @@ -21,7 +20,7 @@ QVariant ExtraDataModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() > (int)items.size()) return QVariant(); - const Item &item = items[index.row()]; + const extra_data &item = items[index.row()]; switch (role) { case Qt::FontRole: @@ -31,9 +30,9 @@ QVariant ExtraDataModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: switch (index.column()) { case KEY: - return item.key; + return QString::fromStdString(item.key); case VALUE: - return item.value; + return QString::fromStdString(item.value); } return QVariant(); } @@ -48,11 +47,9 @@ int ExtraDataModel::rowCount(const QModelIndex&) const void ExtraDataModel::updateDiveComputer(const struct divecomputer *dc) { beginResetModel(); - struct extra_data *ed = dc ? dc->extra_data : nullptr; - items.clear(); - while (ed) { - items.push_back({ ed->key, ed->value }); - ed = ed->next; - } + if (dc) + items = dc->extra_data; + else + items.clear(); endResetModel(); } diff --git a/qt-models/divecomputerextradatamodel.h b/qt-models/divecomputerextradatamodel.h index 9af4028eb..be1d8bb40 100644 --- a/qt-models/divecomputerextradatamodel.h +++ b/qt-models/divecomputerextradatamodel.h @@ -3,6 +3,7 @@ #define DIVECOMPUTEREXTRADATAMODEL_H #include "cleanertablemodel.h" +#include "core/extradata.h" struct divecomputer; @@ -22,11 +23,7 @@ public: void updateDiveComputer(const struct divecomputer *dc); private: - struct Item { - QString key; - QString value; - }; - std::vector items; + std::vector items; }; #endif diff --git a/qt-models/diveimportedmodel.cpp b/qt-models/diveimportedmodel.cpp index d869e925d..796e82818 100644 --- a/qt-models/diveimportedmodel.cpp +++ b/qt-models/diveimportedmodel.cpp @@ -16,7 +16,7 @@ int DiveImportedModel::columnCount(const QModelIndex&) const int DiveImportedModel::rowCount(const QModelIndex&) const { - return log.dives->nr; + return static_cast(log.dives.size()); } QVariant DiveImportedModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -46,15 +46,10 @@ QVariant DiveImportedModel::headerData(int section, Qt::Orientation orientation, QVariant DiveImportedModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) + if (index.row() < 0 || static_cast(index.row()) >= log.dives.size()) return QVariant(); - if (index.row() >= log.dives->nr) - return QVariant(); - - struct dive *d = get_dive_from_table(index.row(), log.dives); - if (!d) - return QVariant(); + const struct dive &d = *log.dives[index.row()]; // widgets access the model via index.column(), qml via role. int column = index.column(); @@ -66,11 +61,11 @@ QVariant DiveImportedModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole) { switch (column) { case 0: - return QVariant(get_short_dive_date_string(d->when)); + return QVariant(get_short_dive_date_string(d.when)); case 1: - return QVariant(get_dive_duration_string(d->duration.seconds, tr("h"), tr("min"))); + return QVariant(get_dive_duration_string(d.duration.seconds, tr("h"), tr("min"))); case 2: - return QVariant(get_depth_string(d->maxdepth.mm, true, false)); + return QVariant(get_depth_string(d.maxdepth.mm, true, false)); case 3: return checkStates[index.row()]; } @@ -90,8 +85,10 @@ void DiveImportedModel::changeSelected(QModelIndex clickedIndex) void DiveImportedModel::selectAll() { + if (log.dives.empty()) + return; std::fill(checkStates.begin(), checkStates.end(), true); - dataChanged(index(0, 0), index(log.dives->nr - 1, 0), QVector() << Qt::CheckStateRole << Selected); + dataChanged(index(0, 0), index(log.dives.size() - 1, 0), QVector() << Qt::CheckStateRole << Selected); } void DiveImportedModel::selectRow(int row) @@ -102,8 +99,10 @@ void DiveImportedModel::selectRow(int row) void DiveImportedModel::selectNone() { + if (log.dives.empty()) + return; std::fill(checkStates.begin(), checkStates.end(), false); - dataChanged(index(0, 0), index(log.dives->nr - 1, 0 ), QVector() << Qt::CheckStateRole << Selected); + dataChanged(index(0, 0), index(log.dives.size() - 1, 0 ), QVector() << Qt::CheckStateRole << Selected); } Qt::ItemFlags DiveImportedModel::flags(const QModelIndex &index) const @@ -116,7 +115,7 @@ Qt::ItemFlags DiveImportedModel::flags(const QModelIndex &index) const void DiveImportedModel::clearTable() { beginResetModel(); - clear_divelog(&log); + log.clear(); endResetModel(); } @@ -124,10 +123,12 @@ void DiveImportedModel::downloadThreadFinished() { beginResetModel(); - // Move the table data from thread to model - log = std::move(thread.log); + // Move the table data from thread to model. Replace the downloads thread's log + // with an empty log, because it may reuse it. + log.clear(); + std::swap(log, thread.log); - checkStates.resize(log.dives->nr); + checkStates.resize(log.dives.size()); std::fill(checkStates.begin(), checkStates.end(), true); endResetModel(); @@ -150,8 +151,9 @@ struct divelog DiveImportedModel::consumeTables() { beginResetModel(); - // Move tables to result - struct divelog res(std::move(log)); + // Move tables to result and reset local tables (oldschool pre-C++11 flair). + struct divelog res; + std::swap(res, log); // Reset indices checkStates.clear(); @@ -163,24 +165,24 @@ struct divelog DiveImportedModel::consumeTables() int DiveImportedModel::numDives() const { - return log.dives->nr; + return static_cast(log.dives.size()); } // Delete non-selected dives void DiveImportedModel::deleteDeselected() { - int total = log.dives->nr; - int j = 0; - for (int i = 0; i < total; i++) { + size_t total = log.dives.size(); + size_t j = 0; + for (size_t i = 0; i < total; i++) { if (checkStates[i]) { j++; } else { beginRemoveRows(QModelIndex(), j, j); - delete_dive_from_table(log.dives, j); + log.dives.erase(log.dives.begin() + j); endRemoveRows(); } } - checkStates.resize(log.dives->nr); + checkStates.resize(log.dives.size()); std::fill(checkStates.begin(), checkStates.end(), true); } @@ -191,7 +193,7 @@ void DiveImportedModel::recordDives(int flags) deleteDeselected(); struct divelog log = consumeTables(); - if (log.dives->nr > 0) { + if (!log.dives.empty()) { auto data = thread.data(); Command::importDives(&log, flags, data->devName()); } diff --git a/qt-models/diveimportedmodel.h b/qt-models/diveimportedmodel.h index 31316185d..a7874910b 100644 --- a/qt-models/diveimportedmodel.h +++ b/qt-models/diveimportedmodel.h @@ -25,7 +25,7 @@ public: struct divelog consumeTables(); // Returns downloaded tables and resets model. int numDives() const; - Q_INVOKABLE void recordDives(int flags = IMPORT_PREFER_IMPORTED | IMPORT_IS_DOWNLOADED); + Q_INVOKABLE void recordDives(int flags = import_flags::prefer_imported | import_flags::is_downloaded); Q_INVOKABLE void startDownload(); Q_INVOKABLE void waitForDownload(); diff --git a/qt-models/divelocationmodel.cpp b/qt-models/divelocationmodel.cpp index ffa8fe410..9fc2d8cf2 100644 --- a/qt-models/divelocationmodel.cpp +++ b/qt-models/divelocationmodel.cpp @@ -37,7 +37,7 @@ int LocationInformationModel::columnCount(const QModelIndex &) const int LocationInformationModel::rowCount(const QModelIndex &) const { - return divelog.sites->nr; + return (int)divelog.sites.size(); } QVariant LocationInformationModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -81,21 +81,18 @@ Qt::ItemFlags LocationInformationModel::flags(const QModelIndex &index) const return QAbstractItemModel::flags(index); } -QVariant LocationInformationModel::getDiveSiteData(const struct dive_site *ds, int column, int role) +QVariant LocationInformationModel::getDiveSiteData(const struct dive_site &ds, int column, int role) { - if (!ds) - return QVariant(); - switch(role) { case Qt::EditRole: case Qt::DisplayRole: switch(column) { - case DIVESITE: return QVariant::fromValue((dive_site *)ds); // Not nice: casting away const - case NAME: return QString(ds->name); - case NUM_DIVES: return ds->dives.nr; + case DIVESITE: return QVariant::fromValue((dive_site *)&ds); // Not nice: casting away const + case NAME: return QString::fromStdString(ds.name); + case NUM_DIVES: return static_cast(ds.dives.size()); case LOCATION: return "TODO"; - case DESCRIPTION: return QString(ds->description); - case NOTES: return QString(ds->name); + case DESCRIPTION: return QString::fromStdString(ds.description); + case NOTES: return QString::fromStdString(ds.notes); case TAXONOMY: return "TODO"; } break; @@ -111,22 +108,22 @@ QVariant LocationInformationModel::getDiveSiteData(const struct dive_site *ds, i case EDIT: return editIcon(); case REMOVE: return trashIcon(); #endif - case NAME: return dive_site_has_gps_location(ds) ? QIcon(":geotag-icon") : QVariant(); + case NAME: return ds.has_gps_location() ? QIcon(":geotag-icon") : QVariant(); } break; case DIVESITE_ROLE: - return QVariant::fromValue((dive_site *)ds); // Not nice: casting away const + return QVariant::fromValue((dive_site *)&ds); // Not nice: casting away const } return QVariant(); } QVariant LocationInformationModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) + if (!index.isValid() || index.row() >= (int)divelog.sites.size()) return QVariant(); - struct dive_site *ds = get_dive_site(index.row(), divelog.sites); - return getDiveSiteData(ds, index.column(), role); + const auto &ds = (divelog.sites)[index.row()].get(); + return getDiveSiteData(*ds, index.column(), role); } void LocationInformationModel::update() @@ -137,8 +134,8 @@ void LocationInformationModel::update() void LocationInformationModel::diveSiteDiveCountChanged(dive_site *ds) { - int idx = get_divesite_idx(ds, divelog.sites); - if (idx >= 0) + size_t idx = divelog.sites.get_idx(ds); + if (idx != std::string::npos) dataChanged(createIndex(idx, NUM_DIVES), createIndex(idx, NUM_DIVES)); } @@ -162,16 +159,16 @@ void LocationInformationModel::diveSiteDeleted(struct dive_site *, int idx) void LocationInformationModel::diveSiteChanged(struct dive_site *ds, int field) { - int idx = get_divesite_idx(ds, divelog.sites); - if (idx < 0) + size_t idx = divelog.sites.get_idx(ds); + if (idx == std::string::npos) return; dataChanged(createIndex(idx, field), createIndex(idx, field)); } void LocationInformationModel::diveSiteDivesChanged(struct dive_site *ds) { - int idx = get_divesite_idx(ds, divelog.sites); - if (idx < 0) + size_t idx = divelog.sites.get_idx(ds); + if (idx == std::string::npos) return; dataChanged(createIndex(idx, NUM_DIVES), createIndex(idx, NUM_DIVES)); } @@ -181,10 +178,10 @@ bool DiveSiteSortedModel::filterAcceptsRow(int sourceRow, const QModelIndex &sou if (fullText.isEmpty()) return true; - if (sourceRow < 0 || sourceRow > divelog.sites->nr) + if (sourceRow < 0 || sourceRow > (int)divelog.sites.size()) return false; - struct dive_site *ds = divelog.sites->dive_sites[sourceRow]; - QString text = QString(ds->name) + QString(ds->description) + QString(ds->notes); + const auto &ds = (divelog.sites)[sourceRow]; + QString text = QString::fromStdString(ds->name + ds->description + ds->notes); return text.contains(fullText, Qt::CaseInsensitive); } @@ -193,25 +190,30 @@ bool DiveSiteSortedModel::lessThan(const QModelIndex &i1, const QModelIndex &i2) // The source indices correspond to indices in the global dive site table. // Let's access them directly without going via the source model. // Kind of dirty, but less effort. - struct dive_site *ds1 = get_dive_site(i1.row(), divelog.sites); - struct dive_site *ds2 = get_dive_site(i2.row(), divelog.sites); - if (!ds1 || !ds2) // Invalid dive sites compare as different - return false; + + // Be careful to respect proper ordering when sites are invalid. + bool valid1 = i1.row() >= 0 && i1.row() < (int)divelog.sites.size(); + bool valid2 = i2.row() >= 0 && i2.row() < (int)divelog.sites.size(); + if (!valid1 || !valid2) + return valid1 < valid2; + + const auto &ds1 = (divelog.sites)[i1.row()]; + const auto &ds2 = (divelog.sites)[i2.row()]; switch (i1.column()) { case LocationInformationModel::NAME: default: - return QString::localeAwareCompare(QString(ds1->name), QString(ds2->name)) < 0; // TODO: avoid copy + return QString::localeAwareCompare(QString::fromStdString(ds1->name), QString::fromStdString(ds2->name)) < 0; // TODO: avoid copy case LocationInformationModel::DESCRIPTION: { - int cmp = QString::localeAwareCompare(QString(ds1->description), QString(ds2->description)); // TODO: avoid copy + int cmp = QString::localeAwareCompare(QString::fromStdString(ds1->description), QString::fromStdString(ds2->description)); // TODO: avoid copy return cmp != 0 ? cmp < 0 : - QString::localeAwareCompare(QString(ds1->name), QString(ds2->name)) < 0; // TODO: avoid copy + QString::localeAwareCompare(QString::fromStdString(ds1->name), QString::fromStdString(ds2->name)) < 0; // TODO: avoid copy } case LocationInformationModel::NUM_DIVES: { - int cmp = ds1->dives.nr - ds2->dives.nr; + int cmp = static_cast(ds1->dives.size()) - static_cast(ds2->dives.size()); // Since by default nr dives is descending, invert sort direction of names, such that // the names are listed as ascending. return cmp != 0 ? cmp < 0 : - QString::localeAwareCompare(QString(ds1->name), QString(ds2->name)) < 0; // TODO: avoid copy + QString::localeAwareCompare(QString::fromStdString(ds1->name), QString::fromStdString(ds2->name)) < 0; // TODO: avoid copy } } } @@ -230,18 +232,19 @@ QStringList DiveSiteSortedModel::allSiteNames() const // This shouldn't happen, but if model and core get out of sync, // (more precisely: the core has more sites than the model is aware of), // we might get an invalid index. - if (idx < 0 || idx > divelog.sites->nr) { + if (idx < 0 || idx > (int)divelog.sites.size()) { report_info("DiveSiteSortedModel::allSiteNames(): invalid index"); continue; } - locationNames << QString(divelog.sites->dive_sites[idx]->name); + locationNames << QString::fromStdString((divelog.sites)[idx]->name); } return locationNames; } -struct dive_site *DiveSiteSortedModel::getDiveSite(const QModelIndex &idx) +struct dive_site *DiveSiteSortedModel::getDiveSite(const QModelIndex &idx_source) { - return get_dive_site(mapToSource(idx).row(), divelog.sites); + auto idx = mapToSource(idx_source).row(); + return idx >= 0 && idx < (int)divelog.sites.size() ? (divelog.sites)[idx].get() : NULL; } #ifndef SUBSURFACE_MOBILE @@ -294,13 +297,12 @@ bool GPSLocationInformationModel::filterAcceptsRow(int sourceRow, const QModelIn if (!ds || ds == ignoreDs || ds == RECENTLY_ADDED_DIVESITE || !has_location(&ds->location)) return false; - return distance <= 0 ? same_location(&ds->location, &location) - : (int64_t)get_distance(&ds->location, &location) * 1000 <= distance; // We need 64 bit to represent distances in mm + return distance <= 0 ? ds->location == location + : (int64_t)get_distance(ds->location, location) * 1000 <= distance; // We need 64 bit to represent distances in mm } GPSLocationInformationModel::GPSLocationInformationModel(QObject *parent) : QSortFilterProxyModel(parent), ignoreDs(nullptr), - location({{0},{0}}), distance(0) { setSourceModel(LocationInformationModel::instance()); diff --git a/qt-models/divelocationmodel.h b/qt-models/divelocationmodel.h index 57471dee6..adcd425c5 100644 --- a/qt-models/divelocationmodel.h +++ b/qt-models/divelocationmodel.h @@ -19,7 +19,7 @@ public: // Thus, different views can connect to different models. enum Columns { EDIT, REMOVE, NAME, DESCRIPTION, NUM_DIVES, LOCATION, NOTES, DIVESITE, TAXONOMY, COLUMNS }; enum Roles { DIVESITE_ROLE = Qt::UserRole + 1 }; - static QVariant getDiveSiteData(const struct dive_site *ds, int column, int role); + static QVariant getDiveSiteData(const struct dive_site &ds, int column, int role); LocationInformationModel(QObject *obj = 0); static LocationInformationModel *instance(); diff --git a/qt-models/divepicturemodel.cpp b/qt-models/divepicturemodel.cpp index 1d6c0f314..a4ef8a70a 100644 --- a/qt-models/divepicturemodel.cpp +++ b/qt-models/divepicturemodel.cpp @@ -13,17 +13,9 @@ #include #include -PictureEntry::PictureEntry(dive *dIn, const PictureObj &p) : d(dIn), - filename(p.filename), - offsetSeconds(p.offset.seconds), - length({ 0 }) -{ -} - PictureEntry::PictureEntry(dive *dIn, const picture &p) : d(dIn), filename(p.filename), - offsetSeconds(p.offset.seconds), - length({ 0 }) + offsetSeconds(p.offset.seconds) { } @@ -32,7 +24,7 @@ PictureEntry::PictureEntry(dive *dIn, const picture &p) : d(dIn), // should give the same result]. bool PictureEntry::operator<(const PictureEntry &p2) const { - if (int cmp = comp_dives(d, p2.d)) + if (int cmp = comp_dives_ptr(d, p2.d)) return cmp < 0; if (offsetSeconds != p2.offsetSeconds) return offsetSeconds < p2.offsetSeconds; @@ -91,8 +83,8 @@ void DivePictureModel::updateDivePictures() for (struct dive *dive: getDiveSelection()) { size_t first = pictures.size(); - FOR_EACH_PICTURE(dive) - pictures.push_back(PictureEntry(dive, *picture)); + for (auto &picture: dive->pictures) + pictures.push_back(PictureEntry(dive, picture)); // Sort pictures of this dive by offset. // Thus, the list will be sorted by (dive, offset). @@ -197,7 +189,7 @@ void DivePictureModel::picturesRemoved(dive *d, QVector filenamesIn) } // Assumes that pics is sorted! -void DivePictureModel::picturesAdded(dive *d, QVector picsIn) +void DivePictureModel::picturesAdded(dive *d, QVector picsIn) { // We only display pictures of selected dives if (!d->selected || picsIn.empty()) @@ -206,7 +198,7 @@ void DivePictureModel::picturesAdded(dive *d, QVector picsIn) // Convert the picture-data into our own format std::vector pics; pics.reserve(picsIn.size()); - for (const PictureObj &pic: picsIn) + for (const picture &pic: picsIn) pics.push_back(PictureEntry(d, pic)); // Insert batch-wise to avoid too many reloads @@ -258,7 +250,7 @@ static void addDurationToThumbnail(QImage &img, duration_t duration) QStringLiteral("%1:%2").arg(seconds / 60, 2, 10, QChar('0')) .arg(seconds % 60, 2, 10, QChar('0')); - QFont font(system_divelist_default_font, 30); + QFont font(system_divelist_default_font.c_str(), 30); QFontMetrics metrics(font); QSize size = metrics.size(Qt::TextSingleLine, s); QSize imgSize = img.size(); diff --git a/qt-models/divepicturemodel.h b/qt-models/divepicturemodel.h index d7b043d44..fa4ccaef8 100644 --- a/qt-models/divepicturemodel.h +++ b/qt-models/divepicturemodel.h @@ -2,8 +2,7 @@ #ifndef DIVEPICTUREMODEL_H #define DIVEPICTUREMODEL_H -#include "core/units.h" -#include "core/pictureobj.h" +#include "core/picture.h" #include #include @@ -17,7 +16,6 @@ struct PictureEntry { QImage image; int offsetSeconds; duration_t length; - PictureEntry(dive *, const PictureObj &); PictureEntry(dive *, const picture &); bool operator<(const PictureEntry &) const; }; @@ -36,7 +34,7 @@ public slots: void updateThumbnail(QString filename, QImage thumbnail, duration_t duration); void pictureOffsetChanged(dive *d, const QString filename, offset_t offset); void picturesRemoved(dive *d, QVector filenames); - void picturesAdded(dive *d, QVector pics); + void picturesAdded(dive *d, QVector pics); private: DivePictureModel(); std::vector pictures; diff --git a/qt-models/diveplannermodel.cpp b/qt-models/diveplannermodel.cpp index 80900468f..3fc4f0278 100644 --- a/qt-models/diveplannermodel.cpp +++ b/qt-models/diveplannermodel.cpp @@ -1,17 +1,21 @@ // SPDX-License-Identifier: GPL-2.0 #include "diveplannermodel.h" +#include "core/color.h" #include "core/dive.h" #include "core/divelist.h" #include "core/divelog.h" +#include "core/event.h" +#include "core/format.h" #include "core/subsurface-string.h" #include "qt-models/cylindermodel.h" -#include "qt-models/models.h" // For defaultModelFont(). +#include "core/metrics.h" // For defaultModelFont(). #include "core/planner.h" #include "core/device.h" #include "core/qthelper.h" #include "core/range.h" #include "core/sample.h" #include "core/selection.h" +#include "core/subsurface-time.h" #include "core/settings/qPrefDivePlanner.h" #include "core/settings/qPrefUnit.h" #if !defined(SUBSURFACE_TESTING) @@ -66,10 +70,10 @@ void DivePlannerPointsModel::createSimpleDive(struct dive *dIn) // clean out the dive and give it an id and the correct dc model d = dIn; dcNr = 0; - clear_dive(d); + d->clear(); d->id = dive_getUniqID(); d->when = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset() + 3600; - make_planner_dc(&d->dc); + make_planner_dc(&d->dcs[0]); clear(); removeDeco(); @@ -86,12 +90,12 @@ void DivePlannerPointsModel::createSimpleDive(struct dive *dIn) // If we're in drop_stone_mode, don't add a first point. // It will be added implicitly. if (!prefs.drop_stone_mode) - addStop(M_OR_FT(15, 45), 1 * 60, cylinderid, 0, true, UNDEF_COMP_TYPE); + addStop(M_OR_FT(15, 45), 1 * 60, cylinderid, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE); - addStop(M_OR_FT(15, 45), 20 * 60, 0, 0, true, UNDEF_COMP_TYPE); + addStop(M_OR_FT(15, 45), 20 * 60, 0, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE); if (!isPlanner()) { - addStop(M_OR_FT(5, 15), 42 * 60, 0, cylinderid, true, UNDEF_COMP_TYPE); - addStop(M_OR_FT(5, 15), 45 * 60, 0, cylinderid, true, UNDEF_COMP_TYPE); + addStop(M_OR_FT(5, 15), 42 * 60, cylinderid, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE); + addStop(M_OR_FT(5, 15), 45 * 60, cylinderid, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE); } updateDiveProfile(); } @@ -101,9 +105,8 @@ void DivePlannerPointsModel::setupStartTime() // if the latest dive is in the future, then start an hour after it ends // otherwise start an hour from now startTime = QDateTime::currentDateTimeUtc().addSecs(3600 + gettimezoneoffset()); - if (divelog.dives->nr > 0) { - struct dive *d = get_dive(divelog.dives->nr - 1); - time_t ends = dive_endtime(d); + if (!divelog.dives.empty()) { + time_t ends = divelog.dives.back()->endtime(); time_t diff = ends - dateTimeToTimestamp(startTime); if (diff > 0) startTime = startTime.addSecs(diff + 3600); @@ -118,17 +121,15 @@ void DivePlannerPointsModel::loadFromDive(dive *dIn, int dcNrIn) int depthsum = 0; int samplecount = 0; o2pressure_t last_sp; - struct divecomputer *dc = get_dive_dc(d, dcNr); - const struct event *evd = NULL; - enum divemode_t current_divemode = UNDEF_COMP_TYPE; + struct divecomputer *dc = d->get_dc(dcNr); cylinders.updateDive(d, dcNr); - duration_t lasttime = { 0 }; - duration_t lastrecordedtime = {}; - duration_t newtime = {}; + duration_t lasttime; + duration_t lastrecordedtime; + duration_t newtime; clear(); removeDeco(); - free_dps(&diveplan); + diveplan.dp.clear(); diveplan.when = d->when; // is this a "new" dive where we marked manually entered samples? @@ -138,27 +139,27 @@ void DivePlannerPointsModel::loadFromDive(dive *dIn, int dcNrIn) bool hasMarkedSamples = false; - if (dc->samples) - hasMarkedSamples = dc->sample[0].manually_entered; + if (!dc->samples.empty()) + hasMarkedSamples = dc->samples[0].manually_entered; else fake_dc(dc); // if this dive has more than 100 samples (so it is probably a logged dive), // average samples so we end up with a total of 100 samples. - int plansamples = dc->samples <= 100 ? dc->samples : 100; + int plansamples = std::min(static_cast(dc->samples.size()), 100); int j = 0; int cylinderid = 0; - last_sp.mbar = 0; + divemode_loop loop(*dc); for (int i = 0; i < plansamples - 1; i++) { if (dc->last_manual_time.seconds && dc->last_manual_time.seconds > 120 && lasttime.seconds >= dc->last_manual_time.seconds) break; - while (j * plansamples <= i * dc->samples) { - const sample &s = dc->sample[j]; + while (j * plansamples <= i * static_cast(dc->samples.size())) { + const sample &s = dc->samples[j]; if (s.time.seconds != 0 && (!hasMarkedSamples || s.manually_entered)) { depthsum += s.depth.mm; if (j > 0) - last_sp = dc->sample[j-1].setpoint; + last_sp = dc->samples[j-1].setpoint; ++samplecount; newtime = s.time; } @@ -171,7 +172,7 @@ void DivePlannerPointsModel::loadFromDive(dive *dIn, int dcNrIn) if (newtime.seconds - lastrecordedtime.seconds > 10 || cylinderid == get_cylinderid_at_time(d, dc, nexttime)) { if (newtime.seconds == lastrecordedtime.seconds) newtime.seconds += 10; - current_divemode = get_current_divemode(dc, newtime.seconds - 1, &evd, ¤t_divemode); + divemode_t current_divemode = loop.at(newtime.seconds - 1); addStop(depthsum / samplecount, newtime.seconds, cylinderid, last_sp.mbar, true, current_divemode); lastrecordedtime = newtime; } @@ -181,7 +182,7 @@ void DivePlannerPointsModel::loadFromDive(dive *dIn, int dcNrIn) } } // make sure we get the last point right so the duration is correct - current_divemode = get_current_divemode(dc, dc->duration.seconds, &evd, ¤t_divemode); + divemode_t current_divemode = loop.at(dc->duration.seconds); if (!hasMarkedSamples && !dc->last_manual_time.seconds) addStop(0, dc->duration.seconds,cylinderid, last_sp.mbar, true, current_divemode); preserved_until = d->duration; @@ -195,13 +196,13 @@ void DivePlannerPointsModel::loadFromDive(dive *dIn, int dcNrIn) // setup the cylinder widget accordingly void DivePlannerPointsModel::setupCylinders() { - clear_cylinder_table(&d->cylinders); + d->cylinders.clear(); if (mode == PLAN && current_dive) { // take the displayed cylinders from the selected dive as starting point copy_used_cylinders(current_dive, d, !prefs.include_unused_tanks); reset_cylinders(d, true); - if (d->cylinders.nr > 0) { + if (!d->cylinders.empty()) { cylinders.updateDive(d, dcNr); return; // We have at least one cylinder } @@ -215,7 +216,7 @@ void DivePlannerPointsModel::setupCylinders() bool DivePlannerPointsModel::updateMaxDepth() { int prevMaxDepth = d->maxdepth.mm; - d->maxdepth.mm = 0; + d->maxdepth = 0_m; for (int i = 0; i < rowCount(); i++) { divedatapoint p = at(i); if (p.depth.mm > d->maxdepth.mm) @@ -260,15 +261,42 @@ int DivePlannerPointsModel::columnCount(const QModelIndex&) const return COLUMNS; // to disable CCSETPOINT subtract one } +static divemode_t get_local_divemode(struct dive *d, int dcNr, const divedatapoint &p) +{ + divemode_t divemode; + switch (d->get_dc(dcNr)->divemode) { + case OC: + default: + divemode = OC; + + break; + case CCR: + divemode = d->get_cylinder(p.cylinderid)->cylinder_use == DILUENT ? CCR : OC; + if (prefs.allowOcGasAsDiluent && d->get_cylinder(p.cylinderid)->cylinder_use == OC_GAS && p.divemode == CCR) + divemode = CCR; + + break; + case PSCR: + divemode = p.divemode == PSCR ? PSCR : OC; + + break; + } + + return divemode; +} + QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const { - divedatapoint p = divepoints.at(index.row()); + const divedatapoint p = divepoints.at(index.row()); + bool isInappropriateCylinder = !is_cylinder_use_appropriate(*d->get_dc(dcNr), *d->get_cylinder(p.cylinderid), false); + divemode_t divemode = get_local_divemode(d, dcNr, p); if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (index.column()) { case CCSETPOINT: - return (double)p.setpoint / 1000; + return (divemode == CCR) ? (double)(p.setpoint / 1000.0) : QVariant(); case DEPTH: - return (int) lrint(get_depth_units(p.depth.mm, NULL, NULL)); + return int_cast(get_depth_units(p.depth.mm, NULL, NULL)); case RUNTIME: return p.time / 60; case DURATION: @@ -277,17 +305,9 @@ QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const else return p.time / 60; case DIVEMODE: - return gettextFromC::tr(divemode_text_ui[p.divemode]); + return gettextFromC::tr(divemode_text_ui[divemode]); case GAS: - /* Check if we have the same gasmix two or more times - * If yes return more verbose string */ - int same_gas = same_gasmix_cylinder(get_cylinder(d, p.cylinderid), p.cylinderid, d, true); - if (same_gas == -1) - return get_gas_string(get_cylinder(d, p.cylinderid)->gasmix); - else - return get_gas_string(get_cylinder(d, p.cylinderid)->gasmix) + - QString(" (%1 %2 ").arg(tr("cyl.")).arg(p.cylinderid + 1) + - get_cylinder(d, p.cylinderid)->type.description + ")"; + return get_dive_gas(d, dcNr, p.cylinderid); } } else if (role == Qt::DecorationRole) { switch (index.column()) { @@ -306,14 +326,28 @@ QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const return trashForbiddenIcon().size(); } } else if (role == Qt::FontRole) { - if (divepoints.at(index.row()).entered) { - return defaultModelFont(); - } else { - QFont font = defaultModelFont(); - font.setBold(true); - return font; + QFont font = defaultModelFont(); + + font.setBold(!p.entered); + + font.setItalic(isInappropriateCylinder); + + return font; + } else if (role == Qt::BackgroundRole) { + switch (index.column()) { + case GAS: + if (isInappropriateCylinder) + return REDORANGE1_HIGH_TRANS; + + break; + case CCSETPOINT: + if (divemode != CCR) + return MED_GRAY_HIGH_TRANS; + + break; } } + return QVariant(); } @@ -323,13 +357,15 @@ bool DivePlannerPointsModel::setData(const QModelIndex &index, const QVariant &v if (role == Qt::EditRole) { divedatapoint &p = divepoints[index.row()]; switch (index.column()) { - case DEPTH: - if (value.toInt() >= 0) { - p.depth = units_to_depth(value.toInt()); + case DEPTH: { + int depth = value.toInt(); + if (depth >= 0) { + p.depth = units_to_depth(depth); if (updateMaxDepth()) cylinders.updateBestMixes(); } break; + } case RUNTIME: { int secs = value.toInt() * 60; i = index.row(); @@ -346,23 +382,25 @@ bool DivePlannerPointsModel::setData(const QModelIndex &index, const QVariant &v break; } case DURATION: { - int secs = value.toInt() * 60; - if (!secs) - secs = 10; - i = index.row(); - if (i) - shift = divepoints[i].time - divepoints[i - 1].time - secs; - else - shift = divepoints[i].time - secs; - while (i < divepoints.size()) - divepoints[i++].time -= shift; - break; + int secs = value.toInt() * 60; + if (secs < 0) + secs = 10; + i = index.row(); + if (i) + shift = divepoints[i].time - divepoints[i - 1].time - secs; + else + shift = divepoints[i].time - secs; + while (i < divepoints.size()) + divepoints[i++].time -= shift; + break; } case CCSETPOINT: { - int po2 = 0; - QByteArray gasv = value.toByteArray(); - if (validate_po2(gasv.data(), &po2)) - p.setpoint = po2; + bool ok; + int po2 = static_cast(round(value.toFloat(&ok) * 100) * 10); + + if (ok) + p.setpoint = std::max(po2, 160); + break; } case GAS: @@ -376,7 +414,6 @@ bool DivePlannerPointsModel::setData(const QModelIndex &index, const QVariant &v case DIVEMODE: if (value.toInt() < FREEDIVE) { p.divemode = (enum divemode_t) value.toInt(); - p.setpoint = p.divemode == CCR ? prefs.defaultsetpoint : 0; } break; } @@ -415,7 +452,7 @@ QVariant DivePlannerPointsModel::headerData(int section, Qt::Orientation orienta case GAS: return tr("Used gas"); case CCSETPOINT: - return tr("CC setpoint"); + return tr("Setpoint"); case DIVEMODE: return tr("Dive mode"); } @@ -427,10 +464,29 @@ QVariant DivePlannerPointsModel::headerData(int section, Qt::Orientation orienta Qt::ItemFlags DivePlannerPointsModel::flags(const QModelIndex &index) const { - if (index.column() != REMOVE) - return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; - else + if (!index.isValid()) return QAbstractItemModel::flags(index); + + if (index.column() == REMOVE) + return Qt::ItemIsEnabled; + + const divedatapoint p = divepoints.at(index.row()); + switch (index.column()) { + case REMOVE: + return QAbstractItemModel::flags(index); + case CCSETPOINT: + if (get_local_divemode(d, dcNr, p) != CCR) + return QAbstractItemModel::flags(index) & ~Qt::ItemIsEditable & ~Qt::ItemIsEnabled; + + break; + case DIVEMODE: + if (!((d->get_dc(dcNr)->divemode == CCR && prefs.allowOcGasAsDiluent && d->get_cylinder(p.cylinderid)->cylinder_use == OC_GAS) || d->get_dc(dcNr)->divemode == PSCR)) + return QAbstractItemModel::flags(index) & ~Qt::ItemIsEditable & ~Qt::ItemIsEnabled; + + break; + } + + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } int DivePlannerPointsModel::rowCount(const QModelIndex&) const @@ -443,7 +499,6 @@ DivePlannerPointsModel::DivePlannerPointsModel(QObject *parent) : QAbstractTable cylinders(true), mode(NOTHING) { - memset(&diveplan, 0, sizeof(diveplan)); startTime.setTimeSpec(Qt::UTC); // use a Qt-connection to send the variations text across thread boundary (in case we // are calculating the variations in a background thread). @@ -536,14 +591,15 @@ int DivePlannerPointsModel::gfLow() const return diveplan.gflow; } -void DivePlannerPointsModel::setRebreatherMode(int mode) +void DivePlannerPointsModel::cylindersChanged() { - get_dive_dc(d, dcNr)->divemode = (divemode_t) mode; - for (int i = 0; i < rowCount(); i++) { - divepoints[i].setpoint = mode == CCR ? prefs.defaultsetpoint : 0; - divepoints[i].divemode = (enum divemode_t) mode; - } + if (!d) + return; + + cylinders.updateDive(d, dcNr); + emitDataChanged(); + cylinders.emitDataChanged(); } void DivePlannerPointsModel::setVpmbConservatism(int level) @@ -554,7 +610,7 @@ void DivePlannerPointsModel::setVpmbConservatism(int level) } } -void DivePlannerPointsModel::setSurfacePressure(int pressure) +void DivePlannerPointsModel::setSurfacePressure(pressure_t pressure) { diveplan.surface_pressure = pressure; emitDataChanged(); @@ -566,7 +622,7 @@ void DivePlannerPointsModel::setSalinity(int salinity) emitDataChanged(); } -int DivePlannerPointsModel::getSurfacePressure() const +pressure_t DivePlannerPointsModel::getSurfacePressure() const { return diveplan.surface_pressure; } @@ -602,6 +658,7 @@ void DivePlannerPointsModel::setAscratestopsDisplay(int rate) qPrefDivePlanner::set_ascratestops(lrint(rate * unit_factor())); emitDataChanged(); } + int DivePlannerPointsModel::ascratestopsDisplay() const { return lrint((float)prefs.ascratestops / unit_factor()); @@ -751,13 +808,13 @@ int DivePlannerPointsModel::lastEnteredPoint() const void DivePlannerPointsModel::addDefaultStop() { removeDeco(); - addStop(0, 0, -1, 0, true, UNDEF_COMP_TYPE); + addStop(0, 0, -1, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE); } void DivePlannerPointsModel::addStop(int milimeters, int seconds) { removeDeco(); - addStop(milimeters, seconds, -1, 0, true, UNDEF_COMP_TYPE); + addStop(milimeters, seconds, -1, prefs.defaultsetpoint, true, UNDEF_COMP_TYPE); updateDiveProfile(); } @@ -773,18 +830,20 @@ int DivePlannerPointsModel::addStop(int milimeters, int seconds, int cylinderid_ usePrevious = true; int row = divepoints.count(); - if (seconds == 0 && milimeters == 0 && row != 0) { - /* this is only possible if the user clicked on the 'plus' sign on the DivePoints Table */ - const divedatapoint t = divepoints.at(lastEnteredPoint()); - milimeters = t.depth.mm; - seconds = t.time + 600; // 10 minutes. - cylinderid = t.cylinderid; - ccpoint = t.setpoint; - } else if (seconds == 0 && milimeters == 0 && row == 0) { - milimeters = M_OR_FT(5, 15); // 5m / 15ft - seconds = 600; // 10 min - // Default to the first cylinder - cylinderid = 0; + if (seconds == 0 && milimeters == 0) { + if (row == 0) { + milimeters = M_OR_FT(5, 15); // 5m / 15ft + seconds = 600; // 10 min + // Default to the first cylinder + cylinderid = 0; + } else { + /* this is only possible if the user clicked on the 'plus' sign on the DivePoints Table */ + const divedatapoint t = divepoints.at(lastEnteredPoint()); + milimeters = t.depth.mm; + seconds = t.time + 600; // 10 minutes. + cylinderid = t.cylinderid; + ccpoint = t.setpoint; + } } // check if there's already a new stop before this one: @@ -818,19 +877,12 @@ int DivePlannerPointsModel::addStop(int milimeters, int seconds, int cylinderid_ } } if (divemode == UNDEF_COMP_TYPE) - divemode = get_dive_dc_const(d, dcNr)->divemode; + divemode = d->get_dc(dcNr)->divemode; // add the new stop beginInsertRows(QModelIndex(), row, row); - divedatapoint point; - point.depth.mm = milimeters; - point.time = seconds; - point.cylinderid = cylinderid; - point.setpoint = ccpoint; - point.minimum_gas.mbar = 0; - point.entered = entered; + divedatapoint point(seconds, milimeters, cylinderid, ccpoint, entered); point.divemode = divemode; - point.next = NULL; divepoints.insert(divepoints.begin() + row, point); endInsertRows(); return row; @@ -990,7 +1042,7 @@ void DivePlannerPointsModel::cancelPlan() */ setPlanMode(NOTHING); - free_dps(&diveplan); + diveplan.dp.clear(); emit planCanceled(); } @@ -1017,7 +1069,7 @@ bool DivePlannerPointsModel::tankInUse(int cylinderid) const void DivePlannerPointsModel::clear() { cylinders.clear(); - preserved_until.seconds = 0; + preserved_until = 0_sec; beginResetModel(); divepoints.clear(); endResetModel(); @@ -1026,31 +1078,30 @@ void DivePlannerPointsModel::clear() void DivePlannerPointsModel::createTemporaryPlan() { // Get the user-input and calculate the dive info - free_dps(&diveplan); + diveplan.dp.clear(); - for (int i = 0; i < d->cylinders.nr; i++) { - cylinder_t *cyl = get_cylinder(d, i); - if (cyl->depth.mm && cyl->cylinder_use == OC_GAS) { - plan_add_segment(&diveplan, 0, cyl->depth.mm, i, 0, false, OC); - } + for (auto [i, cyl]: enumerated_range(d->cylinders)) { + if (cyl.depth.mm && cyl.cylinder_use == OC_GAS) + plan_add_segment(diveplan, 0, cyl.depth.mm, i, 0, false, OC); } int lastIndex = -1; for (int i = 0; i < rowCount(); i++) { - divedatapoint p = at(i); + const divedatapoint p = at(i); + divemode_t divemode = get_local_divemode(d, dcNr, p); int deltaT = lastIndex != -1 ? p.time - at(lastIndex).time : p.time; lastIndex = i; if (i == 0 && mode == PLAN && prefs.drop_stone_mode) { /* Okay, we add a first segment where we go down to depth */ - plan_add_segment(&diveplan, p.depth.mm / prefs.descrate, p.depth.mm, p.cylinderid, p.setpoint, true, p.divemode); + plan_add_segment(diveplan, p.depth.mm / prefs.descrate, p.depth.mm, p.cylinderid, divemode == CCR ? p.setpoint : 0, true, divemode); deltaT -= p.depth.mm / prefs.descrate; } if (p.entered) - plan_add_segment(&diveplan, deltaT, p.depth.mm, p.cylinderid, p.setpoint, true, p.divemode); + plan_add_segment(diveplan, deltaT, p.depth.mm, p.cylinderid, divemode == CCR ? p.setpoint : 0, true, divemode); } #if DEBUG_PLAN - dump_plan(&diveplan); + dump_plan(diveplan); #endif } @@ -1064,48 +1115,55 @@ void DivePlannerPointsModel::updateDiveProfile() if (!d) return; createTemporaryPlan(); - if (diveplan_empty(&diveplan)) + if (diveplan.is_empty()) return; deco_state_cache cache; - struct decostop stoptable[60]; struct deco_state plan_deco_state; - memset(&plan_deco_state, 0, sizeof(struct deco_state)); - plan(&plan_deco_state, &diveplan, d, dcNr, decotimestep, stoptable, cache, isPlanner(), false); + plan(&plan_deco_state, diveplan, d, dcNr, decotimestep, cache, isPlanner(), false); updateMaxDepth(); if (isPlanner() && shouldComputeVariations()) { - struct diveplan *plan_copy = (struct diveplan *)malloc(sizeof(struct diveplan)); + auto plan_copy = std::make_unique(); lock_planner(); - cloneDiveplan(&diveplan, plan_copy); + *plan_copy = diveplan; unlock_planner(); #ifdef VARIATIONS_IN_BACKGROUND // Since we're calling computeVariations asynchronously and plan_deco_state is allocated // on the stack, it must be copied and freed by the worker-thread. - struct deco_state *plan_deco_state_copy = new deco_state(plan_deco_state); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QtConcurrent::run(&DivePlannerPointsModel::computeVariationsFreeDeco, this, plan_copy, plan_deco_state_copy); + auto deco_copy = std::make_unique(plan_deco_state); + + // Ideally, we would pass the unique_ptrs to the lambda for QtConcurrent::run(). + // This, in principle, can be done as such: + // [ptr = std::move(ptr)] () mutable { f(std::move(ptr)) }; + // However, this make the lambda uncopyable and QtConcurrent::run() sadly + // uses copy semantics. + // So let's be pragmatic and do a release/reaquire pair. + // Somewhat disappointing, but what do you want to do? + // Note 1: this is now not exception safe, but Qt doesn't support + // exceptions anyway. + // Note 2: We also can't use the function / argument syntax of QtConcurrent::run(), + // because it likewise uses copy-semantics. How annoying. + QtConcurrent::run([this, plan = plan_copy.release(), deco = deco_copy.release()] () + { this->computeVariationsFreeDeco(std::unique_ptr(plan), + std::unique_ptr(deco)); }); #else - QtConcurrent::run(this, &DivePlannerPointsModel::computeVariationsFreeDeco, plan_copy, plan_deco_state_copy); -#endif -#else - computeVariations(plan_copy, &plan_deco_state); + computeVariations(std::move(plan_copy), &plan_deco_state); #endif final_deco_state = plan_deco_state; } - emit calculatedPlanNotes(QString(d->notes)); - + emit calculatedPlanNotes(QString::fromStdString(d->notes)); #if DEBUG_PLAN - save_dive(stderr, d); + save_dive(stderr, *d); dump_plan(&diveplan); #endif } void DivePlannerPointsModel::deleteTemporaryPlan() { - free_dps(&diveplan); + diveplan.dp.clear(); } void DivePlannerPointsModel::savePlan() @@ -1118,51 +1176,14 @@ void DivePlannerPointsModel::saveDuplicatePlan() createPlan(true); } -struct divedatapoint *DivePlannerPointsModel::cloneDiveplan(struct diveplan *plan_src, struct diveplan *plan_copy) +int DivePlannerPointsModel::analyzeVariations(const std::vector &min, const std::vector &mid, const std::vector &max, const char *unit) { - divedatapoint *src, *last_segment; - divedatapoint **dp; - - src = plan_src->dp; - *plan_copy = *plan_src; - dp = &plan_copy->dp; - while (src && (!src->time || src->entered)) { - *dp = (struct divedatapoint *)malloc(sizeof(struct divedatapoint)); - **dp = *src; - dp = &(*dp)->next; - src = src->next; - } - (*dp) = NULL; - - last_segment = plan_copy->dp; - while (last_segment && last_segment->next && last_segment->next->next) - last_segment = last_segment->next; - return last_segment; -} - -int DivePlannerPointsModel::analyzeVariations(struct decostop *min, struct decostop *mid, struct decostop *max, const char *unit) -{ - int minsum = 0; - int midsum = 0; - int maxsum = 0; - int leftsum = 0; - int rightsum = 0; - - while (min->depth) { - minsum += min->time; - ++min; - } - while (mid->depth) { - midsum += mid->time; - ++mid; - } - while (max->depth) { - maxsum += max->time; - ++max; - } - - leftsum = midsum - minsum; - rightsum = maxsum - midsum; + auto sum_time = [](int time, const decostop &ds) { return ds.time + time; }; + int minsum = std::accumulate(min.begin(), min.end(), 0, sum_time); + int midsum = std::accumulate(mid.begin(), mid.end(), 0, sum_time); + int maxsum = std::accumulate(max.begin(), max.end(), 0, sum_time); + int leftsum = midsum - minsum; + int rightsum = maxsum - midsum; #ifdef DEBUG_STOPVAR printf("Total + %d:%02d/%s +- %d s/%s\n\n", FRACTION_TUPLE((leftsum + rightsum) / 2, 60), unit, @@ -1173,107 +1194,111 @@ int DivePlannerPointsModel::analyzeVariations(struct decostop *min, struct decos return (leftsum + rightsum) / 2; } -void DivePlannerPointsModel::computeVariationsFreeDeco(struct diveplan *original_plan, struct deco_state *previous_ds) +void DivePlannerPointsModel::computeVariationsFreeDeco(std::unique_ptr original_plan, std::unique_ptr previous_ds) { - computeVariations(original_plan, previous_ds); - delete previous_ds; + computeVariations(std::move(original_plan), previous_ds.get()); + // Note: previous ds automatically free()d by virtue of being a unique_ptr. } -void DivePlannerPointsModel::computeVariations(struct diveplan *original_plan, const struct deco_state *previous_ds) +// Return reference to second to last element. +// Caller is responsible for checking that there are at least two elements. +template +auto &second_to_last(T &v) +{ + return *std::prev(std::prev(v.end())); +} + +void DivePlannerPointsModel::computeVariations(std::unique_ptr original_plan, const struct deco_state *previous_ds) { // nothing to do unless there's an original plan if (!original_plan) return; - struct dive *dive = alloc_dive(); - copy_dive(d, dive); - struct decostop original[60], deeper[60], shallower[60], shorter[60], longer[60]; + auto dive = std::make_unique(); + copy_dive(d, dive.get()); deco_state_cache cache, save; struct diveplan plan_copy; - struct divedatapoint *last_segment; struct deco_state ds = *previous_ds; int my_instance = ++instanceCounter; save.cache(&ds); - duration_t delta_time = { .seconds = 60 }; + duration_t delta_time = 1_min; QString time_units = tr("min"); depth_t delta_depth; QString depth_units; if (prefs.units.length == units::METERS) { - delta_depth.mm = 1000; // 1m + delta_depth = 1_m; depth_units = tr("m"); } else { - delta_depth.mm = feet_to_mm(1.0); // 1ft + delta_depth = 1_ft; depth_units = tr("ft"); } - last_segment = cloneDiveplan(original_plan, &plan_copy); - if (!last_segment) - goto finish; + plan_copy = *original_plan; + if (plan_copy.dp.size() < 2) + return; if (my_instance != instanceCounter) - goto finish; - plan(&ds, &plan_copy, dive, dcNr, 1, original, cache, true, false); - free_dps(&plan_copy); + return; + auto original = plan(&ds, plan_copy, dive.get(), dcNr, 1, cache, true, false); save.restore(&ds, false); - last_segment = cloneDiveplan(original_plan, &plan_copy); - last_segment->depth.mm += delta_depth.mm; - last_segment->next->depth.mm += delta_depth.mm; + plan_copy = *original_plan; + second_to_last(plan_copy.dp).depth.mm += delta_depth.mm; + plan_copy.dp.back().depth.mm += delta_depth.mm; if (my_instance != instanceCounter) - goto finish; - plan(&ds, &plan_copy, dive, dcNr, 1, deeper, cache, true, false); - free_dps(&plan_copy); + return; + auto deeper = plan(&ds, plan_copy, dive.get(), dcNr, 1, cache, true, false); save.restore(&ds, false); - last_segment = cloneDiveplan(original_plan, &plan_copy); - last_segment->depth.mm -= delta_depth.mm; - last_segment->next->depth.mm -= delta_depth.mm; + second_to_last(plan_copy.dp).depth.mm -= delta_depth.mm; + plan_copy.dp.back().depth.mm -= delta_depth.mm; if (my_instance != instanceCounter) - goto finish; - plan(&ds, &plan_copy, dive, dcNr, 1, shallower, cache, true, false); - free_dps(&plan_copy); + return; + auto shallower = plan(&ds, plan_copy, dive.get(), dcNr, 1, cache, true, false); save.restore(&ds, false); - last_segment = cloneDiveplan(original_plan, &plan_copy); - last_segment->next->time += delta_time.seconds; + plan_copy = *original_plan; + plan_copy.dp.back().time += delta_time.seconds; if (my_instance != instanceCounter) - goto finish; - plan(&ds, &plan_copy, dive, dcNr, 1, longer, cache, true, false); - free_dps(&plan_copy); + return; + auto longer = plan(&ds, plan_copy, dive.get(), dcNr, 1, cache, true, false); save.restore(&ds, false); - last_segment = cloneDiveplan(original_plan, &plan_copy); - last_segment->next->time -= delta_time.seconds; + plan_copy.dp.back().time -= delta_time.seconds; if (my_instance != instanceCounter) - goto finish; - plan(&ds, &plan_copy, dive, dcNr, 1, shorter, cache, true, false); - free_dps(&plan_copy); + return; + auto shorter = plan(&ds, plan_copy, dive.get(), dcNr, 1, cache, true, false); save.restore(&ds, false); - char buf[200]; - sprintf(buf, ", %s: %c %d:%02d /%s %c %d:%02d /min", qPrintable(tr("Stop times")), + std::string buf = format_string_std(", %s: %c %d:%02d /%s %c %d:%02d /min", qPrintable(tr("Stop times")), SIGNED_FRAC_TRIPLET(analyzeVariations(shallower, original, deeper, qPrintable(depth_units)), 60), qPrintable(depth_units), SIGNED_FRAC_TRIPLET(analyzeVariations(shorter, original, longer, qPrintable(time_units)), 60)); // By using a signal, we can transport the variations to the main thread. - emit variationsComputed(QString(buf)); + emit variationsComputed(QString::fromStdString(buf)); #ifdef DEBUG_STOPVAR printf("\n\n"); #endif -finish: - free_dps(original_plan); - free(original_plan); - free_dive(dive); } void DivePlannerPointsModel::computeVariationsDone(QString variations) { - QString notes = QString(d->notes); - free(d->notes); - d->notes = copy_qstring(notes.replace("VARIATIONS", variations)); - emit calculatedPlanNotes(QString(d->notes)); + QString notes = QString::fromStdString(d->notes); + notes = notes.replace("VARIATIONS", variations); + d->notes = notes.toStdString(); + emit calculatedPlanNotes(notes); +} + +static void addDive(dive *d, bool autogroup, bool newNumber) +{ + // Create a new dive and clear out the old one. + auto new_d = std::make_unique(); + std::swap(*d, *new_d); +#if !defined(SUBSURFACE_TESTING) + Command::addDive(std::move(new_d), autogroup, newNumber); +#endif // !SUBSURFACE_TESTING } void DivePlannerPointsModel::createPlan(bool saveAsNew) @@ -1283,16 +1308,14 @@ void DivePlannerPointsModel::createPlan(bool saveAsNew) removeDeco(); createTemporaryPlan(); - struct decostop stoptable[60]; - plan(&ds_after_previous_dives, &diveplan, d, dcNr, decotimestep, stoptable, cache, isPlanner(), true); + plan(&ds_after_previous_dives, diveplan, d, dcNr, decotimestep, cache, isPlanner(), true); if (shouldComputeVariations()) { - struct diveplan *plan_copy; - plan_copy = (struct diveplan *)malloc(sizeof(struct diveplan)); + auto plan_copy = std::make_unique(); lock_planner(); - cloneDiveplan(&diveplan, plan_copy); + *plan_copy = diveplan; unlock_planner(); - computeVariations(plan_copy, &ds_after_previous_dives); + computeVariations(std::move(plan_copy), &ds_after_previous_dives); } // Fixup planner notes. @@ -1300,7 +1323,7 @@ void DivePlannerPointsModel::createPlan(bool saveAsNew) // Try to identify old planner output and remove only this part // Treat user provided text as plain text. QTextDocument notesDocument; - notesDocument.setHtml(current_dive->notes); + notesDocument.setHtml(QString::fromStdString(current_dive->notes)); QString oldnotes(notesDocument.toPlainText()); QString disclaimer = get_planner_disclaimer(); int disclaimerMid = disclaimer.indexOf("%s"); @@ -1322,8 +1345,8 @@ void DivePlannerPointsModel::createPlan(bool saveAsNew) } // Deal with line breaks oldnotes.replace("\n", "
"); - oldnotes.append(d->notes); - d->notes = copy_qstring(oldnotes); + oldnotes.append(QString::fromStdString(d->notes)); + d->notes = oldnotes.toStdString(); // If we save as new create a copy of the dive here } @@ -1333,17 +1356,13 @@ void DivePlannerPointsModel::createPlan(bool saveAsNew) if (!current_dive || d->id != current_dive->id) { // we were planning a new dive, not re-planning an existing one d->divetrip = nullptr; // Should not be necessary, just in case! -#if !defined(SUBSURFACE_TESTING) - Command::addDive(d, divelog.autogroup, true); -#endif // !SUBSURFACE_TESTING + addDive(d, divelog.autogroup, true); } else { copy_events_until(current_dive, d, dcNr, preserved_until.seconds); if (saveAsNew) { // we were planning an old dive and save as a new dive d->id = dive_getUniqID(); // Things will break horribly if we create dives with the same id. -#if !defined(SUBSURFACE_TESTING) - Command::addDive(d, false, false); -#endif // !SUBSURFACE_TESTING + addDive(d, false, false); } else { // we were planning an old dive and rewrite the plan #if !defined(SUBSURFACE_TESTING) @@ -1354,7 +1373,7 @@ void DivePlannerPointsModel::createPlan(bool saveAsNew) // Remove and clean the diveplan, so we don't delete // the dive by mistake. - free_dps(&diveplan); + diveplan.dp.clear(); planCreated(); // This signal will exit the UI from planner state. } diff --git a/qt-models/diveplannermodel.h b/qt-models/diveplannermodel.h index 86c283022..af958b938 100644 --- a/qt-models/diveplannermodel.h +++ b/qt-models/diveplannermodel.h @@ -4,6 +4,7 @@ #include #include +#include #include #include "core/deco.h" @@ -51,7 +52,7 @@ public: int ascratestopsDisplay() const; int ascratelast6mDisplay() const; int descrateDisplay() const; - int getSurfacePressure() const; + pressure_t getSurfacePressure() const; int gfLow() const; int gfHigh() const; @@ -65,6 +66,7 @@ public: void loadFromDive(dive *d, int dcNr); void addStop(int millimeters, int seconds); + void cylindersChanged(); public slots: void addDefaultStop(); @@ -72,7 +74,7 @@ slots: void setGFHigh(const int gfhigh); void setGFLow(const int gflow); void setVpmbConservatism(int level); - void setSurfacePressure(int pressure); + void setSurfacePressure(pressure_t pressure); void setSalinity(int salinity); void setBottomSac(double sac); void setDecoSac(double sac); @@ -95,7 +97,6 @@ slots: void removeDeco(); void deleteTemporaryPlan(); void emitDataChanged(); - void setRebreatherMode(int mode); void setReserveGas(int reserve); void setSwitchAtReqStop(bool value); void setMinSwitchDuration(int duration); @@ -129,11 +130,10 @@ private: void updateDiveProfile(); // Creates a temporary plan and updates the dive profile with it. void createTemporaryPlan(); struct diveplan diveplan; - struct divedatapoint *cloneDiveplan(struct diveplan *plan_src, struct diveplan *plan_copy); void computeVariationsDone(QString text); - void computeVariations(struct diveplan *diveplan, const struct deco_state *ds); - void computeVariationsFreeDeco(struct diveplan *diveplan, struct deco_state *ds); - int analyzeVariations(struct decostop *min, struct decostop *mid, struct decostop *max, const char *unit); + void computeVariations(std::unique_ptr plan, const struct deco_state *ds); + void computeVariationsFreeDeco(std::unique_ptr plan, std::unique_ptr ds); + int analyzeVariations(const std::vector &min, const std::vector &mid, const std::vector &max, const char *unit); struct dive *d; int dcNr; CylindersModel cylinders; diff --git a/qt-models/divesiteimportmodel.cpp b/qt-models/divesiteimportmodel.cpp index 07c763bb5..9847dacdd 100644 --- a/qt-models/divesiteimportmodel.cpp +++ b/qt-models/divesiteimportmodel.cpp @@ -1,13 +1,17 @@ #include "divesiteimportmodel.h" #include "core/divelog.h" #include "core/qthelper.h" +#include "core/range.h" #include "core/taxonomy.h" -DivesiteImportedModel::DivesiteImportedModel(QObject *o) : QAbstractTableModel(o), +DivesiteImportedModel::DivesiteImportedModel(dive_site_table &table, QObject *o) : QAbstractTableModel(o), firstIndex(0), lastIndex(-1), - importedSitesTable(nullptr) + importedSitesTable(table) { + checkStates.resize(importedSitesTable.size()); + for (const auto &[row, item]: enumerated_range(importedSitesTable)) + checkStates[row] = !divelog.sites.get_by_gps(&item->location); } int DivesiteImportedModel::columnCount(const QModelIndex &) const @@ -17,7 +21,7 @@ int DivesiteImportedModel::columnCount(const QModelIndex &) const int DivesiteImportedModel::rowCount(const QModelIndex &) const { - return lastIndex - firstIndex + 1; + return static_cast(importedSitesTable.size()); } QVariant DivesiteImportedModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -47,41 +51,36 @@ QVariant DivesiteImportedModel::data(const QModelIndex &index, int role) const if (!index.isValid()) return QVariant(); - if (index.row() + firstIndex > lastIndex) + if (index.row() < 0 || index.row() >= (int)importedSitesTable.size()) return QVariant(); - struct dive_site *ds = get_dive_site(index.row() + firstIndex, importedSitesTable); - if (!ds) - return QVariant(); + struct dive_site *ds = importedSitesTable[index.row()].get(); // widgets access the model via index.column() // Not supporting QML access via roles if (role == Qt::DisplayRole) { switch (index.column()) { case NAME: - return QString(ds->name); + return QString::fromStdString(ds->name); case LOCATION: return printGPSCoords(&ds->location); case COUNTRY: - return taxonomy_get_country(&ds->taxonomy); + return QString::fromStdString(taxonomy_get_country(ds->taxonomy)); case NEAREST: { // 40075000 is circumference of the earth in meters struct dive_site *nearest_ds = - get_dive_site_by_gps_proximity(&ds->location, - 40075000, divelog.sites); + divelog.sites.get_by_gps_proximity(ds->location, 40075000); if (nearest_ds) - return QString(nearest_ds->name); + return QString::fromStdString(nearest_ds->name); else return QString(); } case DISTANCE: { unsigned int distance = 0; struct dive_site *nearest_ds = - get_dive_site_by_gps_proximity(&ds->location, - 40075000, divelog.sites); + divelog.sites.get_by_gps_proximity(ds->location, 40075000); if (nearest_ds) - distance = get_distance(&ds->location, - &nearest_ds->location); + distance = get_distance(ds->location, nearest_ds->location); return distance_string(distance); } case SELECTED: @@ -98,25 +97,27 @@ QVariant DivesiteImportedModel::data(const QModelIndex &index, int role) const void DivesiteImportedModel::changeSelected(QModelIndex clickedIndex) { checkStates[clickedIndex.row()] = !checkStates[clickedIndex.row()]; - dataChanged(index(clickedIndex.row(), 0), index(clickedIndex.row(), 0), QVector() << Qt::CheckStateRole << SELECTED); + dataChanged(index(clickedIndex.row(), 0), index(clickedIndex.row(), 0), QVector { Qt::CheckStateRole, SELECTED }); } void DivesiteImportedModel::selectAll() { std::fill(checkStates.begin(), checkStates.end(), true); - dataChanged(index(0, 0), index(lastIndex - firstIndex, 0), QVector() << Qt::CheckStateRole << SELECTED); + // Qt is mad: for empty lists, last index would be -1, but that makes it crash. + dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector { Qt::CheckStateRole, SELECTED }); } void DivesiteImportedModel::selectRow(int row) { checkStates[row] = !checkStates[row]; - dataChanged(index(row, 0), index(row, 0), QVector() << Qt::CheckStateRole << SELECTED); + dataChanged(index(row, 0), index(row, 0), QVector { Qt::CheckStateRole, SELECTED }); } void DivesiteImportedModel::selectNone() { std::fill(checkStates.begin(), checkStates.end(), false); - dataChanged(index(0, 0), index(lastIndex - firstIndex,0 ), QVector() << Qt::CheckStateRole << SELECTED); + // Qt is mad: for empty lists, last index would be -1, but that makes it crash. + dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector { Qt::CheckStateRole, SELECTED }); } Qt::ItemFlags DivesiteImportedModel::flags(const QModelIndex &index) const @@ -125,19 +126,3 @@ Qt::ItemFlags DivesiteImportedModel::flags(const QModelIndex &index) const return QAbstractTableModel::flags(index); return QAbstractTableModel::flags(index) | Qt::ItemIsUserCheckable; } - -void DivesiteImportedModel::repopulate(struct dive_site_table *sites) -{ - beginResetModel(); - - importedSitesTable = sites; - firstIndex = 0; - lastIndex = importedSitesTable->nr - 1; - checkStates.resize(importedSitesTable->nr); - for (int row = 0; row < importedSitesTable->nr; row++) - if (get_dive_site_by_gps(&importedSitesTable->dive_sites[row]->location, divelog.sites)) - checkStates[row] = false; - else - checkStates[row] = true; - endResetModel(); -} diff --git a/qt-models/divesiteimportmodel.h b/qt-models/divesiteimportmodel.h index 5c7fb27fe..5da80223f 100644 --- a/qt-models/divesiteimportmodel.h +++ b/qt-models/divesiteimportmodel.h @@ -11,13 +11,12 @@ class DivesiteImportedModel : public QAbstractTableModel public: enum columnNames { NAME, LOCATION, COUNTRY, NEAREST, DISTANCE, SELECTED }; - DivesiteImportedModel(QObject *parent = 0); - int columnCount(const QModelIndex& index = QModelIndex()) const; - int rowCount(const QModelIndex& index = QModelIndex()) const; - QVariant data(const QModelIndex& index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - void repopulate(dive_site_table_t *sites); + DivesiteImportedModel(dive_site_table &, QObject *parent = 0); + int columnCount(const QModelIndex& index = QModelIndex()) const override; + int rowCount(const QModelIndex& index = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; public slots: void changeSelected(QModelIndex clickedIndex); @@ -29,7 +28,7 @@ private: int firstIndex; int lastIndex; std::vector checkStates; // char instead of bool to avoid silly pessimization of std::vector. - struct dive_site_table *importedSitesTable; + dive_site_table &importedSitesTable; }; #endif diff --git a/qt-models/divesummarymodel.cpp b/qt-models/divesummarymodel.cpp index b679da1f0..fa5a1ddee 100644 --- a/qt-models/divesummarymodel.cpp +++ b/qt-models/divesummarymodel.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 #include "qt-models/divesummarymodel.h" #include "core/dive.h" +#include "core/divelist.h" +#include "core/divelog.h" #include "core/qthelper.h" #include @@ -116,7 +118,7 @@ Stats::Stats() : static void calculateDive(struct dive *dive, Stats &stats) { - if (is_dc_planner(&dive->dc)) { + if (is_dc_planner(&dive->dcs[0])) { stats.diveplans++; return; } @@ -151,25 +153,19 @@ static void calculateDive(struct dive *dive, Stats &stats) } // EAN dive ? - for (int j = 0; j < dive->cylinders.nr; ++j) { - if (get_cylinder(dive, j)->gasmix.o2.permille > 210) { - stats.divesEAN++; - break; - } - } + if (std::any_of(dive->cylinders.begin(), dive->cylinders.end(), [] (auto &cyl) + { return cyl.gasmix.o2.permille > 210; })) + stats.divesEAN++; } // Returns a (first_dive, last_dive) pair static Stats loopDives(timestamp_t start) { Stats stats; - struct dive *dive; - int i; - - for_each_dive (i, dive) { + for (auto &dive: divelog.dives) { // check if dive is newer than primaryStart (add to first column) if (dive->when > start) - calculateDive(dive, stats); + calculateDive(dive.get(), stats); } return stats; } diff --git a/qt-models/divetripmodel.cpp b/qt-models/divetripmodel.cpp index 12929b605..45a319b18 100644 --- a/qt-models/divetripmodel.cpp +++ b/qt-models/divetripmodel.cpp @@ -28,8 +28,7 @@ static int nitrox_sort_value(const struct dive *dive) { - int o2, he, o2max; - get_dive_gas(dive, &o2, &he, &o2max); + auto [o2, he, o2max ] = dive->get_maximal_gas(); return he * 1000 + o2; } @@ -69,7 +68,7 @@ QString DiveTripModelBase::tripShortDate(const dive_trip *trip) { if (!trip) return QString(); - QDateTime firstTime = timestampToDateTime(trip_date(trip)); + QDateTime firstTime = timestampToDateTime(trip->date()); QString firstMonth = firstTime.toString("MMM"); return QStringLiteral("%1\n'%2").arg(firstMonth,firstTime.toString("yy")); } @@ -78,17 +77,17 @@ QString DiveTripModelBase::tripTitle(const dive_trip *trip) { if (!trip) return QString(); - QString numDives = tr("(%n dive(s))", "", trip->dives.nr); - int shown = trip_shown_dives(trip); - QString shownDives = shown != trip->dives.nr ? QStringLiteral(" ") + tr("(%L1 shown)").arg(shown) : QString(); - QString title(trip->location); + QString numDives = tr("(%n dive(s))", "", static_cast(trip->dives.size())); + int shown = trip->shown_dives(); + QString shownDives = shown != !trip->dives.empty() ? QStringLiteral(" ") + tr("(%L1 shown)").arg(shown) : QString(); + QString title = QString::fromStdString(trip->location); if (title.isEmpty()) { // so use the date range - QDateTime firstTime = timestampToDateTime(trip_date(trip)); + QDateTime firstTime = timestampToDateTime(trip->date()); QString firstMonth = firstTime.toString("MMM"); QString firstYear = firstTime.toString("yyyy"); - QDateTime lastTime = timestampToDateTime(trip->dives.dives[0]->when); + QDateTime lastTime = timestampToDateTime(trip->dives[0]->when); QString lastMonth = lastTime.toString("MMM"); QString lastYear = lastTime.toString("yyyy"); if (lastMonth == firstMonth && lastYear == firstYear) @@ -107,11 +106,11 @@ QVariant DiveTripModelBase::tripData(const dive_trip *trip, int column, int role // Special roles for mobile switch(role) { case MobileListModel::TripIdRole: return QString::number(trip->id); - case MobileListModel::TripNrDivesRole: return trip->dives.nr; + case MobileListModel::TripNrDivesRole: return static_cast(trip->dives.size()); case MobileListModel::TripShortDateRole: return tripShortDate(trip); case MobileListModel::TripTitleRole: return tripTitle(trip); - case MobileListModel::TripLocationRole: return QString(trip->location); - case MobileListModel::TripNotesRole: return QString(trip->notes); + case MobileListModel::TripLocationRole: return QString::fromStdString(trip->location); + case MobileListModel::TripNotesRole: return QString::fromStdString(trip->notes); } #endif // Set the font for all trips alike @@ -125,10 +124,10 @@ QVariant DiveTripModelBase::tripData(const dive_trip *trip, int column, int role switch (column) { case DiveTripModelBase::NR: QString shownText; - int countShown = trip_shown_dives(trip); - if (countShown < trip->dives.nr) + int countShown = trip->shown_dives(); + if (countShown < static_cast(trip->dives.size())) shownText = tr("(%1 shown)").arg(countShown); - return formatTripTitleWithDives(trip) + " " + shownText; + return formatTripTitleWithDives(*trip) + " " + shownText; } } @@ -145,10 +144,10 @@ static const QString icon_names[4] = { static int countPhotos(const struct dive *d) { // Determine whether dive has pictures, and whether they were taken during or before/after dive. const int bufperiod = 120; // A 2-min buffer period. Photos within 2 min of dive are assumed as - int diveTotaltime = dive_endtime(d) - d->when; // taken during the dive, not before/after. + int diveTotaltime = d->totaltime().seconds; // taken during the dive, not before/after. int pic_offset, icon_index = 0; - FOR_EACH_PICTURE (d) { // Step through each of the pictures for this dive: - pic_offset = picture->offset.seconds; + for (auto &picture: d->pictures) { // Step through each of the pictures for this dive: + pic_offset = picture.offset.seconds; if ((pic_offset < -bufperiod) | (pic_offset > diveTotaltime+bufperiod)) { icon_index |= 0x02; // If picture is before/after the dive // then set the appropriate bit ... @@ -162,9 +161,9 @@ static int countPhotos(const struct dive *d) static QString displayDuration(const struct dive *d) { if (prefs.units.show_units_table) - return get_dive_duration_string(d->duration.seconds, gettextFromC::tr("h"), gettextFromC::tr("min"), "", ":", d->dc.divemode == FREEDIVE); + return get_dive_duration_string(d->duration.seconds, gettextFromC::tr("h"), gettextFromC::tr("min"), "", ":", d->dcs[0].divemode == FREEDIVE); else - return get_dive_duration_string(d->duration.seconds, "", "", "", ":", d->dc.divemode == FREEDIVE); + return get_dive_duration_string(d->duration.seconds, "", "", "", ":", d->dcs[0].divemode == FREEDIVE); } static QString displayTemperature(const struct dive *d, bool units) @@ -184,7 +183,7 @@ static QString displaySac(const struct dive *d, bool units) static QString displayWeight(const struct dive *d, bool units) { - QString s = weight_string(total_weight(d)); + QString s = get_weight_string(d->total_weight(), false); if (!units) return s; else if (get_units()->weight == units::KG) @@ -280,30 +279,30 @@ QVariant DiveTripModelBase::diveData(const struct dive *d, int column, int role) case MobileListModel::DateTimeRole: return formatDiveDateTime(d); case MobileListModel::IdRole: return d->id; case MobileListModel::NumberRole: return d->number; - case MobileListModel::LocationRole: return get_dive_location(d); - case MobileListModel::DepthRole: return get_depth_string(d->dc.maxdepth.mm, true, true); + case MobileListModel::LocationRole: return QString::fromStdString(d->get_location()); + case MobileListModel::DepthRole: return get_depth_string(d->dcs[0].maxdepth.mm, true, true); case MobileListModel::DurationRole: return formatDiveDuration(d); - case MobileListModel::DepthDurationRole: return QStringLiteral("%1 / %2").arg(get_depth_string(d->dc.maxdepth.mm, true, true), + case MobileListModel::DepthDurationRole: return QStringLiteral("%1 / %2").arg(get_depth_string(d->dcs[0].maxdepth.mm, true, true), formatDiveDuration(d)); case MobileListModel::RatingRole: return d->rating; case MobileListModel::VizRole: return d->visibility; - case MobileListModel::SuitRole: return QString(d->suit); + case MobileListModel::SuitRole: return QString::fromStdString(d->suit); case MobileListModel::AirTempRole: return get_temperature_string(d->airtemp, true); case MobileListModel::WaterTempRole: return get_temperature_string(d->watertemp, true); case MobileListModel::SacRole: return formatSac(d); case MobileListModel::SumWeightRole: return formatSumWeight(d); - case MobileListModel::DiveGuideRole: return QString(d->diveguide); - case MobileListModel::BuddyRole: return QString(d->buddy); - case MobileListModel::TagsRole: return QString::fromStdString(taglist_get_tagstring(d->tag_list)); + case MobileListModel::DiveGuideRole: return QString::fromStdString(d->diveguide); + case MobileListModel::BuddyRole: return QString::fromStdString(d->buddy); + case MobileListModel::TagsRole: return QString::fromStdString(taglist_get_tagstring(d->tags)); case MobileListModel::NotesRole: return formatNotes(d); case MobileListModel::GpsRole: return formatDiveGPS(d); case MobileListModel::GpsDecimalRole: return format_gps_decimal(d); - case MobileListModel::NoDiveRole: return d->duration.seconds == 0 && d->dc.duration.seconds == 0; + case MobileListModel::NoDiveRole: return d->duration.seconds == 0 && d->dcs[0].duration.seconds == 0; case MobileListModel::DiveSiteRole: return QVariant::fromValue(d->dive_site); case MobileListModel::CylinderRole: return formatGetCylinder(d).join(", "); case MobileListModel::GetCylinderRole: return formatGetCylinder(d); case MobileListModel::CylinderListRole: return formatFullCylinderList(); - case MobileListModel::SingleWeightRole: return d->weightsystems.nr <= 1; + case MobileListModel::SingleWeightRole: return d->weightsystems.size() <= 1; case MobileListModel::StartPressureRole: return formatStartPressure(d); case MobileListModel::EndPressureRole: return formatEndPressure(d); case MobileListModel::FirstGasRole: return formatFirstGas(d); @@ -334,9 +333,9 @@ QVariant DiveTripModelBase::diveData(const struct dive *d, int column, int role) case TOTALWEIGHT: return displayWeight(d, prefs.units.show_units_table); case SUIT: - return QString(d->suit); + return QString::fromStdString(d->suit); case CYLINDER: - return d->cylinders.nr > 0 ? QString(get_cylinder(d, 0)->type.description) : QString(); + return !d->cylinders.empty() ? QString::fromStdString(d->cylinders[0].type.description) : QString(); case SAC: return displaySac(d, prefs.units.show_units_table); case OTU: @@ -347,23 +346,23 @@ QVariant DiveTripModelBase::diveData(const struct dive *d, int column, int role) else return d->maxcns; case TAGS: - return QString::fromStdString(taglist_get_tagstring(d->tag_list)); + return QString::fromStdString(taglist_get_tagstring(d->tags)); case PHOTOS: break; case COUNTRY: - return QString(get_dive_country(d)); + return QString::fromStdString(d->get_country()); case BUDDIES: - return QString(d->buddy); + return QString::fromStdString(d->buddy); case DIVEGUIDE: - return QString(d->diveguide); + return QString::fromStdString(d->diveguide); case LOCATION: - return QString(get_dive_location(d)); + return QString::fromStdString(d->get_location()); case GAS: return formatDiveGasString(d); case NOTES: - return QString(d->notes); + return QString::fromStdString(d->notes); case DIVEMODE: - return QString(divemode_text_ui[(int)d->dc.divemode]); + return QString(divemode_text_ui[(int)d->dcs[0].divemode]); } break; case Qt::DecorationRole: @@ -372,13 +371,13 @@ QVariant DiveTripModelBase::diveData(const struct dive *d, int column, int role) case COUNTRY: return QVariant(); case LOCATION: - if (dive_has_gps_location(d)) + if (d->dive_has_gps_location()) return getGlobeIcon(); break; case PHOTOS: // If there are photos, show one of the three photo icons: fish= photos during dive; // sun=photos before/after dive; sun+fish=photos during dive as well as before/after - if (d->pictures.nr > 0) + if (!d->pictures.empty()) return getPhotoIcon(countPhotos(d)); break; } @@ -707,18 +706,15 @@ void DiveTripModelTree::populate() // we want this to be two calls as the second text is overwritten below by the lines starting with "\r" uiNotification(QObject::tr("populate data model")); uiNotification(QObject::tr("start processing")); - for (int i = 0; i < divelog.dives->nr; ++i) { - dive *d = get_dive(i); - if (!d) // should never happen - continue; - update_cylinder_related_info(d); + for (auto &d: divelog.dives) { + divelog.dives.update_cylinder_related_info(*d); if (d->hidden_by_filter) continue; - dive_trip_t *trip = d->divetrip; + dive_trip *trip = d->divetrip; // If this dive doesn't have a trip, add as top-level item. if (!trip) { - items.emplace_back(d); + items.emplace_back(d.get()); continue; } @@ -728,16 +724,16 @@ void DiveTripModelTree::populate() { return item.d_or_t.trip == trip; }); if (it == items.end()) { // We didn't find an entry for this trip -> add one - items.emplace_back(trip, d); + items.emplace_back(trip, d.get()); } else { // We found the trip -> simply add the dive - it->dives.push_back(d); + it->dives.push_back(d.get()); } } // Remember the index of the current dive oldCurrent = current_dive; - uiNotification(QObject::tr("%1 dives processed").arg(divelog.dives->nr)); + uiNotification(QObject::tr("%1 dives processed").arg(divelog.dives.size())); } int DiveTripModelTree::rowCount(const QModelIndex &parent) const @@ -817,7 +813,7 @@ dive *DiveTripModelTree::Item::getDive() const timestamp_t DiveTripModelTree::Item::when() const { - return d_or_t.trip ? trip_date(d_or_t.trip) : d_or_t.dive->when; + return d_or_t.trip ? d_or_t.trip->date() : d_or_t.dive->when; } dive_or_trip DiveTripModelTree::tripOrDive(const QModelIndex &index) const @@ -846,7 +842,7 @@ void processByTrip(QVector dives, Function action) { // Sort lexicographically by trip then according to the dive_less_than() function. std::sort(dives.begin(), dives.end(), [](const dive *d1, const dive *d2) - { return d1->divetrip == d2->divetrip ? dive_less_than(d1, d2) : d1->divetrip < d2->divetrip; }); + { return d1->divetrip == d2->divetrip ? dive_less_than_ptr(d1, d2) : d1->divetrip < d2->divetrip; }); // Then, process the dives in batches by trip int i, j; // Begin and end of batch @@ -995,7 +991,7 @@ void DiveTripModelTree::addDivesToTrip(int trip, const QVector &dives) QModelIndex parent = createIndex(trip, 0, noParent); addInBatches(items[trip].dives, dives, - [](dive *d, dive *d2) { return dive_less_than(d, d2); }, // comp + [](dive *d, dive *d2) { return dive_less_than_ptr(d, d2); }, // comp [&](std::vector &items, const QVector &dives, int idx, int from, int to) { // inserter beginInsertRows(parent, idx, idx + to - from - 1); items.insert(items.begin() + idx, dives.begin() + from, dives.begin() + to); @@ -1199,10 +1195,10 @@ void DiveTripModelTree::divesDeletedInternal(dive_trip *trip, bool deleteTrip, c static QVector getDivesForSite(struct dive_site *ds) { QVector diveSiteDives; - diveSiteDives.reserve(ds->dives.nr); + diveSiteDives.reserve(ds->dives.size()); - for (int i = 0; i < ds->dives.nr; ++i) - diveSiteDives.push_back(ds->dives.dives[i]); + for (dive *d: ds->dives) + diveSiteDives.push_back(d); return diveSiteDives; } @@ -1485,12 +1481,11 @@ void DiveTripModelList::populate() DiveFilter::instance()->reset(); // The data was reset - update filter status. TODO: should this really be done here? // Fill model - items.reserve(divelog.dives->nr); - for (int i = 0; i < divelog.dives->nr; ++i) { - dive *d = get_dive(i); + items.reserve(divelog.dives.size()); + for (auto &d: divelog.dives) { if (!d || d->hidden_by_filter) continue; - items.push_back(d); + items.push_back(d.get()); } // Remember the index of the current dive @@ -1555,9 +1550,9 @@ QVariant DiveTripModelList::data(const QModelIndex &index, int role) const void DiveTripModelList::addDives(QVector &dives) { - std::sort(dives.begin(), dives.end(), dive_less_than); + std::sort(dives.begin(), dives.end(), dive_less_than_ptr); addInBatches(items, dives, - &dive_less_than, // comp + &dive_less_than_ptr, // comp [&](std::vector &items, const QVector &dives, int idx, int from, int to) { // inserter beginInsertRows(QModelIndex(), idx, idx + to - from - 1); items.insert(items.begin() + idx, dives.begin() + from, dives.begin() + to); @@ -1567,7 +1562,7 @@ void DiveTripModelList::addDives(QVector &dives) void DiveTripModelList::removeDives(QVector dives) { - std::sort(dives.begin(), dives.end(), dive_less_than); + std::sort(dives.begin(), dives.end(), dive_less_than_ptr); processRangesZip(items, dives, std::equal_to(), // Condition: dive-pointers are equal [&](std::vector &items, const QVector &, int from, int to, int) -> int { // Action @@ -1607,7 +1602,7 @@ void DiveTripModelList::diveSiteChanged(dive_site *ds, int field) void DiveTripModelList::divesChanged(const QVector &divesIn) { QVector dives = divesIn; - std::sort(dives.begin(), dives.end(), dive_less_than); + std::sort(dives.begin(), dives.end(), dive_less_than_ptr); ShownChange shownChange = updateShown(dives); removeDives(shownChange.newHidden); @@ -1641,7 +1636,7 @@ void DiveTripModelList::divesTimeChanged(timestamp_t delta, const QVector dives = visibleDives(divesIn); if (dives.empty()) return; - std::sort(dives.begin(), dives.end(), dive_less_than); + std::sort(dives.begin(), dives.end(), dive_less_than_ptr); // See comment for DiveTripModelTree::divesTimeChanged above. divesDeletedInternal(dives); // Use internal version to keep current dive @@ -1696,9 +1691,9 @@ void DiveTripModelList::tripSelected(dive_trip *trip, dive *currentDive) // In the list view, there are no trips, so simply transform this into // a dive selection. QVector dives; - dives.reserve(trip->dives.nr); - for (int i = 0; i < trip->dives.nr; ++i) - dives.push_back(trip->dives.dives[i]); + dives.reserve(trip->dives.size()); + for (auto dive: trip->dives) + dives.push_back(dive); divesSelectedSlot(dives, currentDive, -1); } @@ -1712,13 +1707,9 @@ static bool lessThanHelper(int diff1, int diff2) return diff1 < 0 || (diff1 == 0 && diff2 < 0); } -static int strCmp(const char *s1, const char *s2) +static int strCmp(const std::string &s1, const std::string &s2) { - if (!s1) - return !s2 ? 0 : -1; - if (!s2) - return 1; - return QString::localeAwareCompare(QString(s1), QString(s2)); // TODO: avoid copy + return QString::localeAwareCompare(QString::fromStdString(s1), QString::fromStdString(s2)); // TODO: avoid copy } bool DiveTripModelList::lessThan(const QModelIndex &i1, const QModelIndex &i2) const @@ -1746,13 +1737,13 @@ bool DiveTripModelList::lessThan(const QModelIndex &i1, const QModelIndex &i2) c case TEMPERATURE: return lessThanHelper(d1->watertemp.mkelvin - d2->watertemp.mkelvin, row_diff); case TOTALWEIGHT: - return lessThanHelper(total_weight(d1) - total_weight(d2), row_diff); + return lessThanHelper(d1->total_weight().grams - d2->total_weight().grams, row_diff); case SUIT: return lessThanHelper(strCmp(d1->suit, d2->suit), row_diff); case CYLINDER: - if (d1->cylinders.nr > 0 && d2->cylinders.nr > 0) - return lessThanHelper(strCmp(get_cylinder(d1, 0)->type.description, get_cylinder(d2, 0)->type.description), row_diff); - return d1->cylinders.nr - d2->cylinders.nr < 0; + if (!d1->cylinders.empty() && !d2->cylinders.empty()) + return lessThanHelper(strCmp(d1->cylinders[0].type.description, d2->cylinders[0].type.description), row_diff); + return d1->cylinders.size() < d2->cylinders.size(); case GAS: return lessThanHelper(nitrox_sort_value(d1) - nitrox_sort_value(d2), row_diff); case SAC: @@ -1762,24 +1753,24 @@ bool DiveTripModelList::lessThan(const QModelIndex &i1, const QModelIndex &i2) c case MAXCNS: return lessThanHelper(d1->maxcns - d2->maxcns, row_diff); case TAGS: { - std::string s1 = taglist_get_tagstring(d1->tag_list); - std::string s2 = taglist_get_tagstring(d2->tag_list); - int diff = strCmp(s1.c_str(), s2.c_str()); + std::string s1 = taglist_get_tagstring(d1->tags); + std::string s2 = taglist_get_tagstring(d2->tags); + int diff = strCmp(s1, s2); return lessThanHelper(diff, row_diff); } case PHOTOS: return lessThanHelper(countPhotos(d1) - countPhotos(d2), row_diff); case COUNTRY: - return lessThanHelper(strCmp(get_dive_country(d1), get_dive_country(d2)), row_diff); + return lessThanHelper(strCmp(d1->get_country(), d2->get_country()), row_diff); case BUDDIES: return lessThanHelper(strCmp(d1->buddy, d2->buddy), row_diff); case DIVEGUIDE: return lessThanHelper(strCmp(d1->diveguide, d2->diveguide), row_diff); case LOCATION: - return lessThanHelper(strCmp(get_dive_location(d1), get_dive_location(d2)), row_diff); + return lessThanHelper(strCmp(d1->get_location(), d2->get_location()), row_diff); case NOTES: return lessThanHelper(strCmp(d1->notes, d2->notes), row_diff); case DIVEMODE: - return lessThanHelper((int)d1->dc.divemode - (int)d2->dc.divemode, row_diff); + return lessThanHelper((int)d1->dcs[0].divemode - (int)d2->dcs[0].divemode, row_diff); } } diff --git a/qt-models/filterpresetmodel.cpp b/qt-models/filterpresetmodel.cpp index 52276315e..a78be8d24 100644 --- a/qt-models/filterpresetmodel.cpp +++ b/qt-models/filterpresetmodel.cpp @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 #include "filterpresetmodel.h" +#include "core/divelog.h" #include "core/filterconstraint.h" #include "core/filterpreset.h" +#include "core/filterpresettable.h" #include "core/qthelper.h" #include "core/subsurface-qt/divelistnotifier.h" @@ -26,13 +28,14 @@ FilterPresetModel *FilterPresetModel::instance() QVariant FilterPresetModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= filter_presets_count()) + if (index.row() < 0 || static_cast(index.row()) >= divelog.filter_presets.size()) return QVariant(); + const auto &filter_preset = divelog.filter_presets[index.row()]; switch (role) { case Qt::DisplayRole: if (index.column() == NAME) - return QString(filter_preset_name(index.row()).c_str()); + return QString::fromStdString(filter_preset.name); break; case Qt::DecorationRole: if (index.column() == REMOVE) @@ -52,7 +55,7 @@ QVariant FilterPresetModel::data(const QModelIndex &index, int role) const int FilterPresetModel::rowCount(const QModelIndex &parent) const { - return filter_presets_count(); + return static_cast(divelog.filter_presets.size()); } void FilterPresetModel::reset() diff --git a/qt-models/maplocationmodel.cpp b/qt-models/maplocationmodel.cpp index 96ef0d960..5de18fd39 100644 --- a/qt-models/maplocationmodel.cpp +++ b/qt-models/maplocationmodel.cpp @@ -4,11 +4,13 @@ #include "core/divesite.h" #include "core/divefilter.h" #include "core/divelog.h" +#include "core/range.h" #include "core/settings/qPrefDisplay.h" #if !defined(SUBSURFACE_MOBILE) && !defined(SUBSURFACE_DOWNLOADER) #include "qt-models/filtermodels.h" #include "desktop-widgets/mapwidget.h" #endif +#include #define MIN_DISTANCE_BETWEEN_DIVE_SITES_M 50.0 @@ -17,10 +19,10 @@ // Example: // Japan/Izu Peninsula/Atami/Chinsen-Aft // Short name: Chinsen-Aft -static QString siteMapDisplayName(const char *sitename) +static QString siteMapDisplayName(const std::string &sitename) { const char Separator = '/'; - QString fullname(sitename); + QString fullname = QString::fromStdString(sitename); if (!qPrefDisplay::map_short_names() ) return fullname; @@ -78,15 +80,14 @@ MapLocationModel::MapLocationModel(QObject *parent) : QAbstractListModel(parent) MapLocationModel::~MapLocationModel() { - qDeleteAll(m_mapLocations); } QVariant MapLocationModel::data(const QModelIndex & index, int role) const { - if (index.row() < 0 || index.row() >= m_mapLocations.size()) + if (index.row() < 0 || index.row() >= (int)m_mapLocations.size()) return QVariant(); - return m_mapLocations.at(index.row())->getRole(role); + return m_mapLocations[index.row()].getRole(role); } QHash MapLocationModel::roleNames() const @@ -103,51 +104,52 @@ QHash MapLocationModel::roleNames() const int MapLocationModel::rowCount(const QModelIndex&) const { - return m_mapLocations.size(); + return (int)m_mapLocations.size(); } +/* UNUSED? void MapLocationModel::add(MapLocation *location) { - beginInsertRows(QModelIndex(), m_mapLocations.size(), m_mapLocations.size()); + beginInsertRows(QModelIndex(), (int)m_mapLocations.size(), (int)m_mapLocations.size()); m_mapLocations.append(location); endInsertRows(); } +*/ -const QVector &MapLocationModel::selectedDs() const +const std::vector &MapLocationModel::selectedDs() const { return m_selectedDs; } -static bool hasVisibleDive(const dive_site *ds) +static bool hasVisibleDive(const dive_site &ds) { - return std::any_of(&ds->dives.dives[0], &ds->dives.dives[ds->dives.nr], + return std::any_of(ds.dives.begin(), ds.dives.end(), [] (const dive *d) { return !d->hidden_by_filter; }); } -static bool hasSelectedDive(const dive_site *ds) +static bool hasSelectedDive(const dive_site &ds) { - return std::any_of(&ds->dives.dives[0], &ds->dives.dives[ds->dives.nr], + return std::any_of(ds.dives.begin(), ds.dives.end(), [] (const dive *d) { return d->selected; }); } void MapLocationModel::selectionChanged() { - if (m_mapLocations.isEmpty()) + if (m_mapLocations.empty()) return; - for(MapLocation *m: m_mapLocations) - m->selected = m_selectedDs.contains(m->divesite); - emit dataChanged(createIndex(0, 0), createIndex(m_mapLocations.size() - 1, 0)); + for(MapLocation &m: m_mapLocations) + m.selected = range_contains(m_selectedDs, m.divesite); + emit dataChanged(createIndex(0, 0), createIndex((int)m_mapLocations.size() - 1, 0)); } void MapLocationModel::reload(QObject *map) { beginResetModel(); - qDeleteAll(m_mapLocations); m_mapLocations.clear(); m_selectedDs.clear(); - QMap locationNameMap; + std::map locationNameMap; #if defined(SUBSURFACE_MOBILE) || defined(SUBSURFACE_DOWNLOADER) bool diveSiteMode = false; @@ -160,18 +162,17 @@ void MapLocationModel::reload(QObject *map) if (diveSiteMode) m_selectedDs = DiveFilter::instance()->filteredDiveSites(); #endif - for (int i = 0; i < divelog.sites->nr; ++i) { - struct dive_site *ds = divelog.sites->dive_sites[i]; + for (const auto &ds: divelog.sites) { QGeoCoordinate dsCoord; // Don't show dive sites of hidden dives, unless we're in dive site edit mode. - if (!diveSiteMode && !hasVisibleDive(ds)) + if (!diveSiteMode && !hasVisibleDive(*ds)) continue; - if (!dive_site_has_gps_location(ds)) { + if (!ds->has_gps_location()) { // Dive sites that do not have a gps location are not shown in normal mode. // In dive-edit mode, selected sites are placed at the center of the map, // so that the user can drag them somewhere without having to enter coordinates. - if (!diveSiteMode || !m_selectedDs.contains(ds) || !map) + if (!diveSiteMode || !range_contains(m_selectedDs, ds.get()) || !map) continue; dsCoord = map->property("center").value(); } else { @@ -179,24 +180,24 @@ void MapLocationModel::reload(QObject *map) qreal longitude = ds->location.lon.udeg * 0.000001; dsCoord = QGeoCoordinate(latitude, longitude); } - if (!diveSiteMode && hasSelectedDive(ds) && !m_selectedDs.contains(ds)) - m_selectedDs.append(ds); + if (!diveSiteMode && hasSelectedDive(*ds) && !range_contains(m_selectedDs, ds.get())) + m_selectedDs.push_back(ds.get()); QString name = siteMapDisplayName(ds->name); if (!diveSiteMode) { // don't add dive locations with the same name, unless they are // at least MIN_DISTANCE_BETWEEN_DIVE_SITES_M apart - if (locationNameMap.contains(name)) { - MapLocation *existingLocation = locationNameMap[name]; - QGeoCoordinate coord = existingLocation->coordinate; + auto it = locationNameMap.find(name); + if (it != locationNameMap.end()) { + const MapLocation &existingLocation = m_mapLocations[it->second]; + QGeoCoordinate coord = existingLocation.coordinate; if (dsCoord.distanceTo(coord) < MIN_DISTANCE_BETWEEN_DIVE_SITES_M) continue; } } - bool selected = m_selectedDs.contains(ds); - MapLocation *location = new MapLocation(ds, dsCoord, name, selected); - m_mapLocations.append(location); + bool selected = range_contains(m_selectedDs, ds.get()); + m_mapLocations.emplace_back(ds.get(), dsCoord, name, selected); if (!diveSiteMode) - locationNameMap[name] = location; + locationNameMap[name] = m_mapLocations.size() - 1; } endResetModel(); @@ -206,19 +207,19 @@ void MapLocationModel::setSelected(struct dive_site *ds) { m_selectedDs.clear(); if (ds) - m_selectedDs.append(ds); + m_selectedDs.push_back(ds); } -void MapLocationModel::setSelected(const QVector &divesites) +void MapLocationModel::setSelected(const std::vector &divesites) { m_selectedDs = divesites; } MapLocation *MapLocationModel::getMapLocation(const struct dive_site *ds) { - for (MapLocation *location: m_mapLocations) { - if (ds == location->divesite) - return location; + for (MapLocation &location: m_mapLocations) { + if (location.divesite == ds) + return &location; } return nullptr; } @@ -226,12 +227,9 @@ MapLocation *MapLocationModel::getMapLocation(const struct dive_site *ds) void MapLocationModel::diveSiteChanged(struct dive_site *ds, int field) { // Find dive site - int row; - for (row = 0; row < m_mapLocations.size(); ++row) { - if (m_mapLocations[row]->divesite == ds) - break; - } - if (row == m_mapLocations.size()) + auto it = std::find_if(m_mapLocations.begin(), m_mapLocations.end(), + [ds](auto &entry) { return entry.divesite == ds; }); + if (it == m_mapLocations.end()) return; switch (field) { @@ -240,15 +238,16 @@ void MapLocationModel::diveSiteChanged(struct dive_site *ds, int field) const qreal latitude_r = ds->location.lat.udeg * 0.000001; const qreal longitude_r = ds->location.lon.udeg * 0.000001; QGeoCoordinate coord(latitude_r, longitude_r); - m_mapLocations[row]->coordinate = coord; + it->coordinate = coord; } break; case LocationInformationModel::NAME: - m_mapLocations[row]->name = siteMapDisplayName(ds->name); + it->name = siteMapDisplayName(ds->name); break; default: break; } + int row = static_cast(it - m_mapLocations.begin()); emit dataChanged(createIndex(row, 0), createIndex(row, 0)); } diff --git a/qt-models/maplocationmodel.h b/qt-models/maplocationmodel.h index 8cc5b7269..83b184676 100644 --- a/qt-models/maplocationmodel.h +++ b/qt-models/maplocationmodel.h @@ -3,8 +3,8 @@ #define MAPLOCATIONMODEL_H #include "core/subsurface-qt/divelistnotifier.h" +#include #include -#include #include #include #include @@ -13,7 +13,7 @@ class MapLocation { public: - explicit MapLocation(struct dive_site *ds, QGeoCoordinate coord, QString name, bool selected); + MapLocation(struct dive_site *ds, QGeoCoordinate coord, QString name, bool selected); QVariant getRole(int role) const; @@ -46,9 +46,9 @@ public: // If map is not null, it will be used to place new dive sites without GPS location at the center of the map void reload(QObject *map); void selectionChanged(); - void setSelected(const QVector &divesites); - MapLocation *getMapLocation(const struct dive_site *ds); - const QVector &selectedDs() const; + void setSelected(const std::vector &divesites); + MapLocation *getMapLocation(const struct dive_site *ds); // Attention: not stable! + const std::vector &selectedDs() const; void setSelected(struct dive_site *ds); protected: @@ -58,8 +58,8 @@ private slots: void diveSiteChanged(struct dive_site *ds, int field); private: - QVector m_mapLocations; - QVector m_selectedDs; + std::vector m_mapLocations; + std::vector m_selectedDs; }; #endif diff --git a/qt-models/models.cpp b/qt-models/models.cpp index a1f227eb1..f2c0c1dc5 100644 --- a/qt-models/models.cpp +++ b/qt-models/models.cpp @@ -13,44 +13,84 @@ #include #include -Qt::ItemFlags GasSelectionModel::flags(const QModelIndex&) const +GasSelectionModel::GasSelectionModel(const dive &d, int dcNr, QObject *parent) + : QAbstractListModel(parent) { - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; -} - -GasSelectionModel::GasSelectionModel(const dive &d, QObject *parent) - : QStringListModel(parent) -{ - setStringList(get_dive_gas_list(&d)); + gasNames = get_dive_gas_list(&d, dcNr, true); } QVariant GasSelectionModel::data(const QModelIndex &index, int role) const { - if (role == Qt::FontRole) + if (!index.isValid()) + return QVariant(); + + switch (role) { + case Qt::FontRole: return defaultModelFont(); - return QStringListModel::data(index, role); + case Qt::DisplayRole: + return gasNames.at(index.row()).second; + case Qt::UserRole: + return gasNames.at(index.row()).first; + } + + return QVariant(); +} + +int GasSelectionModel::rowCount(const QModelIndex&) const +{ + return gasNames.size(); } // Dive Type Model for the divetype combo box -Qt::ItemFlags DiveTypeSelectionModel::flags(const QModelIndex&) const +DiveTypeSelectionModel::DiveTypeSelectionModel(const dive &d, int dcNr, QObject *parent) : QAbstractListModel(parent) { - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; -} + divemode_t mode = d.get_dc(dcNr)->divemode; + for (int i = 0; i < FREEDIVE; i++) { + switch (mode) { + case OC: + default: + if (i != OC) + continue; -DiveTypeSelectionModel::DiveTypeSelectionModel(QObject *parent) : QStringListModel(parent) -{ - QStringList modes; - for (int i = 0; i < FREEDIVE; i++) - modes.append(gettextFromC::tr(divemode_text_ui[i])); - setStringList(modes); + break; + case CCR: + if (i != OC && i != CCR) + continue; + + break; + case PSCR: + if (i != OC && i != PSCR) + continue; + + break; + } + + + diveTypes.push_back(std::pair(i, gettextFromC::tr(divemode_text_ui[i]))); + } } QVariant DiveTypeSelectionModel::data(const QModelIndex &index, int role) const { - if (role == Qt::FontRole) + if (!index.isValid()) + return QVariant(); + + switch (role) { + case Qt::FontRole: return defaultModelFont(); - return QStringListModel::data(index, role); + case Qt::DisplayRole: + return diveTypes.at(index.row()).second; + case Qt::UserRole: + return diveTypes.at(index.row()).first; + } + + return QVariant(); +} + +int DiveTypeSelectionModel::rowCount(const QModelIndex&) const +{ + return diveTypes.size(); } // Language Model, The Model to populate the list of possible Languages. diff --git a/qt-models/models.h b/qt-models/models.h index 3db13a5b1..521c61430 100644 --- a/qt-models/models.h +++ b/qt-models/models.h @@ -21,20 +21,24 @@ struct dive; -class GasSelectionModel : public QStringListModel { +class GasSelectionModel : public QAbstractListModel { Q_OBJECT public: - GasSelectionModel(const dive &d, QObject *parent); - Qt::ItemFlags flags(const QModelIndex &index) const; + GasSelectionModel(const dive &d, int dcNr, QObject *parent); QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; +private: + std::vector> gasNames; }; -class DiveTypeSelectionModel : public QStringListModel { +class DiveTypeSelectionModel : public QAbstractListModel { Q_OBJECT public: - DiveTypeSelectionModel(QObject *parent); - Qt::ItemFlags flags(const QModelIndex &index) const; + DiveTypeSelectionModel(const dive &d, int dcNr, QObject *parent); QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; +private: + std::vector> diveTypes; }; class LanguageModel : public QAbstractListModel { diff --git a/qt-models/tankinfomodel.cpp b/qt-models/tankinfomodel.cpp index 0e260374d..c4195a417 100644 --- a/qt-models/tankinfomodel.cpp +++ b/qt-models/tankinfomodel.cpp @@ -7,15 +7,13 @@ QVariant TankInfoModel::data(const QModelIndex &index, int role) const { - if (index.row() < 0 || index.row() >= tank_info_table.nr) + if (index.row() < 0 || index.row() >= (int)tank_info_table.size()) return QVariant(); if (role == Qt::FontRole) return defaultModelFont(); if (role == Qt::DisplayRole || role == Qt::EditRole) { - const struct tank_info &info = tank_info_table.infos[index.row()]; - volume_t size = {0}; - pressure_t pressure = {0}; - extract_tank_info(&info, &size, &pressure); + const struct tank_info &info = tank_info_table[index.row()]; + auto [size, pressure] = extract_tank_info(info); switch (index.column()) { case BAR: @@ -23,7 +21,7 @@ QVariant TankInfoModel::data(const QModelIndex &index, int role) const case ML: return size.mliter; case DESCRIPTION: - return info.name; + return QString::fromStdString(info.name); } } return QVariant(); @@ -31,7 +29,7 @@ QVariant TankInfoModel::data(const QModelIndex &index, int role) const int TankInfoModel::rowCount(const QModelIndex&) const { - return tank_info_table.nr; + return (int)tank_info_table.size(); } TankInfoModel::TankInfoModel(QObject *parent) : CleanerTableModel(parent) diff --git a/qt-models/weightmodel.cpp b/qt-models/weightmodel.cpp index 296a2c8e6..7c14be60a 100644 --- a/qt-models/weightmodel.cpp +++ b/qt-models/weightmodel.cpp @@ -12,8 +12,7 @@ WeightModel::WeightModel(QObject *parent) : CleanerTableModel(parent), d(nullptr), - tempRow(-1), - tempWS(empty_weightsystem) + tempRow(-1) { //enum Column {REMOVE, TYPE, WEIGHT}; setHeaderDataStrings(QStringList() << tr("") << tr("Type") << tr("Weight")); @@ -26,11 +25,11 @@ WeightModel::WeightModel(QObject *parent) : CleanerTableModel(parent), weightsystem_t WeightModel::weightSystemAt(const QModelIndex &index) const { int row = index.row(); - if (row < 0 || row >= d->weightsystems.nr) { - qWarning("WeightModel: Accessing invalid weightsystem %d (of %d)", row, d->weightsystems.nr); - return empty_weightsystem; + if (row < 0 || static_cast(row) >= d->weightsystems.size()) { + qWarning("WeightModel: Accessing invalid weightsystem %d (of %d)", row, static_cast(d->weightsystems.size())); + return weightsystem_t(); } - return d->weightsystems.weightsystems[index.row()]; + return d->weightsystems[index.row()]; } void WeightModel::clear() @@ -40,7 +39,7 @@ void WeightModel::clear() QVariant WeightModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= d->weightsystems.nr) + if (!index.isValid() || static_cast(index.row()) >= d->weightsystems.size()) return QVariant(); weightsystem_t ws = index.row() == tempRow ? tempWS : weightSystemAt(index); @@ -54,7 +53,7 @@ QVariant WeightModel::data(const QModelIndex &index, int role) const case Qt::EditRole: switch (index.column()) { case TYPE: - return gettextFromC::tr(ws.description); + return gettextFromC::tr(ws.description.c_str()); case WEIGHT: return get_weight_string(ws.weight, true); } @@ -78,22 +77,18 @@ QVariant WeightModel::data(const QModelIndex &index, int role) const // Ownership of passed in weight system will be taken. Caller must not use it any longer. void WeightModel::setTempWS(int row, weightsystem_t ws) { - if (!d || row < 0 || row >= d->weightsystems.nr) { // Sanity check: row must exist - free_weightsystem(ws); + if (!d || row < 0 || static_cast(row) >= d->weightsystems.size()) // Sanity check: row must exist return; - } clearTempWS(); // Shouldn't be necessary, just in case: Reset old temporary row. // It is really hard to get the editor-close-hints and setModelData calls under // control. Therefore, if the row is set to the already existing entry, don't // enter temporary mode. - const weightsystem_t &oldWS = d->weightsystems.weightsystems[row]; - if (same_string(oldWS.description, ws.description)) { - free_weightsystem(ws); - } else { + const weightsystem_t &oldWS = d->weightsystems[row]; + if (oldWS.description != ws.description) { tempRow = row; - tempWS = ws; + tempWS = std::move(ws); // If the user had already set a weight, don't overwrite that if (oldWS.weight.grams && !oldWS.auto_filled) @@ -110,18 +105,18 @@ void WeightModel::clearTempWS() return; int oldRow = tempRow; tempRow = -1; - free_weightsystem(tempWS); + tempWS = weightsystem_t(); dataChanged(index(oldRow, TYPE), index(oldRow, WEIGHT)); } void WeightModel::commitTempWS() { #ifndef SUBSURFACE_MOBILE - if (tempRow < 0 || !d || tempRow > d->weightsystems.nr) + if (tempRow < 0 || !d || static_cast(tempRow) > d->weightsystems.size()) return; // Only submit a command if the type changed - weightsystem_t ws = d->weightsystems.weightsystems[tempRow]; - if (!same_string(ws.description, tempWS.description) || gettextFromC::tr(ws.description) != QString(tempWS.description)) { + weightsystem_t ws = d->weightsystems[tempRow]; + if (ws.description != tempWS.description || gettextFromC::tr(ws.description.c_str()) != QString::fromStdString(tempWS.description)) { int count = Command::editWeight(tempRow, tempWS, false); emit divesEdited(count); } @@ -138,7 +133,7 @@ bool WeightModel::setData(const QModelIndex &index, const QVariant &value, int r case WEIGHT: ws.weight = string_to_weight(qPrintable(vString)); ws.auto_filled = false; - int count = Command::editWeight(index.row(), ws, false); + int count = Command::editWeight(index.row(), std::move(ws), false); emit divesEdited(count); return true; } @@ -155,7 +150,7 @@ Qt::ItemFlags WeightModel::flags(const QModelIndex &index) const int WeightModel::rowCount(const QModelIndex&) const { - return d ? d->weightsystems.nr : 0; + return d ? static_cast(d->weightsystems.size()) : 0; } void WeightModel::updateDive(dive *dIn) diff --git a/qt-models/weightsysteminfomodel.cpp b/qt-models/weightsysteminfomodel.cpp index d784dca82..72580258e 100644 --- a/qt-models/weightsysteminfomodel.cpp +++ b/qt-models/weightsysteminfomodel.cpp @@ -1,17 +1,15 @@ // SPDX-License-Identifier: GPL-2.0 #include "qt-models/weightsysteminfomodel.h" -#include "core/subsurface-qt/divelistnotifier.h" -#include "core/dive.h" +#include "core/equipment.h" #include "core/metrics.h" #include "core/gettextfromc.h" QVariant WSInfoModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= rows) + if (index.row() < 0 || index.row() >= static_cast(ws_info_table.size())) return QVariant(); - struct ws_info_t *info = &ws_info[index.row()]; + const ws_info &info = ws_info_table[index.row()]; - int gr = info->grams; switch (role) { case Qt::FontRole: return defaultModelFont(); @@ -19,9 +17,10 @@ QVariant WSInfoModel::data(const QModelIndex &index, int role) const case Qt::EditRole: switch (index.column()) { case GR: - return gr; + return info.weight.grams; case DESCRIPTION: - return gettextFromC::tr(info->name); + // TODO: don't translate user supplied names + return gettextFromC::tr(info.name.c_str()); } break; } @@ -30,13 +29,10 @@ QVariant WSInfoModel::data(const QModelIndex &index, int role) const int WSInfoModel::rowCount(const QModelIndex&) const { - return rows; + return static_cast(ws_info_table.size()); } WSInfoModel::WSInfoModel(QObject *parent) : CleanerTableModel(parent) { setHeaderDataStrings(QStringList() << tr("Description") << tr("kg")); - rows = 0; - for (struct ws_info_t *info = ws_info; info->name && info < ws_info + MAX_WS_INFO; info++, rows++) - ; } diff --git a/qt-models/weightsysteminfomodel.h b/qt-models/weightsysteminfomodel.h index 04ebdbca4..3abf8af99 100644 --- a/qt-models/weightsysteminfomodel.h +++ b/qt-models/weightsysteminfomodel.h @@ -14,11 +14,8 @@ public: }; WSInfoModel(QObject *parent); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; - -private: - int rows; }; #endif diff --git a/qt-models/yearlystatisticsmodel.cpp b/qt-models/yearlystatisticsmodel.cpp index b6613d253..7fd5cbb9e 100644 --- a/qt-models/yearlystatisticsmodel.cpp +++ b/qt-models/yearlystatisticsmodel.cpp @@ -42,77 +42,63 @@ YearStatisticsItem::YearStatisticsItem(const stats_t &interval) : stats_interval QVariant YearStatisticsItem::data(int column, int role) const { - QVariant ret; - if (role == Qt::FontRole) { QFont font = defaultModelFont(); font.setBold(stats_interval.is_year); return font; } else if (role != Qt::DisplayRole) { - return ret; + return QVariant(); } switch (column) { case YEAR: if (stats_interval.is_trip) { - ret = QString(stats_interval.location); + return QString::fromStdString(stats_interval.location); } else { - ret = stats_interval.period; + return stats_interval.period; } - break; case DIVES: - ret = stats_interval.selection_size; - break; + return stats_interval.selection_size; case TOTAL_TIME: - ret = get_dive_duration_string(stats_interval.total_time.seconds, tr("h"), tr("min"), tr("sec"), " "); - break; + return get_dive_duration_string(stats_interval.total_time.seconds, tr("h"), tr("min"), tr("sec"), " "); case AVERAGE_TIME: - ret = formatMinutes(stats_interval.total_time.seconds / stats_interval.selection_size); - break; + return formatMinutes(stats_interval.total_time.seconds / stats_interval.selection_size); case SHORTEST_TIME: - ret = formatMinutes(stats_interval.shortest_time.seconds); - break; + return formatMinutes(stats_interval.shortest_time.seconds); case LONGEST_TIME: - ret = formatMinutes(stats_interval.longest_time.seconds); - break; + return formatMinutes(stats_interval.longest_time.seconds); case AVG_DEPTH: - ret = get_depth_string(stats_interval.avg_depth); - break; + return get_depth_string(stats_interval.avg_depth); case AVG_MAX_DEPTH: if (stats_interval.selection_size) - ret = get_depth_string(stats_interval.combined_max_depth.mm / stats_interval.selection_size); + return get_depth_string(stats_interval.combined_max_depth.mm / stats_interval.selection_size); break; case MIN_DEPTH: - ret = get_depth_string(stats_interval.min_depth); - break; + return get_depth_string(stats_interval.min_depth); case MAX_DEPTH: - ret = get_depth_string(stats_interval.max_depth); - break; + return get_depth_string(stats_interval.max_depth); case AVG_SAC: - ret = get_volume_string(stats_interval.avg_sac); - break; + return get_volume_string(stats_interval.avg_sac); case MIN_SAC: - ret = get_volume_string(stats_interval.min_sac); - break; + return get_volume_string(stats_interval.min_sac); case MAX_SAC: - ret = get_volume_string(stats_interval.max_sac); - break; + return get_volume_string(stats_interval.max_sac); case AVG_TEMP: if (stats_interval.combined_temp.mkelvin && stats_interval.combined_count) { temperature_t avg_temp; avg_temp.mkelvin = stats_interval.combined_temp.mkelvin / stats_interval.combined_count; - ret = get_temperature_string(avg_temp); + return get_temperature_string(avg_temp); } break; case MIN_TEMP: if (stats_interval.min_temp.mkelvin) - ret = get_temperature_string(stats_interval.min_temp); + return get_temperature_string(stats_interval.min_temp); break; case MAX_TEMP: if (stats_interval.max_temp.mkelvin) - ret = get_temperature_string(stats_interval.max_temp); + return get_temperature_string(stats_interval.max_temp); break; } - return ret; + return QVariant(); } YearlyStatisticsModel::YearlyStatisticsModel(QObject *parent) : TreeModel(parent) @@ -184,17 +170,15 @@ QVariant YearlyStatisticsModel::headerData(int section, Qt::Orientation orientat void YearlyStatisticsModel::update_yearly_stats() { - int i, month = 0; - unsigned int j, combined_months; - stats_summary_auto_free stats; QString label; temperature_t t_range_min,t_range_max; - calculate_stats_summary(&stats, false); + stats_summary stats = calculate_stats_summary(false); - for (i = 0; stats.stats_yearly != NULL && stats.stats_yearly[i].period; ++i) { - YearStatisticsItem *item = new YearStatisticsItem(stats.stats_yearly[i]); - combined_months = 0; - for (j = 0; combined_months < stats.stats_yearly[i].selection_size; ++j) { + int month = 0; + for (const auto &s: stats.stats_yearly) { + YearStatisticsItem *item = new YearStatisticsItem(s); + size_t combined_months = 0; + while (combined_months < s.selection_size) { combined_months += stats.stats_monthly[month].selection_size; YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_monthly[month]); item->children.append(iChild); @@ -205,10 +189,10 @@ void YearlyStatisticsModel::update_yearly_stats() item->parent = rootItem.get(); } - if (stats.stats_by_trip != NULL && stats.stats_by_trip[0].is_trip == true) { + if (stats.stats_by_trip[0].is_trip == true) { YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_trip[0]); - for (i = 1; stats.stats_by_trip != NULL && stats.stats_by_trip[i].is_trip; ++i) { - YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_trip[i]); + for (auto it = std::next(stats.stats_by_trip.begin()); it != stats.stats_by_trip.end(); ++it) { + YearStatisticsItem *iChild = new YearStatisticsItem(*it); item->children.append(iChild); iChild->parent = item; } @@ -217,12 +201,12 @@ void YearlyStatisticsModel::update_yearly_stats() } /* Show the statistic sorted by dive type */ - if (stats.stats_by_type != NULL && stats.stats_by_type[0].selection_size) { + if (stats.stats_by_type[0].selection_size) { YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_type[0]); - for (i = 1; i <= NUM_DIVEMODE; ++i) { - if (stats.stats_by_type[i].selection_size == 0) + for (auto it = std::next(stats.stats_by_type.begin()); it != stats.stats_by_type.end(); ++it) { + if (it->selection_size == 0) continue; - YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_type[i]); + YearStatisticsItem *iChild = new YearStatisticsItem(*it); item->children.append(iChild); iChild->parent = item; } @@ -231,35 +215,41 @@ void YearlyStatisticsModel::update_yearly_stats() } /* Show the statistic sorted by dive depth */ - if (stats.stats_by_depth != NULL && stats.stats_by_depth[0].selection_size) { + if (stats.stats_by_depth[0].selection_size) { YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_depth[0]); - for (i = 1; stats.stats_by_depth[i].is_trip; ++i) - if (stats.stats_by_depth[i].selection_size) { - label = QString(tr("%1 - %2")).arg(get_depth_string((i - 1) * (STATS_DEPTH_BUCKET * 1000), true, false), - get_depth_string(i * (STATS_DEPTH_BUCKET * 1000), true, false)); - stats.stats_by_depth[i].location = strdup(label.toUtf8().data()); - YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_depth[i]); + int i = 0; + for (auto it = std::next(stats.stats_by_depth.begin()); it != stats.stats_by_depth.end(); ++it) { + if (it->selection_size) { + QString label = QString(tr("%1 - %2")).arg(get_depth_string(i * (STATS_DEPTH_BUCKET * 1000), true, false), + get_depth_string((i + 1) * (STATS_DEPTH_BUCKET * 1000), true, false)); + it->location = label.toStdString(); + YearStatisticsItem *iChild = new YearStatisticsItem(*it); item->children.append(iChild); iChild->parent = item; } + i++; + } rootItem->children.append(item); item->parent = rootItem.get(); } /* Show the statistic sorted by dive temperature */ - if (stats.stats_by_temp != NULL && stats.stats_by_temp[0].selection_size) { + if (stats.stats_by_temp[0].selection_size) { YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_temp[0]); - for (i = 1; stats.stats_by_temp[i].is_trip; ++i) - if (stats.stats_by_temp[i].selection_size) { - t_range_min.mkelvin = C_to_mkelvin((i - 1) * STATS_TEMP_BUCKET); - t_range_max.mkelvin = C_to_mkelvin(i * STATS_TEMP_BUCKET); + int i = 0; + for (auto it = std::next(stats.stats_by_temp.begin()); it != stats.stats_by_temp.end(); ++it) { + if (it->selection_size) { + t_range_min.mkelvin = C_to_mkelvin(i * STATS_TEMP_BUCKET); + t_range_max.mkelvin = C_to_mkelvin((i + 1) * STATS_TEMP_BUCKET); label = QString(tr("%1 - %2")).arg(get_temperature_string(t_range_min, true), get_temperature_string(t_range_max, true)); - stats.stats_by_temp[i].location = strdup(label.toUtf8().data()); - YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_temp[i]); + it->location = label.toStdString(); + YearStatisticsItem *iChild = new YearStatisticsItem(*it); item->children.append(iChild); iChild->parent = item; } + i++; + } rootItem->children.append(item); item->parent = rootItem.get(); } diff --git a/scripts/build.sh b/scripts/build.sh index 95b3e242f..69dcb649d 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -376,17 +376,6 @@ if [[ $PLATFORM = Darwin && "$BUILD_DEPS" == "1" ]] ; then make install popd - ./${SRC_DIR}/scripts/get-dep-lib.sh single . libcurl - pushd libcurl - bash ./buildconf - mkdir -p build - cd build - CFLAGS="$MAC_OPTS" ../configure --prefix="$INSTALL_ROOT" --with-openssl \ - --disable-tftp --disable-ftp --disable-ldap --disable-ldaps --disable-imap --disable-pop3 --disable-smtp --disable-gopher --disable-smb --disable-rtsp - make -j4 - make install - popd - # openssl doesn't support fat binaries out of the box # this tries to hack around this by first doing an install for x86_64, then a build for arm64 # and then manually creating fat libraries from that @@ -416,6 +405,17 @@ if [[ $PLATFORM = Darwin && "$BUILD_DEPS" == "1" ]] ; then fi popd + ./${SRC_DIR}/scripts/get-dep-lib.sh single . libcurl + pushd libcurl + bash ./buildconf + mkdir -p build + cd build + CFLAGS="$MAC_OPTS" ../configure --prefix="$INSTALL_ROOT" --with-openssl \ + --disable-tftp --disable-ftp --disable-ldap --disable-ldaps --disable-imap --disable-pop3 --disable-smtp --disable-gopher --disable-smb --disable-rtsp + make -j4 + make install + popd + ./${SRC_DIR}/scripts/get-dep-lib.sh single . libssh2 pushd libssh2 mkdir -p build diff --git a/scripts/docker/android-build-container/Dockerfile b/scripts/docker/android-build-container/Dockerfile index b42959871..f42f679cc 100644 --- a/scripts/docker/android-build-container/Dockerfile +++ b/scripts/docker/android-build-container/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 as base +FROM ubuntu:24.04 as base RUN apt-get update && \ apt-get dist-upgrade -y && \ @@ -83,7 +83,6 @@ RUN apt-get install -y \ bzip2 \ pkg-config \ libx11-xcb1 \ - libgl1-mesa-glx \ libglib2.0-0 \ openjdk-8-jdk \ curl \ diff --git a/scripts/docker/mxe-build-container/build-container.sh b/scripts/docker/mxe-build-container/build-container.sh deleted file mode 100755 index 617fcd1b0..000000000 --- a/scripts/docker/mxe-build-container/build-container.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -x -set -e - -# known good MXE sha -MXE_SHA="c0bfefc57a00fdf6cb5278263e21a478e47b0bf5" -SCRIPTPATH=$(dirname $0) - -# version of the docker image -VERSION=3.1.0 - -pushd $SCRIPTPATH - -# we use the 'experimental' --squash argument to significantly reduce the size of the massively huge -# Docker container this produces -docker build -t subsurface/mxe-build:$VERSION --build-arg=mxe_sha=$MXE_SHA -f Dockerfile . -docker images -popd diff --git a/scripts/docker/mxe-build-container/settings.mk b/scripts/docker/mxe-build-container/settings.mk index 995b3e4c0..56d4835ee 100644 --- a/scripts/docker/mxe-build-container/settings.mk +++ b/scripts/docker/mxe-build-container/settings.mk @@ -30,6 +30,7 @@ LOCAL_PKG_LIST := gcc \ libgit2 \ libftdi1 \ mdbtools \ + libraw \ qtconnectivity \ qtdeclarative \ qtimageformats \ diff --git a/scripts/get-version.sh b/scripts/get-version.sh index 73d9299a4..6ac5f307c 100755 --- a/scripts/get-version.sh +++ b/scripts/get-version.sh @@ -48,7 +48,7 @@ if [ ! -f latest-subsurface-buildnumber ] ; then LAST_BUILD_BRANCHES=$(git branch -a --sort=-committerdate --list | grep remotes/origin/branch-for | cut -d/ -f3) for LAST_BUILD_BRANCH in $LAST_BUILD_BRANCHES "not-found" ; do LAST_BUILD_SHA=$(cut -d- -f 3 <<< "$LAST_BUILD_BRANCH") - git -C "$SUBSURFACE_SOURCE" merge-base --is-ancestor "$LAST_BUILD_SHA" HEAD && break + git -C "$SUBSURFACE_SOURCE" merge-base --is-ancestor "$LAST_BUILD_SHA" HEAD 2> /dev/null&& break done [ "not-found" = "$LAST_BUILD_BRANCH" ] && croak "can't find a build number for the current working tree" git checkout "$LAST_BUILD_BRANCH" &> /dev/null || croak "failed to check out $LAST_BUILD_BRANCH in nightly-builds" diff --git a/scripts/smtk2ssrf-build.sh b/scripts/smtk2ssrf-build.sh index b78bf5321..976ee2107 100755 --- a/scripts/smtk2ssrf-build.sh +++ b/scripts/smtk2ssrf-build.sh @@ -148,6 +148,7 @@ cmake -DBTSUPPORT=OFF \ -DNO_DOCS=ON \ -DNO_PRINTING=ON \ -DNO_USERMANUAL=ON \ + -DLIBRAW_SUPPORT=OFF \ -DSUBSURFACE_TARGET_EXECUTABLE=DesktopExecutable \ build cd build || aborting "Couldn't cd into $SSRF_PATH/build directory" diff --git a/scripts/whitespace.pl b/scripts/whitespace.pl index be1c4416a..3e96a2b0c 100755 --- a/scripts/whitespace.pl +++ b/scripts/whitespace.pl @@ -3,7 +3,7 @@ my $input = $ARGV[0]; my $source = `clang-format $input`; -# for_each_dive (...) and friends... +# for_each (...) and friends... $source =~ s/(?:\G|^)(.*each.*\(.*) \* (\S.*\))$/$1 *$2/img; # if a variable is declared in the argument, '*' is an indicator for a pointer, not arithmatic $source =~ s/(?:\G|^)(.*each.*\(.*) \& (\S.*\))$/$1 &$2/img; # if a variable is declared in the argument, '&' is an indicator for a reference, not bit logic $source =~ s/(?:\G|^)(.*each[^\s(]*)\s*(\(.*)$/$1 $2/img; # we want exactly one space between keyword and opening parenthesis '(' diff --git a/smtk-import/CMakeLists.txt b/smtk-import/CMakeLists.txt index 0d90aa74b..db0971d3b 100644 --- a/smtk-import/CMakeLists.txt +++ b/smtk-import/CMakeLists.txt @@ -34,7 +34,6 @@ pkg_config_library(GLIB2 glib-2.0 REQUIRED) pkg_config_library(LIBGIT2 libgit2 REQUIRED) pkg_config_library(LIBMDB libmdb REQUIRED) pkg_config_library(LIBDC libdivecomputer REQUIRED) -pkg_config_library(LIBFTDI libftdi1 QUIET) find_package(Qt5 REQUIRED COMPONENTS Core Concurrent diff --git a/smtk-import/cmake/Modules/version.cmake b/smtk-import/cmake/Modules/version.cmake index d3ee23b73..6aa00a994 100644 --- a/smtk-import/cmake/Modules/version.cmake +++ b/smtk-import/cmake/Modules/version.cmake @@ -4,7 +4,7 @@ if(DEFINED ENV{CANONICALVERSION}) set(CANONICAL_VERSION_STRING $ENV{CANONICALVERSION}) else() execute_process( - COMMAND bash ${CMAKE_TOP_SRC_DIR}/scripts/get-version.sh + COMMAND bash ${CMAKE_TOP_SRC_DIR}/../scripts/get-version.sh WORKING_DIRECTORY ${CMAKE_TOP_SRC_DIR} OUTPUT_VARIABLE CANONICAL_VERSION_STRING OUTPUT_STRIP_TRAILING_WHITESPACE @@ -15,7 +15,7 @@ if(DEFINED ENV{CANONICALVERSION_4}) set(CANONICAL_VERSION_STRING_4 $ENV{CANONICALVERSION_4}) else() execute_process( - COMMAND bash ${CMAKE_TOP_SRC_DIR}/scripts/get-version.sh 4 + COMMAND bash ${CMAKE_TOP_SRC_DIR}/../scripts/get-version.sh 4 WORKING_DIRECTORY ${CMAKE_TOP_SRC_DIR} OUTPUT_VARIABLE CANONICAL_VERSION_STRING_4 OUTPUT_STRIP_TRAILING_WHITESPACE diff --git a/smtk-import/smartrak.cpp b/smtk-import/smartrak.cpp index 0ac692398..50910cac2 100644 --- a/smtk-import/smartrak.cpp +++ b/smtk-import/smartrak.cpp @@ -264,26 +264,6 @@ static void concat(std::string &orig, const char *sep, std::string_view s) orig += s; } -/* - * Temporary funcion as long as core still has C-strings. - * Equivalent to concat, but takes a C-string as first argument and - * frees it. Returns a newly allocated copy. - * - * Note: taking "const char * const *" is an ugly hack to - * allow passing pointer to "const char *" as well as - * "char *". Which in turn is necessary, as this function currently - * replaces "const char *" strings. - */ -static void concat(const char * const *orig_in, const char *sep, std::string_view s) -{ - char **orig = const_cast(orig_in); - char *to_free = *orig; - std::string orig_std(*orig ? *orig : ""); - concat(orig_std, sep, s); - *orig = strdup(orig_std.c_str()); - free(to_free); -} - /* * A site may be a wreck, which has its own table. * Parse this table referred by the site idx. If found, put the different info items in @@ -347,7 +327,7 @@ static void smtk_wreck_site(MdbHandle *mdb, char *site_idx, struct dive_site *ds break; } } - concat(&ds->notes, "\n", notes); + concat(ds->notes, "\n", notes); break; } } @@ -423,18 +403,17 @@ static void smtk_build_location(MdbHandle *mdb, char *idx, struct dive_site **lo concat(str, ", ", table.get_string_view(1)); // Locality concat(str, ", ", site); - ds = get_dive_site_by_name(str.c_str(), log->sites); + ds = log->sites.get_by_name(str); if (!ds) { if (!has_location(&loc)) - ds = create_dive_site(str.c_str(), log->sites); + ds = log->sites.create(str); else - ds = create_dive_site_with_gps(str.c_str(), &loc, log->sites); + ds = log->sites.create(str, loc); } *location = ds; /* Insert site notes */ - free(ds->notes); - ds->notes = strdup(notes.c_str()); + ds->notes = notes.c_str(); /* Check if we have a wreck */ smtk_wreck_site(mdb, idx, ds); @@ -450,7 +429,7 @@ static void smtk_build_tank_info(MdbHandle *mdb, cylinder_t *tank, char *idx) for (i = 1; i <= atoi(idx); i++) table.fetch_row(); - tank->type.description = copy_string(table.get_data(1)); + tank->type.description = table.get_data(1); tank->type.size.mliter = lrint(strtod(table.get_data(2), NULL) * 1000); tank->type.workingpressure.mbar = lrint(strtod(table.get_data(4), NULL) * 1000); } @@ -473,9 +452,9 @@ static bool is_same_cylinder(cylinder_t *cyl_a, cylinder_t *cyl_b) if (!(abs(cyl_a->end.mbar - cyl_b->end.mbar) <= 100)) return false; // different names (none of them null) - if (!same_string(cyl_a->type.description, "---") && - !same_string(cyl_b->type.description, "---") && - !same_string(cyl_a->type.description, cyl_b->type.description)) + if (cyl_a->type.description != "---" && + cyl_b->type.description != "---" && + cyl_a->type.description != cyl_b->type.description) return false; // Cylinders are most probably the same return true; @@ -493,12 +472,11 @@ static bool is_same_cylinder(cylinder_t *cyl_a, cylinder_t *cyl_b) static void merge_cylinder_type(cylinder_type_t *src, cylinder_type_t *dst) { if (!dst->size.mliter) - dst->size.mliter = src->size.mliter; + dst->size = src->size; if (!dst->workingpressure.mbar) - dst->workingpressure.mbar = src->workingpressure.mbar; - if (!dst->description || same_string(dst->description, "---")) { - dst->description = src->description; - src->description = NULL; + dst->workingpressure = src->workingpressure; + if (dst->description.empty() || dst->description == "---") { + dst->description = std::move(src->description); } } @@ -529,11 +507,11 @@ static void merge_cylinder_info(cylinder_t *src, cylinder_t *dst) static void smtk_clean_cylinders(struct dive *d) { int i = tanks - 1; - cylinder_t *cyl, *base = get_cylinder(d, 0); + cylinder_t *cyl, *base = d->get_cylinder(0); cyl = base + tanks - 1; while (cyl != base) { - if (same_string(cyl->type.description, "---") && cyl->start.mbar == 0 && cyl->end.mbar == 0) + if (cyl->type.description == "---" && cyl->start.mbar == 0 && cyl->end.mbar == 0) remove_cylinder(d, i); else if (is_same_cylinder(cyl, cyl - 1)) { @@ -693,16 +671,16 @@ static void smtk_parse_relations(MdbHandle *mdb, struct dive *dive, char *dive_i if (str.empty()) continue; if (tag) - taglist_add_tag(&dive->tag_list, str.c_str()); + taglist_add_tag(dive->tags, str); else concat(tmp, ", ", str); if (str.find("SCR") != std::string::npos) - dive->dc.divemode = PSCR; + dive->dcs[0].divemode = PSCR; else if (str.find("CCR") != std::string::npos) - dive->dc.divemode = CCR; + dive->dcs[0].divemode = CCR; } if (!tmp.empty()) - concat(&dive->notes, "\n", format_string_std("Smartrak %s: %s", table_name, tmp.c_str())); + concat(dive->notes, "\n", format_string_std("Smartrak %s: %s", table_name, tmp.c_str())); } /* @@ -719,9 +697,9 @@ static void smtk_parse_other(struct dive *dive, const std::vector & const std::string &str = list[i]; if (!str.empty()) { if (tag) - taglist_add_tag(&dive->tag_list, str.c_str()); + taglist_add_tag(dive->tags, str); else - concat(&dive->notes, "\n", format_string_std("Smartrak %s: %s", data_name, str.c_str())); + concat(dive->notes, "\n", format_string_std("Smartrak %s: %s", data_name, str.c_str())); } } @@ -729,16 +707,12 @@ static void smtk_parse_other(struct dive *dive, const std::vector & * Returns a pointer to a bookmark event in an event list if it exists for * a given time. Return NULL otherwise. */ -static struct event *find_bookmark(struct event *orig, int t) +static struct event *find_bookmark(struct divecomputer &dc, int t) { - struct event *ev = orig; - - while (ev) { - if ((ev->time.seconds == t) && (ev->type == SAMPLE_EVENT_BOOKMARK)) - return ev; - ev = ev->next; - } - return NULL; + auto it = std::find_if(dc.events.begin(), dc.events.end(), + [t](auto &ev) + { return ev.time.seconds == t && ev.type == SAMPLE_EVENT_BOOKMARK; }); + return it != dc.events.end() ? &*it : nullptr; } /* @@ -764,11 +738,11 @@ static void smtk_parse_bookmarks(MdbHandle *mdb, struct dive *d, char *dive_idx) if (same_string(table.get_data(0), dive_idx)) { time = lrint(strtod(table.get_data(4), NULL) * 60); const char *tmp = table.get_data(2); - ev = find_bookmark(d->dc.events, time); + ev = find_bookmark(d->dcs[0], time); if (ev) - update_event_name(d, 0, ev, tmp); + ev->name = tmp; else - if (!add_event(&d->dc, time, SAMPLE_EVENT_BOOKMARK, 0, 0, tmp)) + if (!add_event(&d->dcs[0], time, SAMPLE_EVENT_BOOKMARK, 0, 0, tmp)) report_error("[smtk-import] Error - Couldn't add bookmark, dive %d, Name = %s", d->number, tmp); } @@ -819,36 +793,29 @@ static dc_descriptor_t *get_data_descriptor(int data_model, dc_family_t data_fam * DC. dc_family_t is certainly known *only* if it is Aladin/Memomouse family * otherwise it will be known after get_data_descriptor call. */ -static dc_status_t prepare_data(int data_model, const char *serial, dc_family_t dc_fam, device_data_t *dev_data) +static dc_status_t prepare_data(int data_model, const char *serial, dc_family_t dc_fam, device_data_t &dev_data) { - dev_data->device = NULL; - dev_data->context = NULL; + dev_data.device = NULL; + dev_data.context = NULL; if (!data_model) { - dev_data->model = copy_string(manual_dc_name); - dev_data->descriptor = NULL; + dev_data.model = manual_dc_name; + dev_data.descriptor = NULL; return DC_STATUS_NODEVICE; } - dev_data->descriptor = get_data_descriptor(data_model, dc_fam); - if (dev_data->descriptor) { - dev_data->vendor = dc_descriptor_get_vendor(dev_data->descriptor); - dev_data->product = dc_descriptor_get_product(dev_data->descriptor); - concat(&dev_data->model, "", format_string_std("%s %s", dev_data->vendor, dev_data->product)); - dev_data->devinfo.serial = (uint32_t) lrint(strtod(serial, NULL)); + dev_data.descriptor = get_data_descriptor(data_model, dc_fam); + if (dev_data.descriptor) { + dev_data.vendor = dc_descriptor_get_vendor(dev_data.descriptor); + dev_data.product = dc_descriptor_get_product(dev_data.descriptor); + dev_data.model += dev_data.vendor + " " + dev_data.product; + dev_data.devinfo.serial = (uint32_t) lrint(strtod(serial, NULL)); return DC_STATUS_SUCCESS; } else { - dev_data->model = copy_string("unsupported dive computer"); - dev_data->devinfo.serial = (uint32_t) lrint(strtod(serial, NULL)); + dev_data.model = "unsupported dive computer"; + dev_data.devinfo.serial = (uint32_t) lrint(strtod(serial, NULL)); return DC_STATUS_UNSUPPORTED; } } -static void device_data_free(device_data_t *dev_data) -{ - free((void *) dev_data->model); - dc_descriptor_free(dev_data->descriptor); - free(dev_data); -} - /* * Returns a buffer prepared for libdc parsing. * Aladin and memomouse dives were imported from datatrak, so they lack of a @@ -882,7 +849,7 @@ static dc_status_t libdc_buffer_complete(device_data_t *dev_data, unsigned char * a DB clone is necessary as calling mdb_fetch_row() over different tables in * a single DB breaks binded row data, and so would break the top loop. */ -extern "C" void smartrak_import(const char *file, struct divelog *log) +void smartrak_import(const char *file, struct divelog *log) { MdbHandle *mdb, *mdb_clon; MdbColumn *col[MDB_MAX_COLS]; @@ -931,10 +898,10 @@ extern "C" void smartrak_import(const char *file, struct divelog *log) return; } while (mdb_table.fetch_row()) { - device_data_t *devdata = (device_data_t *)calloc(1, sizeof(device_data_t)); + device_data_t devdata; dc_family_t dc_fam = DC_FAMILY_NULL; unsigned char *prf_buffer, *hdr_buffer; - struct dive *smtkdive = alloc_dive(); + auto smtkdive = std::make_unique(); struct tm tm_date; size_t hdr_length, prf_length; dc_status_t rc = DC_STATUS_SUCCESS; @@ -953,34 +920,34 @@ extern "C" void smartrak_import(const char *file, struct divelog *log) dc_fam = DC_FAMILY_UWATEC_ALADIN; } rc = prepare_data(dc_model, (char *)col[coln(DCNUMBER)]->bind_ptr, dc_fam, devdata); - smtkdive->dc.model = copy_string(devdata->model); + smtkdive->dcs[0].model = devdata.model; if (rc == DC_STATUS_SUCCESS && mdb_table.get_len(coln(PROFILE))) { prf_buffer = static_cast(mdb_ole_read_full(mdb, col[coln(PROFILE)], &prf_length)); if (prf_length > 0) { - if (dc_descriptor_get_type(devdata->descriptor) == DC_FAMILY_UWATEC_ALADIN || dc_descriptor_get_type(devdata->descriptor) == DC_FAMILY_UWATEC_MEMOMOUSE) + if (dc_descriptor_get_type(devdata.descriptor) == DC_FAMILY_UWATEC_ALADIN || dc_descriptor_get_type(devdata.descriptor) == DC_FAMILY_UWATEC_MEMOMOUSE) hdr_length = 18; std::vector compl_buffer(hdr_length+prf_length); - rc = libdc_buffer_complete(devdata, hdr_buffer, hdr_length, prf_buffer, prf_length, compl_buffer.data()); + rc = libdc_buffer_complete(&devdata, hdr_buffer, hdr_length, prf_buffer, prf_length, compl_buffer.data()); if (rc != DC_STATUS_SUCCESS) { report_error("[Error][smartrak_import]\t- %s - for dive %d", errmsg(rc), smtkdive->number); } else { - rc = libdc_buffer_parser(smtkdive, devdata, compl_buffer.data(), hdr_length + prf_length); + rc = libdc_buffer_parser(smtkdive.get(), &devdata, compl_buffer.data(), hdr_length + prf_length); if (rc != DC_STATUS_SUCCESS) report_error("[Error][libdc]\t\t- %s - for dive %d", errmsg(rc), smtkdive->number); } } else { /* Dives without profile samples (usual in older aladin series) */ report_error("[Warning][smartrak_import]\t No profile for dive %d", smtkdive->number); - smtkdive->dc.duration.seconds = smtkdive->duration.seconds = smtk_time_to_secs((char *)col[coln(DURATION)]->bind_ptr); - smtkdive->dc.maxdepth.mm = smtkdive->maxdepth.mm = lrint(strtod((char *)col[coln(MAXDEPTH)]->bind_ptr, NULL) * 1000); + smtkdive->dcs[0].duration.seconds = smtkdive->duration.seconds = smtk_time_to_secs((char *)col[coln(DURATION)]->bind_ptr); + smtkdive->dcs[0].maxdepth.mm = smtkdive->maxdepth.mm = lrint(strtod((char *)col[coln(MAXDEPTH)]->bind_ptr, NULL) * 1000); } free(hdr_buffer); free(prf_buffer); } else { /* Manual dives or unknown DCs */ report_error("[Warning][smartrak_import]\t Manual or unknown dive computer for dive %d", smtkdive->number); - smtkdive->dc.duration.seconds = smtkdive->duration.seconds = smtk_time_to_secs((char *)col[coln(DURATION)]->bind_ptr); - smtkdive->dc.maxdepth.mm = smtkdive->maxdepth.mm = lrint(strtod((char *)col[coln(MAXDEPTH)]->bind_ptr, NULL) * 1000); + smtkdive->dcs[0].duration.seconds = smtkdive->duration.seconds = smtk_time_to_secs((char *)col[coln(DURATION)]->bind_ptr); + smtkdive->dcs[0].maxdepth.mm = smtkdive->maxdepth.mm = lrint(strtod((char *)col[coln(MAXDEPTH)]->bind_ptr, NULL) * 1000); } /* * Cylinder and gasmixes completion. @@ -993,7 +960,7 @@ extern "C" void smartrak_import(const char *file, struct divelog *log) int tankidxcol = coln(TANKIDX); for (i = 0; i < tanks; i++) { - cylinder_t *tmptank = get_or_create_cylinder(smtkdive, i); + cylinder_t *tmptank = smtkdive->get_or_create_cylinder(i); if (!tmptank) break; if (tmptank->start.mbar == 0) @@ -1011,18 +978,18 @@ extern "C" void smartrak_import(const char *file, struct divelog *log) if (tmptank->gasmix.he.permille == 0) tmptank->gasmix.he.permille = lrint(strtod((char *)col[i + hefraccol]->bind_ptr, NULL) * 10); } else { - tmptank->gasmix.he.permille = 0; + tmptank->gasmix.he = 0_percent; } smtk_build_tank_info(mdb_clon, tmptank, (char *)col[i + tankidxcol]->bind_ptr); } /* Check for duplicated cylinders and clean them */ - smtk_clean_cylinders(smtkdive); + smtk_clean_cylinders(smtkdive.get()); /* Date issues with libdc parser - Take date time from mdb */ smtk_date_to_tm((char *)col[coln(_DATE)]->bind_ptr, &tm_date); smtk_time_to_tm((char *)col[coln(INTIME)]->bind_ptr, &tm_date); - smtkdive->dc.when = smtkdive->when = smtk_timegm(&tm_date); - smtkdive->dc.surfacetime.seconds = smtk_time_to_secs((char *)col[coln(INTVAL)]->bind_ptr); + smtkdive->dcs[0].when = smtkdive->when = smtk_timegm(&tm_date); + smtkdive->dcs[0].surfacetime.seconds = smtk_time_to_secs((char *)col[coln(INTVAL)]->bind_ptr); /* Data that user may have registered manually if not supported by DC, or not parsed */ if (!smtkdive->airtemp.mkelvin) @@ -1034,27 +1001,26 @@ extern "C" void smartrak_import(const char *file, struct divelog *log) /* No DC related data */ smtkdive->visibility = strtod((char *)col[coln(VISIBILITY)]->bind_ptr, NULL) > 25 ? 5 : lrint(strtod((char *)col[13]->bind_ptr, NULL) / 5); - weightsystem_t ws = { {(int)lrint(strtod((char *)col[coln(WEIGHT)]->bind_ptr, NULL) * 1000)}, "", false }; - add_cloned_weightsystem(&smtkdive->weightsystems, ws); - smtkdive->suit = strdup(get(suit_list, atoi((char *)col[coln(SUITIDX)]->bind_ptr) - 1).c_str()); + weightsystem_t ws = { { .grams = int_cast(strtod((char *)col[coln(WEIGHT)]->bind_ptr, NULL) * 1000)}, std::string(), false }; + smtkdive->weightsystems.push_back(std::move(ws)); + smtkdive->suit = get(suit_list, atoi((char *)col[coln(SUITIDX)]->bind_ptr) - 1); smtk_build_location(mdb_clon, (char *)col[coln(SITEIDX)]->bind_ptr, &smtkdive->dive_site, log); - smtkdive->buddy = strdup(smtk_locate_buddy(mdb_clon, (char *)col[0]->bind_ptr, buddy_list).c_str()); - smtk_parse_relations(mdb_clon, smtkdive, (char *)col[0]->bind_ptr, "Type", "TypeRelation", type_list, true); - smtk_parse_relations(mdb_clon, smtkdive, (char *)col[0]->bind_ptr, "Activity", "ActivityRelation", activity_list, false); - smtk_parse_relations(mdb_clon, smtkdive, (char *)col[0]->bind_ptr, "Gear", "GearRelation", gear_list, false); - smtk_parse_relations(mdb_clon, smtkdive, (char *)col[0]->bind_ptr, "Fish", "FishRelation", fish_list, false); - smtk_parse_other(smtkdive, weather_list, "Weather", (char *)col[coln(WEATHERIDX)]->bind_ptr, false); - smtk_parse_other(smtkdive, underwater_list, "Underwater", (char *)col[coln(UNDERWATERIDX)]->bind_ptr, false); - smtk_parse_other(smtkdive, surface_list, "Surface", (char *)col[coln(SURFACEIDX)]->bind_ptr, false); - smtk_parse_bookmarks(mdb_clon, smtkdive, (char *)col[0]->bind_ptr); - concat(&smtkdive->notes, "\n", std::string((char *)col[coln(REMARKS)]->bind_ptr)); + smtkdive->buddy = smtk_locate_buddy(mdb_clon, (char *)col[0]->bind_ptr, buddy_list); + smtk_parse_relations(mdb_clon, smtkdive.get(), (char *)col[0]->bind_ptr, "Type", "TypeRelation", type_list, true); + smtk_parse_relations(mdb_clon, smtkdive.get(), (char *)col[0]->bind_ptr, "Activity", "ActivityRelation", activity_list, false); + smtk_parse_relations(mdb_clon, smtkdive.get(), (char *)col[0]->bind_ptr, "Gear", "GearRelation", gear_list, false); + smtk_parse_relations(mdb_clon, smtkdive.get(), (char *)col[0]->bind_ptr, "Fish", "FishRelation", fish_list, false); + smtk_parse_other(smtkdive.get(), weather_list, "Weather", (char *)col[coln(WEATHERIDX)]->bind_ptr, false); + smtk_parse_other(smtkdive.get(), underwater_list, "Underwater", (char *)col[coln(UNDERWATERIDX)]->bind_ptr, false); + smtk_parse_other(smtkdive.get(), surface_list, "Surface", (char *)col[coln(SURFACEIDX)]->bind_ptr, false); + smtk_parse_bookmarks(mdb_clon, smtkdive.get(), (char *)col[0]->bind_ptr); + concat(smtkdive->notes, "\n", std::string((char *)col[coln(REMARKS)]->bind_ptr)); - record_dive_to_table(smtkdive, log->dives); - device_data_free(devdata); + log->dives.record_dive(std::move(smtkdive)); } mdb_free_catalog(mdb_clon); mdb->catalog = NULL; mdb_close(mdb_clon); mdb_close(mdb); - sort_dive_table(log->dives); + log->dives.sort(); } diff --git a/smtk-import/smrtk2ssrfc_window.cpp b/smtk-import/smrtk2ssrfc_window.cpp index 78b9ef1c3..7915c0e7a 100644 --- a/smtk-import/smrtk2ssrfc_window.cpp +++ b/smtk-import/smrtk2ssrfc_window.cpp @@ -15,11 +15,9 @@ QStringList inputFiles; QString outputFile; QString error_buf; -extern "C" void getErrorFromC(char *buf) +void getErrorFromC(std::string buf) { - QString error(buf); - free(buf); - error_buf = error; + error_buf = QString::fromStdString(std::move(buf)); } Smrtk2ssrfcWindow::Smrtk2ssrfcWindow(QWidget *parent) : diff --git a/smtk-import/smrtk2ssrfc_window.h b/smtk-import/smrtk2ssrfc_window.h index d17f8d098..5998f935a 100644 --- a/smtk-import/smrtk2ssrfc_window.h +++ b/smtk-import/smrtk2ssrfc_window.h @@ -7,7 +7,7 @@ #include struct divelog; -extern "C" void smartrak_import(const char *file, struct divelog *log); +void smartrak_import(const char *file, struct divelog *log); namespace Ui { class Smrtk2ssrfcWindow; diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 95e94dbf5..3d9efc52d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -12,6 +12,10 @@ confinement: strict base: core22 adopt-info: subsurface +layout: + /etc/gitconfig: + bind-file: $SNAP_DATA/gitconfig + apps: subsurface: environment: @@ -146,6 +150,7 @@ parts: - libqt5charts5-dev - libqt5svg5-dev - libqt5webkit5-dev + - libraw-dev - libsqlite3-dev - libssh2-1-dev - libssl-dev @@ -198,6 +203,7 @@ parts: - libsqlite3-0 - libssh2-1 - libssl3 + - libraw20 - libusb-1.0-0 - libxml2 - libxslt1.1 diff --git a/stats/statsaxis.cpp b/stats/statsaxis.cpp index 484ecfaa9..eb1affd8b 100644 --- a/stats/statsaxis.cpp +++ b/stats/statsaxis.cpp @@ -572,7 +572,7 @@ static void inc(std::array &ymd) // the separator character. Returns a (day_first, separator) pair. static std::pair day_format() { - const char *fmt = prefs.date_format; + const char *fmt = prefs.date_format.c_str(); const char *d, *m, *sep; for (d = fmt; *d && *d != 'd' && *d != 'D'; ++d) ; diff --git a/stats/statsvariables.cpp b/stats/statsvariables.cpp index 3f8bacd6f..4f223b134 100644 --- a/stats/statsvariables.cpp +++ b/stats/statsvariables.cpp @@ -47,7 +47,7 @@ struct DiveSiteWrapper { const dive_site *ds; QString name; DiveSiteWrapper(const dive_site *ds) : ds(ds), - name(ds ? ds->name : "") + name(ds ? QString::fromStdString(ds->name) : QString()) { } bool operator<(const DiveSiteWrapper &d2) const { @@ -74,8 +74,8 @@ struct TripWrapper { QString name; timestamp_t date; TripWrapper(const dive_trip *t) : t(t), - name(formatTripTitle(t)), // safe to pass null - date(trip_date(t)) // safe to pass null + name(t ? formatTripTitle(*t) : QString()), + date(t ? t->date() : 0) { } bool operator<(const TripWrapper &t2) const { @@ -216,7 +216,7 @@ bool StatsQuartiles::isValid() const // Define an ordering for gas types // invalid < air < ean (including oxygen) < trimix // The latter two are sorted by (helium, oxygen) -// This is in analogy to the global get_dive_gas() function. +// This is in analogy to the dive::get_maximal_gas() function. static bool operator<(const gas_bin_t &t1, const gas_bin_t &t2) { if (t1.type != t2.type) @@ -1297,8 +1297,8 @@ struct WeightBinner : public IntRangeBinner { return get_weight_unit(metric); } int to_bin_value(const dive *d) const { - return metric ? total_weight(d) / 1000 / bin_size - : lrint(grams_to_lbs(total_weight(d))) / bin_size; + return metric ? d->total_weight().grams / 1000 / bin_size + : lrint(grams_to_lbs(d->total_weight().grams)) / bin_size; } }; @@ -1328,8 +1328,8 @@ struct WeightVariable : public StatsVariableTemplatetotal_weight().grams / 1000.0 + : grams_to_lbs(d->total_weight().grams); } std::vector supportedOperations() const override { return { StatsOperation::Median, StatsOperation::Mean, StatsOperation::Sum, StatsOperation::Min, StatsOperation::Max }; @@ -1381,7 +1381,7 @@ struct DiveNrVariable : public StatsVariableTemplate binners() const override { - if (divelog.dives->nr > 1000) + if (divelog.dives.size() > 1000) return { &dive_nr_binner_20, &dive_nr_binner_50, &dive_nr_binner_100, &dive_nr_binner_200 }; else return { &dive_nr_binner_5, &dive_nr_binner_10, &dive_nr_binner_20, &dive_nr_binner_50 }; @@ -1402,7 +1402,7 @@ struct DiveModeBinner : public SimpleBinner { return QString(divemode_text_ui[derived_bin(bin).value]); } int to_bin_value(const dive *d) const { - int res = (int)d->dc.divemode; + int res = (int)d->dcs[0].divemode; return res >= 0 && res < NUM_DIVEMODE ? res : OC; } }; @@ -1413,7 +1413,7 @@ struct DiveModeVariable : public StatsVariableTemplatedc.divemode; + int mode = (int)d->dcs[0].divemode; return mode >= 0 && mode < NUM_DIVEMODE ? QString(divemode_text_ui[mode]) : QString(); } @@ -1427,9 +1427,9 @@ struct DiveModeVariable : public StatsVariableTemplate { std::vector to_bin_values(const dive *d) const { std::vector dive_people; - for (const QString &s: QString(d->buddy).split(",", SKIP_EMPTY)) + for (const QString &s: QString::fromStdString(d->buddy).split(",", SKIP_EMPTY)) dive_people.push_back(s.trimmed()); - for (const QString &s: QString(d->diveguide).split(",", SKIP_EMPTY)) + for (const QString &s: QString::fromStdString(d->diveguide).split(",", SKIP_EMPTY)) dive_people.push_back(s.trimmed()); return dive_people; } @@ -1441,8 +1441,8 @@ struct PeopleVariable : public StatsVariableTemplatebuddy).trimmed(); - QString diveguide = QString(d->diveguide).trimmed(); + QString buddy = QString::fromStdString(d->buddy).trimmed(); + QString diveguide = QString::fromStdString(d->diveguide).trimmed(); if (!buddy.isEmpty() && !diveguide.isEmpty()) buddy += ", "; return buddy + diveguide; @@ -1455,7 +1455,7 @@ struct PeopleVariable : public StatsVariableTemplate { std::vector to_bin_values(const dive *d) const { std::vector buddies; - for (const QString &s: QString(d->buddy).split(",", SKIP_EMPTY)) + for (const QString &s: QString::fromStdString(d->buddy).split(",", SKIP_EMPTY)) buddies.push_back(s.trimmed()); return buddies; } @@ -1467,7 +1467,7 @@ struct BuddyVariable : public StatsVariableTemplatebuddy).trimmed(); + return QString::fromStdString(d->buddy).trimmed(); } std::vector binners() const override { return { &buddy_binner }; @@ -1477,7 +1477,7 @@ struct BuddyVariable : public StatsVariableTemplate { std::vector to_bin_values(const dive *d) const { std::vector dive_guides; - for (const QString &s: QString(d->diveguide).split(",", SKIP_EMPTY)) + for (const QString &s: QString::fromStdString(d->diveguide).split(",", SKIP_EMPTY)) dive_guides.push_back(s.trimmed()); return dive_guides; } @@ -1489,7 +1489,7 @@ struct DiveGuideVariable : public StatsVariableTemplatediveguide).trimmed(); + return QString::fromStdString(d->diveguide).trimmed(); } std::vector binners() const override { return { &dive_guide_binner }; @@ -1501,8 +1501,8 @@ struct DiveGuideVariable : public StatsVariableTemplate { std::vector to_bin_values(const dive *d) const { std::vector tags; - for (const tag_entry *tag = d->tag_list; tag; tag = tag->next) - tags.push_back(QString::fromStdString(tag->tag->name).trimmed()); + for (const divetag *tag: d->tags) + tags.push_back(QString::fromStdString(tag->name).trimmed()); return tags; } }; @@ -1513,7 +1513,7 @@ struct TagVariable : public StatsVariableTemplate return StatsTranslations::tr("Tags"); } QString diveCategories(const dive *d) const override { - return QString::fromStdString(taglist_get_tagstring(d->tag_list)); + return QString::fromStdString(taglist_get_tagstring(d->tags)); } std::vector binners() const override { return { &tag_binner }; @@ -1553,9 +1553,9 @@ struct GasTypeBinner : public MultiBinner { } std::vector to_bin_values(const dive *d) const { std::vector res; - res.reserve(d->cylinders.nr); - for (int i = 0; i < d->cylinders.nr; ++i) { - struct gasmix mix = d->cylinders.cylinders[i].gasmix; + res.reserve(d->cylinders.size()); + for (auto &cyl: d->cylinders) { + struct gasmix mix = cyl.gasmix; if (gasmix_is_invalid(mix)) continue; // Add dive to each bin only once. @@ -1591,9 +1591,9 @@ struct GasTypeGeneralBinner : public MultiBinner { } std::vector to_bin_values(const dive *d) const { std::vector res; - res.reserve(d->cylinders.nr); - for (int i = 0; i < d->cylinders.nr; ++i) { - struct gasmix mix = d->cylinders.cylinders[i].gasmix; + res.reserve(d->cylinders.size()); + for (auto &cyl: d->cylinders) { + struct gasmix mix = cyl.gasmix; if (gasmix_is_invalid(mix)) continue; res.push_back(gasmix_to_type(mix)); @@ -1619,9 +1619,9 @@ struct GasTypeVariable : public StatsVariableTemplate mixes; // List multiple cylinders only once - mixes.reserve(d->cylinders.nr); - for (int i = 0; i < d->cylinders.nr; ++i) { - struct gasmix mix = d->cylinders.cylinders[i].gasmix; + mixes.reserve(d->cylinders.size()); + for (auto &cyl: d->cylinders) { + struct gasmix mix = cyl.gasmix; if (gasmix_is_invalid(mix)) continue; if (std::find_if(mixes.begin(), mixes.end(), @@ -1648,7 +1648,7 @@ struct GasTypeVariable : public StatsVariableTemplatecylinders.nr <= 0) + if (d->cylinders.empty()) return invalid_value(); // If sorting be He, the second sort criterion is O2 descending, because // we are interested in the "bottom gas": highest He and lowest O2. @@ -1657,7 +1657,7 @@ static int get_gas_content(const struct dive *d, bool he, bool max_he) std::make_tuple(get_he(c2.gasmix), -get_o2(c2.gasmix)); } : [] (const cylinder_t &c1, const cylinder_t &c2) { return get_o2(c1.gasmix) < get_o2(c2.gasmix); }; - auto it = std::max_element(d->cylinders.cylinders, d->cylinders.cylinders + d->cylinders.nr, comp); + auto it = std::max_element(d->cylinders.begin(), d->cylinders.end(), comp); return he ? get_he(it->gasmix) : get_o2(it->gasmix); } @@ -1746,7 +1746,7 @@ struct GasContentHeVariable : GasContentVariable { struct SuitBinner : public StringBinner { std::vector to_bin_values(const dive *d) const { - return { QString(d->suit) }; + return { QString::fromStdString(d->suit) }; } }; @@ -1756,7 +1756,7 @@ struct SuitVariable : public StatsVariableTemplatesuit); + return QString::fromStdString(d->suit); } std::vector binners() const override { return { &suit_binner }; @@ -1768,9 +1768,9 @@ struct SuitVariable : public StatsVariableTemplate weightsystems(const dive *d) { std::vector res; - res.reserve(d->weightsystems.nr); - for (int i = 0; i < d->weightsystems.nr; ++i) - add_to_vector_unique(res, QString(d->weightsystems.weightsystems[i].description).trimmed()); + res.reserve(d->weightsystems.size()); + for (auto &ws: d->weightsystems) + add_to_vector_unique(res, QString::fromStdString(ws.description).trimmed()); return res; } @@ -1798,9 +1798,9 @@ struct WeightsystemVariable : public StatsVariableTemplate cylinder_types(const dive *d) { std::vector res; - res.reserve(d->cylinders.nr); - for (int i = 0; i < d->cylinders.nr; ++i) - add_to_vector_unique(res, QString(d->cylinders.cylinders[i].type.description).trimmed()); + res.reserve(d->cylinders.size()); + for (auto &cyl: d->cylinders) + add_to_vector_unique(res, QString::fromStdString(cyl.type.description).trimmed()); return res; } @@ -1868,7 +1868,7 @@ struct TripVariable : public StatsVariableTemplatedivetrip); + return d->divetrip ? formatTripTitle(*d->divetrip) : QString(); } std::vector binners() const override { return { &trip_binner }; diff --git a/subsurface-desktop-main.cpp b/subsurface-desktop-main.cpp index d17ffb90c..97e74dddd 100644 --- a/subsurface-desktop-main.cpp +++ b/subsurface-desktop-main.cpp @@ -45,8 +45,7 @@ int main(int argc, char **argv) std::vector importedFiles; QStringList arguments = QCoreApplication::arguments(); - const char *default_directory = system_default_directory(); - subsurface_mkdir(default_directory); + subsurface_mkdir(system_default_directory().c_str()); for (int i = 1; i < arguments.length(); i++) { std::string a = arguments[i].toStdString(); @@ -75,18 +74,18 @@ int main(int argc, char **argv) git_libgit2_init(); #endif setup_system_prefs(); - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; CheckCloudConnection ccc; ccc.pickServer(); fill_computer_list(); - reset_tank_info_table(&tank_info_table); + reset_tank_info_table(tank_info_table); parse_xml_init(); taglist_init_global(); init_ui(); if (no_filenames) { if (prefs.default_file_behavior == LOCAL_DEFAULT_FILE) { - if (!empty_string(prefs.default_filename)) - files.emplace_back(prefs.default_filename ? prefs.default_filename : ""); + if (!prefs.default_filename.empty()) + files.push_back(prefs.default_filename); } else if (prefs.default_file_behavior == CLOUD_DEFAULT_FILE) { auto cloudURL = getCloudURL(); if (cloudURL) @@ -106,15 +105,12 @@ int main(int argc, char **argv) if (!quit) run_ui(); exit_ui(); - clear_divelog(&divelog); parse_xml_exit(); subsurface_console_exit(); // Sync struct preferences to disk qPref::sync(); - free_prefs(); - clear_tank_info_table(&tank_info_table); return 0; } diff --git a/subsurface-downloader-main.cpp b/subsurface-downloader-main.cpp index d655c3c59..1c9d569fe 100644 --- a/subsurface-downloader-main.cpp +++ b/subsurface-downloader-main.cpp @@ -4,11 +4,11 @@ #include "core/errorhelper.h" #include "core/parse.h" #include "core/qthelper.h" -#include "core/subsurfacestartup.h" #include "core/settings/qPref.h" #include "core/tag.h" #include "core/dive.h" #include "core/divelog.h" +#include "core/subsurfacestartup.h" #include "core/subsurface-string.h" #include "core/file.h" #include "core/trip.h" @@ -21,7 +21,7 @@ #include static void messageHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &msg); -extern void cliDownloader(const char *vendor, const char *product, const char *device); +extern void cliDownloader(const std::string &vendor, const std::string &product, const std::string &device); int main(int argc, char **argv) { @@ -45,8 +45,7 @@ int main(int argc, char **argv) // set a default logfile name for libdivecomputer so we always get a logfile logfile_name = "subsurface-downloader.log"; - const char *default_directory = system_default_directory(); - subsurface_mkdir(default_directory); + subsurface_mkdir(system_default_directory().c_str()); if (subsurface_user_is_root() && !force_root) { printf("You are running Subsurface as root. This is not recommended.\n"); @@ -55,7 +54,7 @@ int main(int argc, char **argv) } git_libgit2_init(); setup_system_prefs(); - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; // now handle the arguments fill_computer_list(); @@ -79,8 +78,8 @@ int main(int argc, char **argv) if (no_filenames) { if (prefs.default_file_behavior == LOCAL_DEFAULT_FILE) { - if (!empty_string(prefs.default_filename)) - files.emplace_back(prefs.default_filename ? prefs.default_filename : ""); + if (!prefs.default_filename.empty()) + files.emplace_back(prefs.default_filename.c_str()); } else if (prefs.default_file_behavior == CLOUD_DEFAULT_FILE) { auto cloudURL = getCloudURL(); if (cloudURL) @@ -96,9 +95,10 @@ int main(int argc, char **argv) } print_files(); if (!quit) { - if (!empty_string(prefs.dive_computer.vendor) && !empty_string(prefs.dive_computer.product) && !empty_string(prefs.dive_computer.device)) { + if (!prefs.dive_computer.vendor.empty() && !prefs.dive_computer.product.empty() && !prefs.dive_computer.device.empty()) { // download from that dive computer - printf("Downloading dives from %s %s (via %s)\n", prefs.dive_computer.vendor, prefs.dive_computer.product, prefs.dive_computer.device); + printf("Downloading dives from %s %s (via %s)\n", prefs.dive_computer.vendor.c_str(), + prefs.dive_computer.product.c_str(), prefs.dive_computer.device.c_str()); cliDownloader(prefs.dive_computer.vendor, prefs.dive_computer.product, prefs.dive_computer.device); } } @@ -109,13 +109,11 @@ int main(int argc, char **argv) printf("No log files given, not saving dive data.\n"); printf("Give a log file name as argument, or configure a cloud URL.\n"); } - clear_divelog(&divelog); parse_xml_exit(); // Sync struct preferences to disk qPref::sync(); - free_prefs(); return 0; } diff --git a/subsurface-helper.cpp b/subsurface-helper.cpp index b881cab37..df40cb433 100644 --- a/subsurface-helper.cpp +++ b/subsurface-helper.cpp @@ -13,7 +13,6 @@ #include "core/globals.h" #include "core/qt-gui.h" #include "core/settings/qPref.h" -#include "core/ssrf.h" #ifdef SUBSURFACE_MOBILE #include diff --git a/subsurface-mobile-main.cpp b/subsurface-mobile-main.cpp index 813d448cc..06e52d242 100644 --- a/subsurface-mobile-main.cpp +++ b/subsurface-mobile-main.cpp @@ -29,7 +29,6 @@ #include // Implementation of STP logging -#include "core/ssrf.h" int main(int argc, char **argv) { @@ -57,11 +56,11 @@ int main(int argc, char **argv) default_prefs.units = SI_units; else default_prefs.units = IMPERIAL_units; - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; CheckCloudConnection ccc; ccc.pickServer(); fill_computer_list(); - reset_tank_info_table(&tank_info_table); + reset_tank_info_table(tank_info_table); parse_xml_init(); taglist_init_global(); @@ -93,15 +92,12 @@ int main(int argc, char **argv) if (!quit) run_mobile_ui(initial_font_size); exit_ui(); - clear_divelog(&divelog); parse_xml_exit(); subsurface_console_exit(); // Sync struct preferences to disk qPref::sync(); - free_prefs(); - clear_tank_info_table(&tank_info_table); return 0; } diff --git a/tests/testAirPressure.cpp b/tests/testAirPressure.cpp index 8cca009c7..b84d3dd02 100644 --- a/tests/testAirPressure.cpp +++ b/tests/testAirPressure.cpp @@ -14,54 +14,40 @@ void TestAirPressure::initTestCase() { /* we need to manually tell that the resource exists, because we are using it as library. */ Q_INIT_RESOURCE(subsurface); - prefs.cloud_base_url = strdup(default_prefs.cloud_base_url); + prefs.cloud_base_url = default_prefs.cloud_base_url; } void TestAirPressure::get_dives() { - struct dive *dive; verbose = 1; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/TestAtmPress.xml", &divelog), 0); - dive = get_dive(0); - dive->selected = true; - QVERIFY(dive != NULL); + QVERIFY(divelog.dives.size() >= 1); } void TestAirPressure::testReadAirPressure() { - struct dive *dive; - dive = get_dive(0); - QVERIFY(dive != NULL); - dive->selected = true; - QCOMPARE(1012, dive->surface_pressure.mbar); - dive = get_dive(1); - QVERIFY(dive != NULL); - dive->selected = true; - QCOMPARE(991, dive->surface_pressure.mbar); + QVERIFY(divelog.dives.size() >= 2); + QCOMPARE(1012, divelog.dives[0]->surface_pressure.mbar); + QCOMPARE(991, divelog.dives[1]->surface_pressure.mbar); } void TestAirPressure::testConvertAltitudetoAirPressure() { - QCOMPARE(891,altitude_to_pressure(1000000)); // 1000 m altitude in mm - QCOMPARE(1013,altitude_to_pressure(0)); // sea level + QCOMPARE(891, altitude_to_pressure(1000000).mbar); // 1000 m altitude in mm + QCOMPARE(1013, altitude_to_pressure(0).mbar); // sea level } void TestAirPressure::testWriteReadBackAirPressure() { - struct dive *dive; int32_t ap = 1111; - dive = get_dive(0); - QVERIFY(dive != NULL); - dive->selected = true; - dive->surface_pressure.mbar = ap; + QVERIFY(divelog.dives.size() >= 1); + divelog.dives[0]->surface_pressure.mbar = ap; QCOMPARE(save_dives("./testout.ssrf"), 0); clear_dive_file_data(); QCOMPARE(parse_file("./testout.ssrf", &divelog), 0); - dive = get_dive(0); - QVERIFY(dive != NULL); - dive->selected = true; - QCOMPARE(ap, dive->surface_pressure.mbar); + QVERIFY(divelog.dives.size() >= 1); + QCOMPARE(ap, divelog.dives[0]->surface_pressure.mbar); } QTEST_GUILESS_MAIN(TestAirPressure) diff --git a/tests/testdivesiteduplication.cpp b/tests/testdivesiteduplication.cpp index e905e3ee4..12618dc82 100644 --- a/tests/testdivesiteduplication.cpp +++ b/tests/testdivesiteduplication.cpp @@ -7,9 +7,9 @@ void TestDiveSiteDuplication::testReadV2() { - prefs.cloud_base_url = strdup(default_prefs.cloud_base_url); + prefs.cloud_base_url = default_prefs.cloud_base_url; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/TwoTimesTwo.ssrf", &divelog), 0); - QCOMPARE(divelog.sites->nr, 2); + QCOMPARE(divelog.sites.size(), 2); } QTEST_GUILESS_MAIN(TestDiveSiteDuplication) diff --git a/tests/testformatDiveGasString.cpp b/tests/testformatDiveGasString.cpp index a9518ee1a..87d4e905c 100644 --- a/tests/testformatDiveGasString.cpp +++ b/tests/testformatDiveGasString.cpp @@ -9,228 +9,239 @@ void TestformatDiveGasString::init() void TestformatDiveGasString::test_empty() { - struct dive dive = {0}; + struct dive dive; QCOMPARE(formatDiveGasString(&dive), "air"); } void TestformatDiveGasString::test_air() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "air"); } -void TestformatDiveGasString::test_nitrox() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_nitrox() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 320; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 32_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "32%"); } -void TestformatDiveGasString::test_nitrox_not_use() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_nitrox_not_use() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 320; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 32_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 1000; + cylinder->gasmix.o2 = 100_percent; cylinder->cylinder_use = NOT_USED; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "32%"); } -void TestformatDiveGasString::test_nitrox_deco() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_nitrox_deco() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 320; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 32_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 1000; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 100_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "32…100%"); } -void TestformatDiveGasString::test_reverse_nitrox_deco() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_reverse_nitrox_deco() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 1000; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 100_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 270; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 27_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "27…100%"); } -void TestformatDiveGasString::test_trimix() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_trimix() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 210; - cylinder->gasmix.he.permille = 350; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 21_percent; + cylinder->gasmix.he = 35_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "21/35"); } -void TestformatDiveGasString::test_trimix_deco() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_trimix_deco() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 210; - cylinder->gasmix.he.permille = 350; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 21_percent; + cylinder->gasmix.he = 35_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 500; - cylinder->gasmix.he.permille = 200; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 50_percent; + cylinder->gasmix.he = 20_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 2); + cylinder = dive.get_or_create_cylinder(2); - cylinder->gasmix.o2.permille = 1000; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 100_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "21/35…100%"); } -void TestformatDiveGasString::test_reverse_trimix_deco() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_reverse_trimix_deco() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 1000; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 100_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 500; - cylinder->gasmix.he.permille = 200; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 50_percent; + cylinder->gasmix.he = 20_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 2); + cylinder = dive.get_or_create_cylinder(2); - cylinder->gasmix.o2.permille = 210; - cylinder->gasmix.he.permille = 350; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 21_percent; + cylinder->gasmix.he = 35_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "21/35…100%"); } -void TestformatDiveGasString::test_trimix_and_nitrox_same_o2() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_trimix_and_nitrox_same_o2() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 250; - cylinder->gasmix.he.permille = 0; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 25_percent; + cylinder->gasmix.he = 0_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 250; - cylinder->gasmix.he.permille = 250; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 25_percent; + cylinder->gasmix.he = 25_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "25/25"); } -void TestformatDiveGasString::test_trimix_and_nitrox_lower_o2() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_trimix_and_nitrox_lower_o2() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 220; - cylinder->gasmix.he.permille = 0; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 22_percent; + cylinder->gasmix.he = 0_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 250; - cylinder->gasmix.he.permille = 250; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 25_percent; + cylinder->gasmix.he = 25_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "25/25"); } -void TestformatDiveGasString::test_ccr() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_ccr() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 1000; + cylinder->gasmix.o2 = 100_percent; cylinder->cylinder_use = OXYGEN; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 210; - cylinder->gasmix.he.permille = 350; + cylinder->gasmix.o2 = 21_percent; + cylinder->gasmix.he = 35_percent; cylinder->cylinder_use = DILUENT; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "21/35"); } -void TestformatDiveGasString::test_ccr_bailout() { - struct dive dive = {0}; - cylinder_t *cylinder = get_or_create_cylinder(&dive, 0); +void TestformatDiveGasString::test_ccr_bailout() +{ + struct dive dive; + cylinder_t *cylinder = dive.get_or_create_cylinder(0); - cylinder->gasmix.o2.permille = 1000; + cylinder->gasmix.o2 = 100_percent; cylinder->cylinder_use = OXYGEN; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 1); + cylinder = dive.get_or_create_cylinder(1); - cylinder->gasmix.o2.permille = 220; - cylinder->gasmix.he.permille = 200; + cylinder->gasmix.o2 = 22_percent; + cylinder->gasmix.he = 20_percent; cylinder->cylinder_use = DILUENT; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->start = 230_bar; + cylinder->end = 100_bar; - cylinder = get_or_create_cylinder(&dive, 2); + cylinder = dive.get_or_create_cylinder(2); - cylinder->gasmix.o2.permille = 210; - cylinder->gasmix.he.permille = 0; - cylinder->start.mbar = 230000; - cylinder->end.mbar = 100000; + cylinder->gasmix.o2 = 21_percent; + cylinder->gasmix.he = 0_percent; + cylinder->start = 230_bar; + cylinder->end = 100_bar; QCOMPARE(formatDiveGasString(&dive), "22/20"); } diff --git a/tests/testgitstorage.cpp b/tests/testgitstorage.cpp index 7b1aafdfb..4fdcbbae9 100644 --- a/tests/testgitstorage.cpp +++ b/tests/testgitstorage.cpp @@ -77,7 +77,7 @@ void TestGitStorage::initTestCase() QTextCodec::setCodecForLocale(QTextCodec::codecForMib(106)); // first, setup the preferences an proxy information - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; QCoreApplication::setOrganizationName("Subsurface"); QCoreApplication::setOrganizationDomain("subsurface.hohndel.org"); QCoreApplication::setApplicationName("Subsurface"); @@ -108,8 +108,8 @@ void TestGitStorage::initTestCase() if (gitUrl.empty() || gitUrl.back() != '/') gitUrl += "/"; gitUrl += "git"; - prefs.cloud_storage_email_encoded = strdup(email.c_str()); - prefs.cloud_storage_password = strdup(password.c_str()); + prefs.cloud_storage_email_encoded = email; + prefs.cloud_storage_password = password.c_str(); gitUrl += "/" + email; // all user storage for historical reasons always uses the user's email both as // repo name and as branch. To allow us to keep testing and not step on parallel @@ -138,11 +138,11 @@ void TestGitStorage::initTestCase() // make sure we deal with any proxy settings that are needed QNetworkProxy proxy; proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); - proxy.setHostName(prefs.proxy_host); + proxy.setHostName(QString::fromStdString(prefs.proxy_host)); proxy.setPort(prefs.proxy_port); if (prefs.proxy_auth) { - proxy.setUser(prefs.proxy_user); - proxy.setPassword(prefs.proxy_pass); + proxy.setUser(QString::fromStdString(prefs.proxy_user)); + proxy.setPassword(QString::fromStdString(prefs.proxy_pass)); } QNetworkProxy::setApplicationProxy(proxy); @@ -243,7 +243,7 @@ void TestGitStorage::testGitStorageCloudOfflineSync() QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test10.xml", &divelog), 0); // calling process_loaded_dives() sorts the table, but calling add_imported_dives() // causes it to try to update the window title... let's not do that - process_loaded_dives(); + divelog.process_loaded_dives(); // now save only to the local cache but not to the remote server git_local_only = true; QCOMPARE(save_dives(cloudTestRepo.c_str()), 0); @@ -297,7 +297,7 @@ void TestGitStorage::testGitStorageCloudMerge() git_local_only = false; QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test11.xml", &divelog), 0); - process_loaded_dives(); + divelog.process_loaded_dives(); QCOMPARE(save_dives(cloudTestRepo.c_str()), 0); clear_dive_file_data(); @@ -309,7 +309,7 @@ void TestGitStorage::testGitStorageCloudMerge() git_local_only = true; QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test12.xml", &divelog), 0); - process_loaded_dives(); + divelog.process_loaded_dives(); QCOMPARE(save_dives(cloudTestRepo.c_str()), 0); clear_dive_file_data(); @@ -323,12 +323,12 @@ void TestGitStorage::testGitStorageCloudMerge() QCOMPARE(parse_file("./SampleDivesV3plus10local.ssrf", &divelog), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test11.xml", &divelog), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test12.xml", &divelog), 0); - process_loaded_dives(); + divelog.process_loaded_dives(); QCOMPARE(save_dives("./SampleDivesV3plus10-11-12.ssrf"), 0); // then load from the cloud clear_dive_file_data(); QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); - process_loaded_dives(); + divelog.process_loaded_dives(); QCOMPARE(save_dives("./SampleDivesV3plus10-11-12-merged.ssrf"), 0); // finally compare what we have QFile org("./SampleDivesV3plus10-11-12-merged.ssrf"); @@ -345,7 +345,7 @@ void TestGitStorage::testGitStorageCloudMerge() // (6) move ourselves back to the first client and compare data there moveDir(localCacheDir + "client1", localCacheDir); QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); - process_loaded_dives(); + divelog.process_loaded_dives(); QCOMPARE(save_dives("./SampleDivesV3plus10-11-12-merged-client1.ssrf"), 0); QFile client1("./SampleDivesV3plus10-11-12-merged-client1.ssrf"); client1.open(QFile::ReadOnly); @@ -361,9 +361,9 @@ void TestGitStorage::testGitStorageCloudMerge2() // merge // (1) open repo, delete second dive, save offline QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); - process_loaded_dives(); - struct dive *dive = get_dive(1); - delete_single_dive(&divelog, 1); + divelog.process_loaded_dives(); + QVERIFY(divelog.dives.size() >= 2); + divelog.delete_multiple_dives(std::vector{ divelog.dives[1].get() }); QCOMPARE(save_dives("./SampleDivesMinus1.ssrf"), 0); git_local_only = true; QCOMPARE(save_dives(localCacheRepo.c_str()), 0); @@ -375,11 +375,9 @@ void TestGitStorage::testGitStorageCloudMerge2() // (3) now we open the cloud storage repo and modify that second dive QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); - process_loaded_dives(); - dive = get_dive(1); - QVERIFY(dive != NULL); - free(dive->notes); - dive->notes = strdup("These notes have been modified by TestGitStorage"); + QVERIFY(divelog.dives.size() >= 2); + divelog.process_loaded_dives(); + divelog.dives[1]->notes = "These notes have been modified by TestGitStorage"; QCOMPARE(save_dives(cloudTestRepo.c_str()), 0); clear_dive_file_data(); @@ -410,32 +408,21 @@ void TestGitStorage::testGitStorageCloudMerge3() // (1) open repo, edit notes of first three dives QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); - process_loaded_dives(); - struct dive *dive; - QVERIFY((dive = get_dive(0)) != 0); - free(dive->notes); - dive->notes = strdup("Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"); - QVERIFY((dive = get_dive(1)) != 0); - free(dive->notes); - dive->notes = strdup("Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"); - QVERIFY((dive = get_dive(2)) != 0); - free(dive->notes); - dive->notes = strdup("Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"); + divelog.process_loaded_dives(); + QVERIFY(divelog.dives.size() >= 3); + divelog.dives[0]->notes = "Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"; + divelog.dives[1]->notes = "Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"; + divelog.dives[2]->notes = "Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"; QCOMPARE(save_dives(cloudTestRepo.c_str()), 0); clear_dive_file_data(); // (2) make different edits offline QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); - process_loaded_dives(); - QVERIFY((dive = get_dive(0)) != 0); - free(dive->notes); - dive->notes = strdup("Create multi line dive notes\nDifferent line 2 and removed 3-5\n\nThat should be enough"); - QVERIFY((dive = get_dive(1)) != 0); - free(dive->notes); - dive->notes = strdup("Line 2\nLine 3\nLine 4\nLine 5"); // keep the middle, remove first and last"); - QVERIFY((dive = get_dive(2)) != 0); - free(dive->notes); - dive->notes = strdup("single line dive notes"); + divelog.process_loaded_dives(); + QVERIFY(divelog.dives.size() >= 3); + divelog.dives[0]->notes = "Create multi line dive notes\nDifferent line 2 and removed 3-5\n\nThat should be enough"; + divelog.dives[1]->notes = "Line 2\nLine 3\nLine 4\nLine 5"; // keep the middle, remove first and last"); + divelog.dives[2]->notes = "single line dive notes"; git_local_only = true; QCOMPARE(save_dives(cloudTestRepo.c_str()), 0); git_local_only = false; @@ -445,16 +432,11 @@ void TestGitStorage::testGitStorageCloudMerge3() // those first dive notes differently while online moveDir(localCacheDir, localCacheDir + "save"); QCOMPARE(parse_file(cloudTestRepo.c_str(), &divelog), 0); - process_loaded_dives(); - QVERIFY((dive = get_dive(0)) != 0); - free(dive->notes); - dive->notes = strdup("Completely different dive notes\nBut also multi line"); - QVERIFY((dive = get_dive(1)) != 0); - free(dive->notes); - dive->notes = strdup("single line dive notes"); - QVERIFY((dive = get_dive(2)) != 0); - free(dive->notes); - dive->notes = strdup("Line 2\nLine 3\nLine 4\nLine 5"); // keep the middle, remove first and last"); + divelog.process_loaded_dives(); + QVERIFY(divelog.dives.size() >= 3); + divelog.dives[0]->notes = "Completely different dive notes\nBut also multi line"; + divelog.dives[1]->notes = "single line dive notes"; + divelog.dives[2]->notes = "Line 2\nLine 3\nLine 4\nLine 5"; // keep the middle, remove first and last"); QCOMPARE(save_dives(cloudTestRepo.c_str()), 0); clear_dive_file_data(); diff --git a/tests/testhelper.cpp b/tests/testhelper.cpp index dc4e4d1bb..6882ba363 100644 --- a/tests/testhelper.cpp +++ b/tests/testhelper.cpp @@ -26,16 +26,16 @@ void TestHelper::recognizeBtAddress() void TestHelper::parseNameAddress() { QString name, address; - address = extractBluetoothNameAddress("01:a2:b3:c4:d5:06", name); + std::tie(address, name) = extractBluetoothNameAddress("01:a2:b3:c4:d5:06"); QCOMPARE(address, QString("01:a2:b3:c4:d5:06")); QCOMPARE(name, QString()); - address = extractBluetoothNameAddress("somename (01:a2:b3:c4:d5:06)", name); + std::tie(address, name) = extractBluetoothNameAddress("somename (01:a2:b3:c4:d5:06)"); QCOMPARE(address, QString("01:a2:b3:c4:d5:06")); QCOMPARE(name, QString("somename")); - address = extractBluetoothNameAddress("garbage", name); + std::tie(address, name) = extractBluetoothNameAddress("garbage"); QCOMPARE(address, QString()); QCOMPARE(name, QString()); - address = extractBluetoothNameAddress("somename (LE:{6e50ff5d-cdd3-4c43-a80a-1ed4c7d2d2a5})", name); + std::tie(address, name) = extractBluetoothNameAddress("somename (LE:{6e50ff5d-cdd3-4c43-a80a-1ed4c7d2d2a5})"); QCOMPARE(address, QString("LE:{6e50ff5d-cdd3-4c43-a80a-1ed4c7d2d2a5}")); QCOMPARE(name, QString("somename")); diff --git a/tests/testmerge.cpp b/tests/testmerge.cpp index bc7fe1db7..86a90dc5a 100644 --- a/tests/testmerge.cpp +++ b/tests/testmerge.cpp @@ -13,7 +13,7 @@ void TestMerge::initTestCase() { /* we need to manually tell that the resource exists, because we are using it as library. */ Q_INIT_RESOURCE(subsurface); - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; } void TestMerge::cleanup() @@ -28,9 +28,9 @@ void TestMerge::testMergeEmpty() */ struct divelog log; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47.xml", &log), 0); - add_imported_dives(&log, IMPORT_MERGE_ALL_TRIPS); + divelog.add_imported_dives(log, import_flags::merge_all_trips); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test48.xml", &log), 0); - add_imported_dives(&log, IMPORT_MERGE_ALL_TRIPS); + divelog.add_imported_dives(log, import_flags::merge_all_trips); QCOMPARE(save_dives("./testmerge47+48.ssrf"), 0); QFile org(SUBSURFACE_TEST_DATA "/dives/test47+48.xml"); org.open(QFile::ReadOnly); @@ -51,9 +51,9 @@ void TestMerge::testMergeBackwards() */ struct divelog log; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test48.xml", &log), 0); - add_imported_dives(&log, IMPORT_MERGE_ALL_TRIPS); + divelog.add_imported_dives(log, import_flags::merge_all_trips); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47.xml", &log), 0); - add_imported_dives(&log, IMPORT_MERGE_ALL_TRIPS); + divelog.add_imported_dives(log, import_flags::merge_all_trips); QCOMPARE(save_dives("./testmerge47+48.ssrf"), 0); QFile org(SUBSURFACE_TEST_DATA "/dives/test48+47.xml"); org.open(QFile::ReadOnly); diff --git a/tests/testparse.cpp b/tests/testparse.cpp index 47fa67dd9..c75a8bb49 100644 --- a/tests/testparse.cpp +++ b/tests/testparse.cpp @@ -36,7 +36,7 @@ void TestParse::initTestCase() { /* we need to manually tell that the resource exists, because we are using it as library. */ Q_INIT_RESOURCE(subsurface); - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; } void TestParse::init() @@ -92,8 +92,8 @@ int TestParse::parseCSV(int units, std::string file) int TestParse::parseDivingLog() { // Parsing of DivingLog import from SQLite database - struct dive_site *ds = alloc_or_get_dive_site(0xdeadbeef, divelog.sites); - ds->name = copy_string("Suomi - - Hälvälä"); + struct dive_site *ds = divelog.sites.alloc_or_get(0xdeadbeef); + ds->name = "Suomi - - Hälvälä"; int ret = sqlite3_open(SUBSURFACE_TEST_DATA "/dives/TestDivingLog4.1.1.sql", &_sqlite3_handle); if (ret == 0) @@ -118,19 +118,19 @@ int TestParse::parseV3() void TestParse::testParse() { + // On some platforms (Windows) size_t has a different format string. + // Let's just cast to int. QCOMPARE(parseCSV(0, SUBSURFACE_TEST_DATA "/dives/test41.csv"), 0); - fprintf(stderr, "number of dives %d \n", divelog.dives->nr); + fprintf(stderr, "number of dives %d \n", static_cast(divelog.dives.size())); QCOMPARE(parseDivingLog(), 0); - fprintf(stderr, "number of dives %d \n", divelog.dives->nr); + fprintf(stderr, "number of dives %d \n", static_cast(divelog.dives.size())); QCOMPARE(parseV2NoQuestion(), 0); - fprintf(stderr, "number of dives %d \n", divelog.dives->nr); + fprintf(stderr, "number of dives %d \n", static_cast(divelog.dives.size())); QCOMPARE(parseV3(), 0); - fprintf(stderr, "number of dives %d \n", divelog.dives->nr); - - sort_dive_table(divelog.dives); + fprintf(stderr, "number of dives %d \n", static_cast(divelog.dives.size())); QCOMPARE(save_dives("./testout.ssrf"), 0); FILE_COMPARE("./testout.ssrf", @@ -142,8 +142,6 @@ void TestParse::testParseDM4() QCOMPARE(sqlite3_open(SUBSURFACE_TEST_DATA "/dives/TestDiveDM4.db", &_sqlite3_handle), 0); QCOMPARE(parse_dm4_buffer(_sqlite3_handle, 0, 0, 0, &divelog), 0); - sort_dive_table(divelog.dives); - QCOMPARE(save_dives("./testdm4out.ssrf"), 0); FILE_COMPARE("./testdm4out.ssrf", SUBSURFACE_TEST_DATA "/dives/TestDiveDM4.xml"); @@ -154,8 +152,6 @@ void TestParse::testParseDM5() QCOMPARE(sqlite3_open(SUBSURFACE_TEST_DATA "/dives/TestDiveDM5.db", &_sqlite3_handle), 0); QCOMPARE(parse_dm5_buffer(_sqlite3_handle, 0, 0, 0, &divelog), 0); - sort_dive_table(divelog.dives); - QCOMPARE(save_dives("./testdm5out.ssrf"), 0); FILE_COMPARE("./testdm5out.ssrf", SUBSURFACE_TEST_DATA "/dives/TestDiveDM5.xml"); @@ -186,20 +182,18 @@ void TestParse::testParseHUDC() ¶ms, "csv", &divelog), 0); - QCOMPARE(divelog.dives->nr, 1); + QCOMPARE(divelog.dives.size(), 1); /* * CSV import uses time and date stamps relative to current * time, thus we need to use a static (random) timestamp */ - if (divelog.dives->nr > 0) { - struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; - dive->when = 1255152761; - dive->dc.when = 1255152761; + if (!divelog.dives.empty()) { + struct dive &dive = *divelog.dives.back(); + dive.when = 1255152761; + dive.dcs[0].when = 1255152761; } - sort_dive_table(divelog.dives); - QCOMPARE(save_dives("./testhudcout.ssrf"), 0); FILE_COMPARE("./testhudcout.ssrf", SUBSURFACE_TEST_DATA "/dives/TestDiveSeabearHUDC.xml"); @@ -232,12 +226,10 @@ void TestParse::testParseNewFormat() .toLatin1() .data(), &divelog), 0); - QCOMPARE(divelog.dives->nr, i + 1); + QCOMPARE(divelog.dives.size(), i + 1); } - sort_dive_table(divelog.dives); - - fprintf(stderr, "number of dives %d \n", divelog.dives->nr); + fprintf(stderr, "number of dives %d \n", static_cast(divelog.dives.size())); QCOMPARE(save_dives("./testsbnewout.ssrf"), 0); // Currently the CSV parse fails @@ -253,9 +245,7 @@ void TestParse::testParseDLD() QVERIFY(err > 0); QVERIFY(try_to_open_zip(filename.toLatin1().data(), &divelog) > 0); - fprintf(stderr, "number of dives from DLD: %d \n", divelog.dives->nr); - - sort_dive_table(divelog.dives); + fprintf(stderr, "number of dives from DLD: %d \n", static_cast(divelog.dives.size())); // Compare output QCOMPARE(save_dives("./testdldout.ssrf"), 0); @@ -271,8 +261,6 @@ void TestParse::testParseMerge() QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/ostc.xml", &divelog), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/vyper.xml", &divelog), 0); - sort_dive_table(divelog.dives); - QCOMPARE(save_dives("./testmerge.ssrf"), 0); FILE_COMPARE("./testmerge.ssrf", SUBSURFACE_TEST_DATA "/dives/mergedVyperOstc.xml"); @@ -324,21 +312,15 @@ void TestParse::exportCSVDiveDetails() export_dives_xslt("testcsvexportmanual.csv", 0, 0, "xml2manualcsv.xslt", false); export_dives_xslt("testcsvexportmanualimperial.csv", 0, 1, "xml2manualcsv.xslt", false); - if (divelog.dives->nr > 0) { - struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; - saved_sac = dive->sac; - } + if (!divelog.dives.empty()) + saved_sac = divelog.dives.back()->sac; clear_dive_file_data(); parseCSVmanual(1, "testcsvexportmanualimperial.csv"); // We do not currently support reading SAC, thus faking it - if (divelog.dives->nr > 0) { - struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; - dive->sac = saved_sac; - } - - sort_dive_table(divelog.dives); + if (!divelog.dives.empty()) + divelog.dives.back()->sac = saved_sac; export_dives_xslt("testcsvexportmanual2.csv", 0, 0, "xml2manualcsv.xslt", false); FILE_COMPARE("testcsvexportmanual2.csv", @@ -358,10 +340,8 @@ void TestParse::exportSubsurfaceCSV() export_dives_xslt("testcsvexportmanual-cyl.csv", 0, 0, "xml2manualcsv.xslt", false); export_dives_xslt("testcsvexportmanualimperial-cyl.csv", 0, 1, "xml2manualcsv.xslt", false); - if (divelog.dives->nr > 0) { - struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; - saved_sac = dive->sac; - } + if (!divelog.dives.empty()) + saved_sac = divelog.dives.back()->sac; clear_dive_file_data(); @@ -370,12 +350,8 @@ void TestParse::exportSubsurfaceCSV() parse_csv_file("testcsvexportmanualimperial-cyl.csv", ¶ms, "SubsurfaceCSV", &divelog); // We do not currently support reading SAC, thus faking it - if (divelog.dives->nr > 0) { - struct dive *dive = divelog.dives->dives[divelog.dives->nr - 1]; - dive->sac = saved_sac; - } - - sort_dive_table(divelog.dives); + if (!divelog.dives.empty()) + divelog.dives.back()->sac = saved_sac; export_dives_xslt("testcsvexportmanual2-cyl.csv", 0, 0, "xml2manualcsv.xslt", false); FILE_COMPARE("testcsvexportmanual2-cyl.csv", @@ -414,7 +390,6 @@ void TestParse::exportCSVDiveProfile() clear_dive_file_data(); parseCSVprofile(1, "testcsvexportprofileimperial.csv"); - sort_dive_table(divelog.dives); export_dives_xslt("testcsvexportprofile2.csv", 0, 0, "xml2csv.xslt", false); FILE_COMPARE("testcsvexportprofile2.csv", @@ -432,7 +407,6 @@ void TestParse::exportUDDF() clear_dive_file_data(); parse_file("testuddfexport.uddf", &divelog); - sort_dive_table(divelog.dives); export_dives_xslt("testuddfexport2.uddf", 0, 1, "uddf-export.xslt", false); FILE_COMPARE("testuddfexport.uddf", @@ -478,9 +452,7 @@ void TestParse::parseDL7() QCOMPARE(parse_csv_file(SUBSURFACE_TEST_DATA "/dives/DL7.zxu", ¶ms, "DL7", &divelog), 0); - QCOMPARE(divelog.dives->nr, 3); - - sort_dive_table(divelog.dives); + QCOMPARE(divelog.dives.size(), 3); QCOMPARE(save_dives("./testdl7out.ssrf"), 0); FILE_COMPARE("./testdl7out.ssrf", diff --git a/tests/testparseperformance.cpp b/tests/testparseperformance.cpp index eac021da8..ee84ff6a5 100644 --- a/tests/testparseperformance.cpp +++ b/tests/testparseperformance.cpp @@ -24,7 +24,7 @@ void TestParsePerformance::initTestCase() QTextCodec::setCodecForLocale(QTextCodec::codecForMib(106)); // first, setup the preferences an proxy information - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; QCoreApplication::setOrganizationName("Subsurface"); QCoreApplication::setOrganizationDomain("subsurface.hohndel.org"); QCoreApplication::setApplicationName("Subsurface"); @@ -33,11 +33,11 @@ void TestParsePerformance::initTestCase() QNetworkProxy proxy; proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); - proxy.setHostName(prefs.proxy_host); + proxy.setHostName(QString::fromStdString(prefs.proxy_host)); proxy.setPort(prefs.proxy_port); if (prefs.proxy_auth) { - proxy.setUser(prefs.proxy_user); - proxy.setPassword(prefs.proxy_pass); + proxy.setUser(QString::fromStdString(prefs.proxy_user)); + proxy.setPassword(QString::fromStdString(prefs.proxy_pass)); } QNetworkProxy::setApplicationProxy(proxy); diff --git a/tests/testpicture.cpp b/tests/testpicture.cpp index 3c92ce4c0..010e1525d 100644 --- a/tests/testpicture.cpp +++ b/tests/testpicture.cpp @@ -14,7 +14,7 @@ void TestPicture::initTestCase() { /* we need to manually tell that the resource exists, because we are using it as library. */ Q_INIT_RESOURCE(subsurface); - prefs.cloud_base_url = strdup(default_prefs.cloud_base_url); + prefs.cloud_base_url = default_prefs.cloud_base_url; } #define PIC1_NAME "/dives/images/wreck.jpg" @@ -24,45 +24,43 @@ void TestPicture::initTestCase() void TestPicture::addPicture() { - struct dive *dive, *dive1, *dive2; - struct picture *pic1, *pic2; verbose = 1; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test44.xml", &divelog), 0); - dive = get_dive(0); + QVERIFY(divelog.dives.size() >= 1); + struct dive &dive = *divelog.dives[0]; // Pictures will be added to selected dives - dive->selected = true; - QVERIFY(dive != NULL); + dive.selected = true; // So far no picture in dive - QVERIFY(dive->pictures.nr == 0); + QVERIFY(dive.pictures.size() == 0); - pic1 = create_picture(SUBSURFACE_TEST_DATA "/dives/images/wreck.jpg", 0, false, &dive1); - pic2 = create_picture(SUBSURFACE_TEST_DATA "/dives/images/data_after_EOI.jpg", 0, false, &dive2); - QVERIFY(pic1 != NULL); - QVERIFY(pic2 != NULL); - QVERIFY(dive1 == dive); - QVERIFY(dive2 == dive); + { + auto [pic1, dive1] = create_picture(SUBSURFACE_TEST_DATA "/dives/images/wreck.jpg", 0, false); + auto [pic2, dive2] = create_picture(SUBSURFACE_TEST_DATA "/dives/images/data_after_EOI.jpg", 0, false); + QVERIFY(pic1); + QVERIFY(pic2); + QVERIFY(dive1 == &dive); + QVERIFY(dive2 == &dive); - add_picture(&dive->pictures, *pic1); - add_picture(&dive->pictures, *pic2); - free(pic1); - free(pic2); + add_picture(dive.pictures, std::move(*pic1)); + add_picture(dive.pictures, std::move(*pic2)); + } // Now there are two pictures - QVERIFY(dive->pictures.nr == 2); - pic1 = &dive->pictures.pictures[0]; - pic2 = &dive->pictures.pictures[1]; + QVERIFY(dive.pictures.size() == 2); + const picture &pic1 = dive.pictures[0]; + const picture &pic2 = dive.pictures[1]; // 1st appearing at time 21:01 // 2nd appearing at time 22:01 - QVERIFY(pic1->offset.seconds == 1261); - QVERIFY(pic1->location.lat.udeg == 47934500); - QVERIFY(pic1->location.lon.udeg == 11334500); - QVERIFY(pic2->offset.seconds == 1321); + QVERIFY(pic1.offset.seconds == 1261); + QVERIFY(pic1.location.lat.udeg == 47934500); + QVERIFY(pic1.location.lon.udeg == 11334500); + QVERIFY(pic2.offset.seconds == 1321); - learnPictureFilename(pic1->filename, PIC1_NAME); - learnPictureFilename(pic2->filename, PIC2_NAME); - QCOMPARE(localFilePath(pic1->filename), QString(PIC1_NAME)); - QCOMPARE(localFilePath(pic2->filename), QString(PIC2_NAME)); + learnPictureFilename(QString::fromStdString(pic1.filename), PIC1_NAME); + learnPictureFilename(QString::fromStdString(pic2.filename), PIC2_NAME); + QCOMPARE(localFilePath(QString::fromStdString(pic1.filename)), QString(PIC1_NAME)); + QCOMPARE(localFilePath(QString::fromStdString(pic2.filename)), QString(PIC2_NAME)); } QTEST_GUILESS_MAIN(TestPicture) diff --git a/tests/testplan.cpp b/tests/testplan.cpp index d6caaba2b..4e145c766 100644 --- a/tests/testplan.cpp +++ b/tests/testplan.cpp @@ -12,13 +12,11 @@ #define DEBUG 1 // testing the dive plan algorithm -static struct dive dive = { 0 }; -static struct decostop stoptable[60]; +static struct dive dive; static struct deco_state test_deco_state; -extern bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, int dcNr, int timestep, struct decostop *decostoptable, deco_state_cache &cache, bool is_planner, bool show_disclaimer); void setupPrefs() { - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; prefs.ascrate50 = feet_to_mm(30) / 60; prefs.ascrate75 = prefs.ascrate50; prefs.ascratestops = prefs.ascrate50; @@ -28,7 +26,7 @@ void setupPrefs() void setupPrefsVpmb() { - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; prefs.ascrate50 = 10000 / 60; prefs.ascrate75 = prefs.ascrate50; prefs.ascratestops = prefs.ascrate50; @@ -39,401 +37,415 @@ void setupPrefsVpmb() prefs.vpmb_conservatism = 0; } -void setupPlan(struct diveplan *dp) +diveplan setupPlan() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->gfhigh = 100; - dp->gflow = 100; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.gfhigh = 100; + dp.gflow = 100; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{150}, {450}}; - struct gasmix ean36 = {{360}, {0}}; - struct gasmix oxygen = {{1000}, {0}}; - pressure_t po2 = {1600}; + struct gasmix bottomgas = { 15_percent, 45_percent}; + struct gasmix ean36 = { 36_percent, 0_percent}; + struct gasmix oxygen = { 100_percent, 0_percent}; + pressure_t po2 = 1600_mbar;; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); + cylinder_t *cyl2 = dive.get_or_create_cylinder(2); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 36000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 36_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = ean36; cyl2->gasmix = oxygen; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(79, 260) * 60 / M_OR_FT(23, 75); - plan_add_segment(dp, 0, gas_mod(ean36, po2, &dive, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); - plan_add_segment(dp, 0, gas_mod(oxygen, po2, &dive, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(ean36, po2, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(oxygen, po2, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); plan_add_segment(dp, droptime, M_OR_FT(79, 260), 0, 0, 1, OC); plan_add_segment(dp, 30 * 60 - droptime, M_OR_FT(79, 260), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb45m30mTx(struct diveplan *dp) +diveplan setupPlanVpmb45m30mTx() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->gfhigh = 100; - dp->gflow = 100; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.gfhigh = 100; + dp.gflow = 100; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{210}, {350}}; - struct gasmix ean50 = {{500}, {0}}; - struct gasmix oxygen = {{1000}, {0}}; - pressure_t po2 = {1600}; + struct gasmix bottomgas = { 21_percent, 35_percent}; + struct gasmix ean50 = { 50_percent, 0_percent}; + struct gasmix oxygen = { 100_percent, 0_percent}; + pressure_t po2 = 1600_mbar; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); + cylinder_t *cyl2 = dive.get_or_create_cylinder(2); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 24000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 24_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = ean50; cyl2->gasmix = oxygen; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(45, 150) * 60 / M_OR_FT(23, 75); - plan_add_segment(dp, 0, gas_mod(ean50, po2, &dive, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); - plan_add_segment(dp, 0, gas_mod(oxygen, po2, &dive, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(ean50, po2, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(oxygen, po2, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); plan_add_segment(dp, droptime, M_OR_FT(45, 150), 0, 0, 1, OC); plan_add_segment(dp, 30 * 60 - droptime, M_OR_FT(45, 150), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb60m10mTx(struct diveplan *dp) +diveplan setupPlanVpmb60m10mTx() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->gfhigh = 100; - dp->gflow = 100; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.gfhigh = 100; + dp.gflow = 100; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{180}, {450}}; - struct gasmix tx50_15 = {{500}, {150}}; - struct gasmix oxygen = {{1000}, {0}}; - pressure_t po2 = {1600}; + struct gasmix bottomgas = { 18_percent, 45_percent }; + struct gasmix tx50_15 = { 50_percent, 15_percent }; + struct gasmix oxygen = { 100_percent, 0_percent }; + pressure_t po2 = 1600_mbar; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); + cylinder_t *cyl2 = dive.get_or_create_cylinder(2); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 24000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 24_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = tx50_15; cyl2->gasmix = oxygen; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(60, 200) * 60 / M_OR_FT(23, 75); - plan_add_segment(dp, 0, gas_mod(tx50_15, po2, &dive, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); - plan_add_segment(dp, 0, gas_mod(oxygen, po2, &dive, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(tx50_15, po2, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(oxygen, po2, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); plan_add_segment(dp, droptime, M_OR_FT(60, 200), 0, 0, 1, OC); plan_add_segment(dp, 10 * 60 - droptime, M_OR_FT(60, 200), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb60m30minAir(struct diveplan *dp) +diveplan setupPlanVpmb60m30minAir() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{210}, {0}}; - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); + struct gasmix bottomgas = { 21_percent, 0_percent}; + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 100000; - cyl0->type.workingpressure.mbar = 232000; - dive.surface_pressure.mbar = 1013; + cyl0->type.size = 100_l; + cyl0->type.workingpressure = 232_bar; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(60, 200) * 60 / M_OR_FT(99, 330); plan_add_segment(dp, droptime, M_OR_FT(60, 200), 0, 0, 1, OC); plan_add_segment(dp, 30 * 60 - droptime, M_OR_FT(60, 200), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb60m30minEan50(struct diveplan *dp) +diveplan setupPlanVpmb60m30minEan50() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{210}, {0}}; - struct gasmix ean50 = {{500}, {0}}; - pressure_t po2 = {1600}; + struct gasmix bottomgas = { 21_percent, 0_percent }; + struct gasmix ean50 = { 50_percent, 0_percent }; + pressure_t po2 = 1600_mbar; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 36000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 36_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = ean50; - dive.surface_pressure.mbar = 1013; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(60, 200) * 60 / M_OR_FT(99, 330); - plan_add_segment(dp, 0, gas_mod(ean50, po2, &dive, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(ean50, po2, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); plan_add_segment(dp, droptime, M_OR_FT(60, 200), 0, 0, 1, OC); plan_add_segment(dp, 30 * 60 - droptime, M_OR_FT(60, 200), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb60m30minTx(struct diveplan *dp) +diveplan setupPlanVpmb60m30minTx() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{180}, {450}}; - struct gasmix ean50 = {{500}, {0}}; - pressure_t po2 = {1600}; + struct gasmix bottomgas = { 18_percent, 45_percent }; + struct gasmix ean50 = { 50_percent, 0_percent }; + pressure_t po2 = 1600_mbar; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 36000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 36_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = ean50; - dive.surface_pressure.mbar = 1013; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(60, 200) * 60 / M_OR_FT(99, 330); - plan_add_segment(dp, 0, gas_mod(ean50, po2, &dive, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(ean50, po2, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); plan_add_segment(dp, droptime, M_OR_FT(60, 200), 0, 0, 1, OC); plan_add_segment(dp, 30 * 60 - droptime, M_OR_FT(60, 200), 0, 0, 1, OC); + return dp; } -void setupPlanVpmbMultiLevelAir(struct diveplan *dp) +diveplan setupPlanVpmbMultiLevelAir() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{210}, {0}}; - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); + struct gasmix bottomgas = { 21_percent, 0_percent }; + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 200000; - cyl0->type.workingpressure.mbar = 232000; - dive.surface_pressure.mbar = 1013; + cyl0->type.size = 200_l; + cyl0->type.workingpressure = 232_bar; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(20, 66) * 60 / M_OR_FT(99, 330); plan_add_segment(dp, droptime, M_OR_FT(20, 66), 0, 0, 1, OC); plan_add_segment(dp, 10 * 60 - droptime, M_OR_FT(20, 66), 0, 0, 1, OC); plan_add_segment(dp, 1 * 60, M_OR_FT(60, 200), 0, 0, 1, OC); plan_add_segment(dp, 29 * 60, M_OR_FT(60, 200), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb100m60min(struct diveplan *dp) +diveplan setupPlanVpmb100m60min() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{180}, {450}}; - struct gasmix ean50 = {{500}, {0}}; - struct gasmix oxygen = {{1000}, {0}}; - pressure_t po2 = {1600}; + struct gasmix bottomgas = { 18_percent, 45_percent }; + struct gasmix ean50 = { 50_percent, 0_percent }; + struct gasmix oxygen = { 100_percent, 0_percent }; + pressure_t po2 = 1600_mbar; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); + cylinder_t *cyl2 = dive.get_or_create_cylinder(2); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 200000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 200_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = ean50; cyl2->gasmix = oxygen; - dive.surface_pressure.mbar = 1013; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(100, 330) * 60 / M_OR_FT(99, 330); - plan_add_segment(dp, 0, gas_mod(ean50, po2, &dive, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); - plan_add_segment(dp, 0, gas_mod(oxygen, po2, &dive, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(ean50, po2, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(oxygen, po2, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); plan_add_segment(dp, droptime, M_OR_FT(100, 330), 0, 0, 1, OC); plan_add_segment(dp, 60 * 60 - droptime, M_OR_FT(100, 330), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb100m10min(struct diveplan *dp) +diveplan setupPlanVpmb100m10min() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{180}, {450}}; - struct gasmix ean50 = {{500}, {0}}; - struct gasmix oxygen = {{1000}, {0}}; - pressure_t po2 = {1600}; + struct gasmix bottomgas = { 18_percent, 45_percent }; + struct gasmix ean50 = { 50_percent, 0_percent}; + struct gasmix oxygen = { 100_percent, 0_percent}; + pressure_t po2 = 1600_mbar; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); + cylinder_t *cyl2 = dive.get_or_create_cylinder(2); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 60000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 60_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = ean50; cyl2->gasmix = oxygen; - dive.surface_pressure.mbar = 1013; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(100, 330) * 60 / M_OR_FT(99, 330); - plan_add_segment(dp, 0, gas_mod(ean50, po2, &dive, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); - plan_add_segment(dp, 0, gas_mod(oxygen, po2, &dive, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(ean50, po2, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(oxygen, po2, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); plan_add_segment(dp, droptime, M_OR_FT(100, 330), 0, 0, 1, OC); plan_add_segment(dp, 10 * 60 - droptime, M_OR_FT(100, 330), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb30m20min(struct diveplan *dp) +diveplan setupPlanVpmb30m20min() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{210}, {0}}; - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); + struct gasmix bottomgas = { 21_percent, 0_percent }; + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 36000; - cyl0->type.workingpressure.mbar = 232000; - dive.surface_pressure.mbar = 1013; + cyl0->type.size = 36_l; + cyl0->type.workingpressure = 232_bar; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(30, 100) * 60 / M_OR_FT(18, 60); plan_add_segment(dp, droptime, M_OR_FT(30, 100), 0, 0, 1, OC); plan_add_segment(dp, 20 * 60 - droptime, M_OR_FT(30, 100), 0, 0, 1, OC); + return dp; } -void setupPlanVpmb100mTo70m30min(struct diveplan *dp) +diveplan setupPlanVpmb100mTo70m30min() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix bottomgas = {{120}, {650}}; - struct gasmix tx21_35 = {{210}, {350}}; - struct gasmix ean50 = {{500}, {0}}; - struct gasmix oxygen = {{1000}, {0}}; - pressure_t po2 = {1600}; + struct gasmix bottomgas = { 12_percent, 65_percent }; + struct gasmix tx21_35 = { 21_percent, 35_percent }; + struct gasmix ean50 = { 50_percent, 0_percent }; + struct gasmix oxygen = { 100_percent, 0_percent }; + pressure_t po2 = 1600_mbar; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl3 = get_or_create_cylinder(&dive, 3); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); - cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2); + cylinder_t *cyl3 = dive.get_or_create_cylinder(3); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); + cylinder_t *cyl2 = dive.get_or_create_cylinder(2); cyl0->gasmix = bottomgas; - cyl0->type.size.mliter = 36000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 36_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = tx21_35; cyl2->gasmix = ean50; cyl3->gasmix = oxygen; - dive.surface_pressure.mbar = 1013; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); int droptime = M_OR_FT(100, 330) * 60 / M_OR_FT(18, 60); - plan_add_segment(dp, 0, gas_mod(tx21_35, po2, &dive, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); - plan_add_segment(dp, 0, gas_mod(ean50, po2, &dive, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); - plan_add_segment(dp, 0, gas_mod(oxygen, po2, &dive, M_OR_FT(3, 10)).mm, 3, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(tx21_35, po2, M_OR_FT(3, 10)).mm, 1, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(ean50, po2, M_OR_FT(3, 10)).mm, 2, 0, 1, OC); + plan_add_segment(dp, 0, dive.gas_mod(oxygen, po2, M_OR_FT(3, 10)).mm, 3, 0, 1, OC); plan_add_segment(dp, droptime, M_OR_FT(100, 330), 0, 0, 1, OC); plan_add_segment(dp, 20 * 60 - droptime, M_OR_FT(100, 330), 0, 0, 1, OC); plan_add_segment(dp, 3 * 60, M_OR_FT(70, 230), 0, 0, 1, OC); plan_add_segment(dp, (30 - 20 - 3) * 60, M_OR_FT(70, 230), 0, 0, 1, OC); + return dp; } /* This tests handling different gases in the manually entered part of the dive */ -void setupPlanSeveralGases(struct diveplan *dp) +diveplan setupPlanSeveralGases() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - struct gasmix ean36 = {{360}, {0}}; - struct gasmix tx11_50 = {{110}, {500}}; + struct gasmix ean36 = { 36_percent, 0_percent }; + struct gasmix tx11_50 = { 11_percent, 50_percent }; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); cyl0->gasmix = ean36; - cyl0->type.size.mliter = 36000; - cyl0->type.workingpressure.mbar = 232000; + cyl0->type.size = 36_l; + cyl0->type.workingpressure = 232_bar; cyl1->gasmix = tx11_50; - dive.surface_pressure.mbar = 1013; + dive.surface_pressure = 1_atm; reset_cylinders(&dive, true); - free_dps(dp); plan_add_segment(dp, 120, 40000, 0, 0, true, OC); plan_add_segment(dp, 18 * 60, 40000, 0, 0, true, OC); plan_add_segment(dp, 10 * 60, 10000, 1, 0, true, OC); plan_add_segment(dp, 5 * 60, 10000, 0, 0, true, OC); + return dp; } -void setupPlanCcr(struct diveplan *dp) +diveplan setupPlanCcr() { - dp->salinity = 10300; - dp->surface_pressure = 1013; - dp->gflow = 50; - dp->gfhigh = 70; - dp->bottomsac = prefs.bottomsac; - dp->decosac = prefs.decosac; + diveplan dp; + dp.salinity = 10300; + dp.surface_pressure = 1_atm; + dp.gflow = 50; + dp.gfhigh = 70; + dp.bottomsac = prefs.bottomsac; + dp.decosac = prefs.decosac; - pressure_t po2 = {1600}; - struct gasmix diluent = {{200}, {210}}; - struct gasmix ean53 = {{530}, {0}}; - struct gasmix tx19_33 = {{190}, {330}}; + pressure_t po2 = 1600_mbar; + struct gasmix diluent = { 20_percent, 21_percent}; + struct gasmix ean53 = { 53_percent, 0_percent}; + struct gasmix tx19_33 = { 19_percent, 33_percent}; // Note: we add the highest-index cylinder first, because // pointers to cylinders are not stable when reallocating. // For testing OK - don't do this in actual code! - cylinder_t *cyl2 = get_or_create_cylinder(&dive, 2); - cylinder_t *cyl0 = get_or_create_cylinder(&dive, 0); - cylinder_t *cyl1 = get_or_create_cylinder(&dive, 1); + cylinder_t *cyl2 = dive.get_or_create_cylinder(2); + cylinder_t *cyl0 = dive.get_or_create_cylinder(0); + cylinder_t *cyl1 = dive.get_or_create_cylinder(1); cyl0->gasmix = diluent; - cyl0->depth = gas_mod(diluent, po2, &dive, M_OR_FT(3, 10)); - cyl0->type.size.mliter = 3000; - cyl0->type.workingpressure.mbar = 200000; + cyl0->depth = dive.gas_mod(diluent, po2, M_OR_FT(3, 10)); + cyl0->type.size = 3_l; + cyl0->type.workingpressure = 200_bar; cyl0->cylinder_use = DILUENT; cyl1->gasmix = ean53; - cyl1->depth = gas_mod(ean53, po2, &dive, M_OR_FT(3, 10)); + cyl1->depth = dive.gas_mod(ean53, po2, M_OR_FT(3, 10)); cyl2->gasmix = tx19_33; - cyl2->depth = gas_mod(tx19_33, po2, &dive, M_OR_FT(3, 10)); + cyl2->depth = dive.gas_mod(tx19_33, po2, M_OR_FT(3, 10)); reset_cylinders(&dive, true); - free_dps(dp); plan_add_segment(dp, 0, cyl1->depth.mm, 1, 0, false, OC); plan_add_segment(dp, 0, cyl2->depth.mm, 2, 0, false, OC); plan_add_segment(dp, 20 * 60, M_OR_FT(60, 197), 0, 1300, true, CCR); + + return dp; } /* We compare the calculated runtimes against two values: @@ -480,36 +492,32 @@ void TestPlan::testMetric() prefs.units.length = units::METERS; prefs.planner_deco_mode = BUEHLMANN; - struct diveplan testPlan = {}; - setupPlan(&testPlan); + auto testPlan = setupPlan(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 148l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 148l); + QVERIFY(dive.dcs[0].events.size() >= 2); // check first gas change to EAN36 at 33m - struct event *ev = dive.dc.events; - QVERIFY(ev != NULL); + struct event *ev = &dive.dcs[0].events[0]; QCOMPARE(ev->gas.index, 1); QCOMPARE(ev->value, 36); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 33000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 33000); // check second gas change to Oxygen at 6m - ev = ev->next; + ev = &dive.dcs[0].events[1]; QVERIFY(ev != NULL); QCOMPARE(ev->gas.index, 2); QCOMPARE(ev->value, 100); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 6000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 6000); // check expected run time of 109 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 109u * 60u, 109u * 60u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 109u * 60u, 109u * 60u)); } void TestPlan::testImperial() @@ -521,36 +529,32 @@ void TestPlan::testImperial() prefs.units.length = units::FEET; prefs.planner_deco_mode = BUEHLMANN; - struct diveplan testPlan = {}; - setupPlan(&testPlan); + auto testPlan = setupPlan(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 155l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 155l); + QVERIFY(dive.dcs[0].events.size() >= 2); // check first gas change to EAN36 at 33m - struct event *ev = dive.dc.events; - QVERIFY(ev != NULL); + struct event *ev = &dive.dcs[0].events[0]; QCOMPARE(ev->gas.index, 1); QCOMPARE(ev->value, 36); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 33528); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 33528); // check second gas change to Oxygen at 6m - ev = ev->next; + ev = &dive.dcs[0].events[1]; QVERIFY(ev != NULL); QCOMPARE(ev->gas.index, 2); QCOMPARE(ev->value, 100); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 6096); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 6096); // check expected run time of 111 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 111u * 60u - 2u, 111u * 60u - 2u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 111u * 60u - 2u, 111u * 60u - 2u)); } void TestPlan::testVpmbMetric45m30minTx() @@ -561,26 +565,22 @@ void TestPlan::testVpmbMetric45m30minTx() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmb45m30mTx(&testPlan); + auto testPlan = setupPlanVpmb45m30mTx(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 108l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 108l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); // check benchmark run time of 141 minutes, and known Subsurface runtime of 139 minutes - //QVERIFY(compareDecoTime(dive.dc.duration.seconds, 141u * 60u + 20u, 139u * 60u + 20u)); + //QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 141u * 60u + 20u, 139u * 60u + 20u)); } void TestPlan::testVpmbMetric60m10minTx() @@ -591,26 +591,22 @@ void TestPlan::testVpmbMetric60m10minTx() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmb60m10mTx(&testPlan); + auto testPlan = setupPlanVpmb60m10mTx(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 162l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 162l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); // check benchmark run time of 141 minutes, and known Subsurface runtime of 139 minutes - //QVERIFY(compareDecoTime(dive.dc.duration.seconds, 141u * 60u + 20u, 139u * 60u + 20u)); + //QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 141u * 60u + 20u, 139u * 60u + 20u)); } void TestPlan::testVpmbMetric60m30minAir() @@ -621,26 +617,22 @@ void TestPlan::testVpmbMetric60m30minAir() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmb60m30minAir(&testPlan); + auto testPlan = setupPlanVpmb60m30minAir(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 180l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 180l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); // check benchmark run time of 141 minutes, and known Subsurface runtime of 139 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 141u * 60u + 20u, 139u * 60u + 20u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 141u * 60u + 20u, 139u * 60u + 20u)); } void TestPlan::testVpmbMetric60m30minEan50() @@ -651,32 +643,28 @@ void TestPlan::testVpmbMetric60m30minEan50() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmb60m30minEan50(&testPlan); + auto testPlan = setupPlanVpmb60m30minEan50(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 155l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 155l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); + QVERIFY(dive.dcs[0].events.size() >= 1); // check first gas change to EAN50 at 21m - struct event *ev = dive.dc.events; - QVERIFY(ev != NULL); + struct event *ev = &dive.dcs[0].events[0]; QCOMPARE(ev->gas.index, 1); QCOMPARE(ev->value, 50); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 21000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 21000); // check benchmark run time of 95 minutes, and known Subsurface runtime of 96 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 95u * 60u + 20u, 96u * 60u + 20u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 95u * 60u + 20u, 96u * 60u + 20u)); } void TestPlan::testVpmbMetric60m30minTx() @@ -687,32 +675,28 @@ void TestPlan::testVpmbMetric60m30minTx() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmb60m30minTx(&testPlan); + auto testPlan = setupPlanVpmb60m30minTx(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 159l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 159l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); // check first gas change to EAN50 at 21m - struct event *ev = dive.dc.events; - QVERIFY(ev != NULL); + QVERIFY(dive.dcs[0].events.size() >= 1); + struct event *ev = &dive.dcs[0].events[0]; QCOMPARE(ev->gas.index, 1); QCOMPARE(ev->value, 50); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 21000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 21000); // check benchmark run time of 89 minutes, and known Subsurface runtime of 89 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 89u * 60u + 20u, 89u * 60u + 20u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 89u * 60u + 20u, 89u * 60u + 20u)); } void TestPlan::testVpmbMetric100m60min() @@ -723,38 +707,34 @@ void TestPlan::testVpmbMetric100m60min() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmb100m60min(&testPlan); + auto testPlan = setupPlanVpmb100m60min(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 157l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 157l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); + QVERIFY(dive.dcs[0].events.size() >= 2); // check first gas change to EAN50 at 21m - struct event *ev = dive.dc.events; - QVERIFY(ev != NULL); + struct event *ev = &dive.dcs[0].events[0]; QCOMPARE(ev->gas.index, 1); QCOMPARE(ev->value, 50); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 21000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 21000); // check second gas change to Oxygen at 6m - ev = ev->next; + ev = &dive.dcs[0].events[1]; QVERIFY(ev != NULL); QCOMPARE(ev->gas.index, 2); QCOMPARE(ev->value, 100); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 6000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 6000); // check benchmark run time of 311 minutes, and known Subsurface runtime of 314 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 311u * 60u + 20u, 315u * 60u + 20u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 311u * 60u + 20u, 315u * 60u + 20u)); } void TestPlan::testMultipleGases() @@ -765,22 +745,18 @@ void TestPlan::testMultipleGases() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; + auto testPlan = setupPlanSeveralGases(); - setupPlanSeveralGases(&testPlan); - - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif - gasmix gas; - gas = get_gasmix_at_time(&dive, &dive.dc, {20 * 60 + 1}); + gasmix gas = dive.get_gasmix_at_time(dive.dcs[0], 20_min + 1_sec); QCOMPARE(get_o2(gas), 110); - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 2480u, 2480u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 2480u, 2480u)); } void TestPlan::testVpmbMetricMultiLevelAir() @@ -791,26 +767,22 @@ void TestPlan::testVpmbMetricMultiLevelAir() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmbMultiLevelAir(&testPlan); + auto testPlan = setupPlanVpmbMultiLevelAir(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 101l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 101l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); // check benchmark run time of 167 minutes, and known Subsurface runtime of 169 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 167u * 60u + 20u, 169u * 60u + 20u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 167u * 60u + 20u, 169u * 60u + 20u)); } void TestPlan::testVpmbMetric100m10min() @@ -821,38 +793,35 @@ void TestPlan::testVpmbMetric100m10min() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmb100m10min(&testPlan); + auto testPlan = setupPlanVpmb100m10min(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 175l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 175l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); + QVERIFY(dive.dcs[0].events.size() >= 2); // check first gas change to EAN50 at 21m - struct event *ev = dive.dc.events; + struct event *ev = &dive.dcs[0].events[0]; QVERIFY(ev != NULL); QCOMPARE(ev->gas.index, 1); QCOMPARE(ev->value, 50); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 21000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 21000); // check second gas change to Oxygen at 6m - ev = ev->next; + ev = &dive.dcs[0].events[1]; QVERIFY(ev != NULL); QCOMPARE(ev->gas.index, 2); QCOMPARE(ev->value, 100); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 6000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 6000); // check benchmark run time of 58 minutes, and known Subsurface runtime of 57 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 58u * 60u + 20u, 57u * 60u + 20u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 58u * 60u + 20u, 57u * 60u + 20u)); } /* This tests that a previously calculated plan isn't affecting the calculations of the next plan. @@ -867,85 +836,76 @@ void TestPlan::testVpmbMetricRepeat() prefs.unit_system = METRIC; prefs.units.length = units::METERS; - struct diveplan testPlan = {}; - setupPlanVpmb30m20min(&testPlan); + auto testPlan = setupPlanVpmb30m20min(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - struct divedatapoint *dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 61l); + auto dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 61l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); // check benchmark run time of 27 minutes, and known Subsurface runtime of 28 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 27u * 60u + 20u, 27u * 60u + 20u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 27u * 60u + 20u, 27u * 60u + 20u)); - int firstDiveRunTimeSeconds = dive.dc.duration.seconds; + int firstDiveRunTimeSeconds = dive.dcs[0].duration.seconds; - setupPlanVpmb100mTo70m30min(&testPlan); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + testPlan = setupPlanVpmb100mTo70m30min(); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 80l); + dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 80l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); + QVERIFY(dive.dcs[0].events.size() >= 3); // check first gas change to 21/35 at 66m - struct event *ev = dive.dc.events; + struct event *ev = &dive.dcs[0].events[0]; QVERIFY(ev != NULL); QCOMPARE(ev->gas.index, 1); QCOMPARE(ev->gas.mix.o2.permille, 210); QCOMPARE(ev->gas.mix.he.permille, 350); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 66000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 66000); // check second gas change to EAN50 at 21m - ev = ev->next; + ev = &dive.dcs[0].events[1]; QCOMPARE(ev->gas.index, 2); QCOMPARE(ev->value, 50); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 21000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 21000); // check third gas change to Oxygen at 6m - ev = ev->next; + ev = &dive.dcs[0].events[2]; QVERIFY(ev != NULL); QCOMPARE(ev->gas.index, 3); QCOMPARE(ev->value, 100); - QCOMPARE(get_depth_at_time(&dive.dc, ev->time.seconds), 6000); + QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 6000); // we don't have a benchmark, known Subsurface runtime is 126 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 127u * 60u + 20u, 127u * 60u + 20u)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 127u * 60u + 20u, 127u * 60u + 20u)); - setupPlanVpmb30m20min(&testPlan); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, 1, 0); + testPlan = setupPlanVpmb30m20min(); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, 1, 0); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check minimum gas result - dp = testPlan.dp; - while (!dp->minimum_gas.mbar && dp->next) - dp = dp->next; - QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 61l); + dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; }); + QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 61l); // print first ceiling - printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &dive) * 0.001)); + printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001); // check runtime is exactly the same as the first time - int finalDiveRunTimeSeconds = dive.dc.duration.seconds; + int finalDiveRunTimeSeconds = dive.dcs[0].duration.seconds; QCOMPARE(finalDiveRunTimeSeconds, firstDiveRunTimeSeconds); } @@ -961,37 +921,35 @@ void TestPlan::testCcrBailoutGasSelection() prefs.unit_system = METRIC; prefs.units.length = units::METERS; prefs.planner_deco_mode = BUEHLMANN; - dive.dc.divemode = CCR; + dive.dcs[0].divemode = CCR; prefs.dobailout = true; - struct diveplan testPlan = {}; - setupPlanCcr(&testPlan); + auto testPlan = setupPlanCcr(); - plan(&test_deco_state, &testPlan, &dive, 0, 60, stoptable, cache, true, false); + plan(&test_deco_state, testPlan, &dive, 0, 60, cache, true, false); #if DEBUG - free(dive.notes); - dive.notes = NULL; - save_dive(stdout, &dive, false); + dive.notes.clear(); + save_dive(stdout, dive, false); #endif // check diluent used - cylinder_t *cylinder = get_cylinder(&dive, get_cylinderid_at_time(&dive, &dive.dc, { 20 * 60 - 1 })); + cylinder_t *cylinder = dive.get_cylinder(get_cylinderid_at_time(&dive, &dive.dcs[0], 20_min - 1_sec)); QCOMPARE(cylinder->cylinder_use, DILUENT); QCOMPARE(get_o2(cylinder->gasmix), 200); // check deep bailout used - cylinder = get_cylinder(&dive, get_cylinderid_at_time(&dive, &dive.dc, { 20 * 60 + 1 })); + cylinder = dive.get_cylinder(get_cylinderid_at_time(&dive, &dive.dcs[0], 20_min + 1_sec)); QCOMPARE(cylinder->cylinder_use, OC_GAS); QCOMPARE(get_o2(cylinder->gasmix), 190); // check shallow bailout used - cylinder = get_cylinder(&dive, get_cylinderid_at_time(&dive, &dive.dc, { 30 * 60 })); + cylinder = dive.get_cylinder(get_cylinderid_at_time(&dive, &dive.dcs[0], 30_min)); QCOMPARE(cylinder->cylinder_use, OC_GAS); QCOMPARE(get_o2(cylinder->gasmix), 530); // check expected run time of 51 minutes - QVERIFY(compareDecoTime(dive.dc.duration.seconds, 51 * 60, 51 * 60)); + QVERIFY(compareDecoTime(dive.dcs[0].duration.seconds, 51 * 60, 51 * 60)); } diff --git a/tests/testprofile.cpp b/tests/testprofile.cpp index f43646a84..11fa39c45 100644 --- a/tests/testprofile.cpp +++ b/tests/testprofile.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include "testprofile.h" #include "core/device.h" +#include "core/dive.h" #include "core/divelog.h" #include "core/divesite.h" #include "core/trip.h" @@ -24,7 +25,7 @@ void TestProfile::init() QTextCodec::setCodecForLocale(QTextCodec::codecForMib(106)); // first, setup the preferences - copy_prefs(&default_prefs, &prefs); + prefs = default_prefs; QCoreApplication::setOrganizationName("Subsurface"); QCoreApplication::setOrganizationDomain("subsurface.hohndel.org"); @@ -34,7 +35,6 @@ void TestProfile::testProfileExport() { prefs.planner_deco_mode = BUEHLMANN; parse_file(SUBSURFACE_TEST_DATA "/dives/abitofeverything.ssrf", &divelog); - sort_dive_table(divelog.dives); save_profiledata("exportprofile.csv", false); QFile org(SUBSURFACE_TEST_DATA "/dives/exportprofilereference.csv"); QCOMPARE(org.open(QFile::ReadOnly), true); @@ -51,7 +51,6 @@ void TestProfile::testProfileExportVPMB() { prefs.planner_deco_mode = VPMB; parse_file(SUBSURFACE_TEST_DATA "/dives/abitofeverything.ssrf", &divelog); - sort_dive_table(divelog.dives); save_profiledata("exportprofileVPMB.csv", false); QFile org(SUBSURFACE_TEST_DATA "/dives/exportprofilereferenceVPMB.csv"); QCOMPARE(org.open(QFile::ReadOnly), true); diff --git a/tests/testqPrefCloudStorage.cpp b/tests/testqPrefCloudStorage.cpp index 8f6cebfa1..7d7fb65b7 100644 --- a/tests/testqPrefCloudStorage.cpp +++ b/tests/testqPrefCloudStorage.cpp @@ -22,21 +22,21 @@ void TestQPrefCloudStorage::test_struct_get() auto tst = qPrefCloudStorage::instance(); prefs.cloud_auto_sync = true; - prefs.cloud_base_url = copy_qstring("new url"); - prefs.cloud_storage_email = copy_qstring("myEmail"); - prefs.cloud_storage_email_encoded = copy_qstring("encodedMyEMail"); - prefs.cloud_storage_password = copy_qstring("more secret"); - prefs.cloud_storage_pin = copy_qstring("a pin"); + prefs.cloud_base_url = "new url"; + prefs.cloud_storage_email = "myEmail"; + prefs.cloud_storage_email_encoded = "encodedMyEMail"; + prefs.cloud_storage_password = "more secret"; + prefs.cloud_storage_pin = "a pin"; prefs.cloud_timeout = 117; prefs.cloud_verification_status = qPrefCloudStorage::CS_NOCLOUD; prefs.save_password_local = true; QCOMPARE(tst->cloud_auto_sync(), prefs.cloud_auto_sync); - QCOMPARE(tst->cloud_base_url(), QString(prefs.cloud_base_url)); - QCOMPARE(tst->cloud_storage_email(), QString(prefs.cloud_storage_email)); - QCOMPARE(tst->cloud_storage_email_encoded(), QString(prefs.cloud_storage_email_encoded)); - QCOMPARE(tst->cloud_storage_password(), QString(prefs.cloud_storage_password)); - QCOMPARE(tst->cloud_storage_pin(), QString(prefs.cloud_storage_pin)); + QCOMPARE(tst->cloud_base_url(), QString::fromStdString(prefs.cloud_base_url)); + QCOMPARE(tst->cloud_storage_email(), QString::fromStdString(prefs.cloud_storage_email)); + QCOMPARE(tst->cloud_storage_email_encoded(), QString::fromStdString(prefs.cloud_storage_email_encoded)); + QCOMPARE(tst->cloud_storage_password(), QString::fromStdString(prefs.cloud_storage_password)); + QCOMPARE(tst->cloud_storage_pin(), QString::fromStdString(prefs.cloud_storage_pin)); QCOMPARE(tst->cloud_timeout(), (int)prefs.cloud_timeout); QCOMPARE(tst->cloud_verification_status(), (int)prefs.cloud_verification_status); QCOMPARE(tst->save_password_local(), prefs.save_password_local); @@ -59,11 +59,11 @@ void TestQPrefCloudStorage::test_set_struct() tst->set_save_password_local(false); QCOMPARE(prefs.cloud_auto_sync, false); - QCOMPARE(QString(prefs.cloud_base_url), QString("t2 base")); - QCOMPARE(QString(prefs.cloud_storage_email), QString("t2 email")); - QCOMPARE(QString(prefs.cloud_storage_email_encoded), QString("t2 email2")); - QCOMPARE(QString(prefs.cloud_storage_password), QString("t2 pass2")); - QCOMPARE(QString(prefs.cloud_storage_pin), QString("t2 pin")); + QCOMPARE(QString::fromStdString(prefs.cloud_base_url), QString("t2 base")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_email), QString("t2 email")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_email_encoded), QString("t2 email2")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_password), QString("t2 pass2")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_pin), QString("t2 pin")); QCOMPARE((int)prefs.cloud_timeout, 123); QCOMPARE((int)prefs.cloud_verification_status, (int)qPrefCloudStorage::CS_VERIFIED); QCOMPARE(prefs.save_password_local, false); @@ -87,22 +87,22 @@ void TestQPrefCloudStorage::test_set_load_struct() tst->set_cloud_verification_status(qPrefCloudStorage::CS_NOCLOUD); prefs.cloud_auto_sync = false; - prefs.cloud_base_url = copy_qstring("error1"); - prefs.cloud_storage_email = copy_qstring("error1"); - prefs.cloud_storage_email_encoded = copy_qstring("error1"); - prefs.cloud_storage_password = copy_qstring("error1"); - prefs.cloud_storage_pin = copy_qstring("error1"); + prefs.cloud_base_url = "error1"; + prefs.cloud_storage_email = "error1"; + prefs.cloud_storage_email_encoded = "error1"; + prefs.cloud_storage_password = "error1"; + prefs.cloud_storage_pin = "error1"; prefs.cloud_timeout = 324; prefs.cloud_verification_status = qPrefCloudStorage::CS_VERIFIED; prefs.save_password_local = false; tst->load(); QCOMPARE(prefs.cloud_auto_sync, true); - QCOMPARE(QString(prefs.cloud_base_url), QString("t3 base")); - QCOMPARE(QString(prefs.cloud_storage_email), QString("t3 email")); - QCOMPARE(QString(prefs.cloud_storage_email_encoded), QString("t3 email2")); - QCOMPARE(QString(prefs.cloud_storage_password), QString("t3 pass2")); - QCOMPARE(QString(prefs.cloud_storage_pin), QString("t3 pin")); + QCOMPARE(QString::fromStdString(prefs.cloud_base_url), QString("t3 base")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_email), QString("t3 email")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_email_encoded), QString("t3 email2")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_password), QString("t3 pass2")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_pin), QString("t3 pin")); QCOMPARE((int)prefs.cloud_timeout, 321); QCOMPARE((int)prefs.cloud_verification_status, (int)qPrefCloudStorage::CS_NOCLOUD); QCOMPARE(prefs.save_password_local, true); @@ -114,25 +114,25 @@ void TestQPrefCloudStorage::test_struct_disk() auto tst = qPrefCloudStorage::instance(); - prefs.cloud_base_url = copy_qstring("t4 base"); + prefs.cloud_base_url = "t4 base"; tst->store_cloud_base_url("t4 base"); // the base URL is no longer automatically saved to disk - prefs.cloud_storage_email = copy_qstring("t4 email"); - prefs.cloud_storage_email_encoded = copy_qstring("t4 email2"); + prefs.cloud_storage_email =("t4 email"); + prefs.cloud_storage_email_encoded = "t4 email2"; prefs.save_password_local = true; prefs.cloud_auto_sync = true; - prefs.cloud_storage_password = copy_qstring("t4 pass2"); - prefs.cloud_storage_pin = copy_qstring("t4 pin"); + prefs.cloud_storage_password = "t4 pass2"; + prefs.cloud_storage_pin = "t4 pin"; prefs.cloud_timeout = 123; prefs.cloud_verification_status = qPrefCloudStorage::CS_VERIFIED; tst->sync(); prefs.cloud_auto_sync = false; - prefs.cloud_base_url = copy_qstring("error1"); - prefs.cloud_storage_email = copy_qstring("error1"); - prefs.cloud_storage_email_encoded = copy_qstring("error1"); - prefs.cloud_storage_password = copy_qstring("error1"); - prefs.cloud_storage_pin = copy_qstring("error1"); + prefs.cloud_base_url = "error1"; + prefs.cloud_storage_email = "error1"; + prefs.cloud_storage_email_encoded = "error1"; + prefs.cloud_storage_password = "error1"; + prefs.cloud_storage_pin = "error1"; prefs.cloud_timeout = 324; prefs.cloud_verification_status = qPrefCloudStorage::CS_VERIFIED; prefs.save_password_local = false; @@ -140,11 +140,11 @@ void TestQPrefCloudStorage::test_struct_disk() tst->load(); QCOMPARE(prefs.cloud_auto_sync, true); - QCOMPARE(QString(prefs.cloud_base_url), QString("t4 base")); - QCOMPARE(QString(prefs.cloud_storage_email), QString("t4 email")); - QCOMPARE(QString(prefs.cloud_storage_email_encoded), QString("t4 email2")); - QCOMPARE(QString(prefs.cloud_storage_password), QString("t4 pass2")); - QCOMPARE(QString(prefs.cloud_storage_pin), QString("t4 pin")); + QCOMPARE(QString::fromStdString(prefs.cloud_base_url), QString("t4 base")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_email), QString("t4 email")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_email_encoded), QString("t4 email2")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_password), QString("t4 pass2")); + QCOMPARE(QString::fromStdString(prefs.cloud_storage_pin), QString("t4 pin")); QCOMPARE((int)prefs.cloud_timeout, 123); QCOMPARE((int)prefs.cloud_verification_status, (int)qPrefCloudStorage::CS_VERIFIED); QCOMPARE(prefs.save_password_local, true); diff --git a/tests/testqPrefDisplay.cpp b/tests/testqPrefDisplay.cpp index 52e827cbd..227935a3d 100644 --- a/tests/testqPrefDisplay.cpp +++ b/tests/testqPrefDisplay.cpp @@ -24,13 +24,13 @@ void TestQPrefDisplay::test_struct_get() prefs.animation_speed = 17; prefs.display_invalid_dives = true; - prefs.divelist_font = copy_qstring("comic"); + prefs.divelist_font = "comic"; prefs.font_size = 12.0; prefs.show_developer = false; QCOMPARE(display->animation_speed(), prefs.animation_speed); QCOMPARE(display->display_invalid_dives(), prefs.display_invalid_dives); - QCOMPARE(display->divelist_font(), QString(prefs.divelist_font)); + QCOMPARE(display->divelist_font(), QString::fromStdString(prefs.divelist_font)); QCOMPARE(display->font_size(), prefs.font_size); QCOMPARE(display->show_developer(), prefs.show_developer); } @@ -98,7 +98,7 @@ void TestQPrefDisplay::test_set_load_struct() prefs.animation_speed = 17; prefs.display_invalid_dives = false; - prefs.divelist_font = copy_qstring("doNotCareAtAll"); + prefs.divelist_font = "doNotCareAtAll"; prefs.font_size = 12.0; prefs.show_developer = false; @@ -131,14 +131,14 @@ void TestQPrefDisplay::test_struct_disk() prefs.animation_speed = 27; prefs.display_invalid_dives = true; - prefs.divelist_font = copy_qstring("doNotCareAtAll"); + prefs.divelist_font = "doNotCareAtAll"; prefs.font_size = 17.0; prefs.show_developer = false; display->sync(); prefs.animation_speed = 35; prefs.display_invalid_dives = false; - prefs.divelist_font = copy_qstring("noString"); + prefs.divelist_font = "noString"; prefs.font_size = 11.0; prefs.show_developer = true; @@ -156,7 +156,7 @@ void TestQPrefDisplay::test_struct_disk() void TestQPrefDisplay::test_multiple() { // test multiple instances have the same information - prefs.divelist_font = copy_qstring("comic"); + prefs.divelist_font = "comic"; auto display = qPrefDisplay::instance(); prefs.font_size = 15.0; diff --git a/tests/testqPrefDiveComputer.cpp b/tests/testqPrefDiveComputer.cpp index 36967bea6..26f019aad 100644 --- a/tests/testqPrefDiveComputer.cpp +++ b/tests/testqPrefDiveComputer.cpp @@ -21,15 +21,15 @@ void TestQPrefDiveComputer::test_struct_get() auto tst = qPrefDiveComputer::instance(); - prefs.dive_computer.device = copy_qstring("my device"); - prefs.dive_computer.device_name = copy_qstring("my device name"); - prefs.dive_computer.product = copy_qstring("my product"); - prefs.dive_computer.vendor = copy_qstring("my vendor"); + prefs.dive_computer.device = "my device"; + prefs.dive_computer.device_name = "my device name"; + prefs.dive_computer.product = "my product"; + prefs.dive_computer.vendor = "my vendor"; - QCOMPARE(tst->device(), QString(prefs.dive_computer.device)); - QCOMPARE(tst->device_name(), QString(prefs.dive_computer.device_name)); - QCOMPARE(tst->product(), QString(prefs.dive_computer.product)); - QCOMPARE(tst->vendor(), QString(prefs.dive_computer.vendor)); + QCOMPARE(tst->device(), QString::fromStdString(prefs.dive_computer.device)); + QCOMPARE(tst->device_name(), QString::fromStdString(prefs.dive_computer.device_name)); + QCOMPARE(tst->product(), QString::fromStdString(prefs.dive_computer.product)); + QCOMPARE(tst->vendor(), QString::fromStdString(prefs.dive_computer.vendor)); } void TestQPrefDiveComputer::test_set_struct() @@ -43,10 +43,10 @@ void TestQPrefDiveComputer::test_set_struct() tst->set_product("t2 product"); tst->set_vendor("t2 vendor"); - QCOMPARE(QString(prefs.dive_computer.device), QString("t2 device")); - QCOMPARE(QString(prefs.dive_computer.device_name), QString("t2 device name")); - QCOMPARE(QString(prefs.dive_computer.product), QString("t2 product")); - QCOMPARE(QString(prefs.dive_computer.vendor), QString("t2 vendor")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.device), QString("t2 device")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.device_name), QString("t2 device name")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.product), QString("t2 product")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.vendor), QString("t2 vendor")); } void TestQPrefDiveComputer::test_set_load_struct() @@ -60,16 +60,16 @@ void TestQPrefDiveComputer::test_set_load_struct() tst->set_product("t3 product"); tst->set_vendor("t3 vendor"); - prefs.dive_computer.device = copy_qstring("error1"); - prefs.dive_computer.device_name = copy_qstring("error2"); - prefs.dive_computer.product = copy_qstring("error3"); - prefs.dive_computer.vendor = copy_qstring("error4"); + prefs.dive_computer.device = "error1"; + prefs.dive_computer.device_name = "error2"; + prefs.dive_computer.product = "error3"; + prefs.dive_computer.vendor = "error4"; tst->load(); - QCOMPARE(QString(prefs.dive_computer.device), QString("t3 device")); - QCOMPARE(QString(prefs.dive_computer.device_name), QString("t3 device name")); - QCOMPARE(QString(prefs.dive_computer.product), QString("t3 product")); - QCOMPARE(QString(prefs.dive_computer.vendor), QString("t3 vendor")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.device), QString("t3 device")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.device_name), QString("t3 device name")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.product), QString("t3 product")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.vendor), QString("t3 vendor")); } void TestQPrefDiveComputer::test_struct_disk() @@ -78,24 +78,24 @@ void TestQPrefDiveComputer::test_struct_disk() auto tst = qPrefDiveComputer::instance(); - prefs.dive_computer.device = copy_qstring("t4 device"); - prefs.dive_computer.device_name = copy_qstring("t4 device name"); - prefs.dive_computer.product = copy_qstring("t4 product"); - prefs.dive_computer.vendor = copy_qstring("t4 vendor"); + prefs.dive_computer.device = "t4 device"; + prefs.dive_computer.device_name = "t4 device name"; + prefs.dive_computer.product = "t4 product"; + prefs.dive_computer.vendor = "t4 vendor"; tst->sync(); - prefs.dive_computer.device = copy_qstring("error"); - prefs.dive_computer.device_name = copy_qstring("error"); - prefs.dive_computer.product = copy_qstring("error"); - prefs.dive_computer.vendor = copy_qstring("error"); + prefs.dive_computer.device = "error"; + prefs.dive_computer.device_name = "error"; + prefs.dive_computer.product = "error"; + prefs.dive_computer.vendor = "error"; tst->load(); - QCOMPARE(QString(prefs.dive_computer.device), QString("t4 device")); - QCOMPARE(QString(prefs.dive_computer.device_name), QString("t4 device name")); - QCOMPARE(QString(prefs.dive_computer.product), QString("t4 product")); - QCOMPARE(QString(prefs.dive_computer.vendor), QString("t4 vendor")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.device), QString("t4 device")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.device_name), QString("t4 device name")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.product), QString("t4 product")); + QCOMPARE(QString::fromStdString(prefs.dive_computer.vendor), QString("t4 vendor")); } void TestQPrefDiveComputer::test_multiple() @@ -103,7 +103,7 @@ void TestQPrefDiveComputer::test_multiple() // test multiple instances have the same information auto tst = qPrefDiveComputer::instance(); - prefs.dive_computer.device = copy_qstring("mine"); + prefs.dive_computer.device = "mine"; QCOMPARE(tst->device(), qPrefDiveComputer::device()); QCOMPARE(tst->device(), QString("mine")); diff --git a/tests/testqPrefEquipment.cpp b/tests/testqPrefEquipment.cpp index 55d3fa576..f164e0875 100644 --- a/tests/testqPrefEquipment.cpp +++ b/tests/testqPrefEquipment.cpp @@ -22,8 +22,8 @@ void TestQPrefEquipment::test_struct_get() // Test struct pref -> get func. auto tst = qPrefEquipment::instance(); - prefs.default_cylinder = copy_qstring("new base11"); - QCOMPARE(tst->default_cylinder(), QString(prefs.default_cylinder)); + prefs.default_cylinder = "new base11"; + QCOMPARE(tst->default_cylinder(), QString::fromStdString(prefs.default_cylinder)); prefs.include_unused_tanks = true; QCOMPARE(tst->include_unused_tanks(), prefs.include_unused_tanks); } @@ -34,7 +34,7 @@ void TestQPrefEquipment::test_set_struct() auto tst = qPrefEquipment::instance(); tst->set_default_cylinder("new base21"); - QCOMPARE(QString(prefs.default_cylinder), QString("new base21")); + QCOMPARE(QString::fromStdString(prefs.default_cylinder), QString("new base21")); tst->set_include_unused_tanks(false); QCOMPARE(prefs.include_unused_tanks, false); } @@ -46,11 +46,11 @@ void TestQPrefEquipment::test_set_load_struct() auto tst = qPrefEquipment::instance(); tst->set_default_cylinder("new base31"); - prefs.default_cylinder = copy_qstring("error"); + prefs.default_cylinder = "error"; tst->set_include_unused_tanks(false); prefs.include_unused_tanks = true; tst->load(); - QCOMPARE(QString(prefs.default_cylinder), QString("new base31")); + QCOMPARE(QString::fromStdString(prefs.default_cylinder), QString("new base31")); QCOMPARE(prefs.include_unused_tanks, false); } @@ -59,15 +59,15 @@ void TestQPrefEquipment::test_struct_disk() // test struct prefs -> disk auto tst = qPrefEquipment::instance(); - prefs.default_cylinder = copy_qstring("base41"); + prefs.default_cylinder = "base41"; prefs.include_unused_tanks = true; tst->sync(); - prefs.default_cylinder = copy_qstring("error"); + prefs.default_cylinder = "error"; prefs.include_unused_tanks = false; tst->load(); - QCOMPARE(QString(prefs.default_cylinder), QString("base41")); + QCOMPARE(QString::fromStdString(prefs.default_cylinder), QString("base41")); QCOMPARE(prefs.include_unused_tanks, true); } diff --git a/tests/testqPrefLanguage.cpp b/tests/testqPrefLanguage.cpp index 6a5ac1e92..2a50ca470 100644 --- a/tests/testqPrefLanguage.cpp +++ b/tests/testqPrefLanguage.cpp @@ -21,21 +21,21 @@ void TestQPrefLanguage::test_struct_get() auto tst = qPrefLanguage::instance(); - prefs.date_format = copy_qstring("new date format"); + prefs.date_format = "new date format"; prefs.date_format_override = true; - prefs.date_format_short = copy_qstring("new short format"); - prefs.locale.language = copy_qstring("new lang format"); - prefs.locale.lang_locale = copy_qstring("new loc lang format"); - prefs.time_format = copy_qstring("new time format"); + prefs.date_format_short = "new short format"; + prefs.locale.language = "new lang format"; + prefs.locale.lang_locale = "new loc lang format"; + prefs.time_format = "new time format"; prefs.time_format_override = true; prefs.locale.use_system_language = true; - QCOMPARE(tst->date_format(), QString(prefs.date_format)); + QCOMPARE(tst->date_format(), QString::fromStdString(prefs.date_format)); QCOMPARE(tst->date_format_override(), prefs.date_format_override); - QCOMPARE(tst->date_format_short(), QString(prefs.date_format_short)); - QCOMPARE(tst->language(), QString(prefs.locale.language)); - QCOMPARE(tst->lang_locale(), QString(prefs.locale.lang_locale)); - QCOMPARE(tst->time_format(), QString(prefs.time_format)); + QCOMPARE(tst->date_format_short(), QString::fromStdString(prefs.date_format_short)); + QCOMPARE(tst->language(), QString::fromStdString(prefs.locale.language)); + QCOMPARE(tst->lang_locale(), QString::fromStdString(prefs.locale.lang_locale)); + QCOMPARE(tst->time_format(), QString::fromStdString(prefs.time_format)); QCOMPARE(tst->time_format_override(), prefs.time_format_override); QCOMPARE(tst->use_system_language(), prefs.locale.use_system_language); } @@ -80,22 +80,22 @@ void TestQPrefLanguage::test_set_load_struct() tst->set_time_format_override(true); tst->set_use_system_language(true); - prefs.date_format = copy_qstring("error3"); + prefs.date_format = "error3"; prefs.date_format_override = false; - prefs.date_format_short = copy_qstring("error3"); - prefs.locale.language = copy_qstring("error3"); - prefs.locale.lang_locale = copy_qstring("error3"); - prefs.time_format = copy_qstring("error3"); + prefs.date_format_short = "error3"; + prefs.locale.language = "error3"; + prefs.locale.lang_locale = "error3"; + prefs.time_format = "error3"; prefs.time_format_override = false; prefs.locale.use_system_language = false; tst->load(); - QCOMPARE(QString(prefs.date_format), QString("new date3")); + QCOMPARE(QString::fromStdString(prefs.date_format), QString("new date3")); QCOMPARE(prefs.date_format_override, true); - QCOMPARE(QString(prefs.date_format_short), QString("new short3")); - QCOMPARE(QString(prefs.locale.language), QString("new lang format3")); - QCOMPARE(QString(prefs.locale.lang_locale), QString("new loc lang3")); - QCOMPARE(QString(prefs.time_format), QString("new time3")); + QCOMPARE(QString::fromStdString(prefs.date_format_short), QString("new short3")); + QCOMPARE(QString::fromStdString(prefs.locale.language), QString("new lang format3")); + QCOMPARE(QString::fromStdString(prefs.locale.lang_locale), QString("new loc lang3")); + QCOMPARE(QString::fromStdString(prefs.time_format), QString("new time3")); QCOMPARE(prefs.time_format_override, true); QCOMPARE(prefs.locale.use_system_language, true); } @@ -106,32 +106,32 @@ void TestQPrefLanguage::test_struct_disk() auto tst = qPrefLanguage::instance(); - prefs.date_format = copy_qstring("new date format"); + prefs.date_format = "new date format"; prefs.date_format_override = true; - prefs.date_format_short = copy_qstring("new short format"); - prefs.locale.language = copy_qstring("new lang format"); - prefs.locale.lang_locale = copy_qstring("new loc lang format"); - prefs.time_format = copy_qstring("new time format"); + prefs.date_format_short = "new short format"; + prefs.locale.language = "new lang format"; + prefs.locale.lang_locale = "new loc lang format"; + prefs.time_format = "new time format"; prefs.time_format_override = true; prefs.locale.use_system_language = true; tst->sync(); - prefs.date_format = copy_qstring("error3"); + prefs.date_format = "error3"; prefs.date_format_override = false; - prefs.date_format_short = copy_qstring("error3"); - prefs.locale.language = copy_qstring("error3"); - prefs.locale.lang_locale = copy_qstring("error3"); - prefs.time_format = copy_qstring("error3"); + prefs.date_format_short = "error3"; + prefs.locale.language = "error3"; + prefs.locale.lang_locale = "error3"; + prefs.time_format = "error3"; prefs.time_format_override = false; prefs.locale.use_system_language = false; tst->load(); - QCOMPARE(QString(prefs.date_format), QString("new date format")); + QCOMPARE(QString::fromStdString(prefs.date_format), QString("new date format")); QCOMPARE(prefs.date_format_override, true); - QCOMPARE(QString(prefs.date_format_short), QString("new short format")); - QCOMPARE(QString(prefs.locale.language), QString("new lang format")); - QCOMPARE(QString(prefs.locale.lang_locale), QString("new loc lang format")); - QCOMPARE(QString(prefs.time_format), QString("new time format")); + QCOMPARE(QString::fromStdString(prefs.date_format_short), QString("new short format")); + QCOMPARE(QString::fromStdString(prefs.locale.language), QString("new lang format")); + QCOMPARE(QString::fromStdString(prefs.locale.lang_locale), QString("new loc lang format")); + QCOMPARE(QString::fromStdString(prefs.time_format), QString("new time format")); QCOMPARE(prefs.time_format_override, true); QCOMPARE(prefs.locale.use_system_language, true); } diff --git a/tests/testqPrefLog.cpp b/tests/testqPrefLog.cpp index 0abd703be..8f80e749d 100644 --- a/tests/testqPrefLog.cpp +++ b/tests/testqPrefLog.cpp @@ -23,13 +23,13 @@ void TestQPrefLog::test_struct_get() auto tst = qPrefLog::instance(); - prefs.default_filename = copy_qstring("new base12"); + prefs.default_filename = "new base12"; prefs.default_file_behavior = UNDEFINED_DEFAULT_FILE; prefs.use_default_file = true; prefs.show_average_depth = true; prefs.extraEnvironmentalDefault = true; - QCOMPARE(tst->default_filename(), QString(prefs.default_filename)); + QCOMPARE(tst->default_filename(), QString::fromStdString(prefs.default_filename)); QCOMPARE(tst->default_file_behavior(), prefs.default_file_behavior); QCOMPARE(tst->use_default_file(), prefs.use_default_file); QCOMPARE(tst->show_average_depth(), prefs.show_average_depth); @@ -48,7 +48,7 @@ void TestQPrefLog::test_set_struct() tst->set_show_average_depth(false); tst->set_extraEnvironmentalDefault(false); - QCOMPARE(QString(prefs.default_filename), QString("new base22")); + QCOMPARE(QString::fromStdString(prefs.default_filename), QString("new base22")); QCOMPARE(prefs.default_file_behavior, LOCAL_DEFAULT_FILE); QCOMPARE(prefs.use_default_file, false); QCOMPARE(prefs.show_average_depth, false); @@ -67,14 +67,14 @@ void TestQPrefLog::test_set_load_struct() tst->set_show_average_depth(true); tst->set_extraEnvironmentalDefault(true); - prefs.default_filename = copy_qstring("error"); + prefs.default_filename = "error"; prefs.default_file_behavior = UNDEFINED_DEFAULT_FILE; prefs.use_default_file = false; prefs.show_average_depth = false; prefs.extraEnvironmentalDefault = false; tst->load(); - QCOMPARE(QString(prefs.default_filename), QString("new base32")); + QCOMPARE(QString::fromStdString(prefs.default_filename), QString("new base32")); QCOMPARE(prefs.default_file_behavior, NO_DEFAULT_FILE); QCOMPARE(prefs.use_default_file, true); QCOMPARE(prefs.show_average_depth, true); @@ -87,21 +87,21 @@ void TestQPrefLog::test_struct_disk() auto tst = qPrefLog::instance(); - prefs.default_filename = copy_qstring("base42"); + prefs.default_filename = "base42"; prefs.default_file_behavior = CLOUD_DEFAULT_FILE; prefs.use_default_file = true; prefs.show_average_depth = true; prefs.extraEnvironmentalDefault = true; tst->sync(); - prefs.default_filename = copy_qstring("error"); + prefs.default_filename = "error"; prefs.default_file_behavior = UNDEFINED_DEFAULT_FILE; prefs.use_default_file = false; prefs.show_average_depth = false; prefs.extraEnvironmentalDefault = false; tst->load(); - QCOMPARE(QString(prefs.default_filename), QString("base42")); + QCOMPARE(QString::fromStdString(prefs.default_filename), QString("base42")); QCOMPARE(prefs.default_file_behavior, CLOUD_DEFAULT_FILE); QCOMPARE(prefs.use_default_file, true); QCOMPARE(prefs.show_average_depth, true); diff --git a/tests/testqPrefMedia.cpp b/tests/testqPrefMedia.cpp index f0c864408..6ffb5bea2 100644 --- a/tests/testqPrefMedia.cpp +++ b/tests/testqPrefMedia.cpp @@ -26,12 +26,12 @@ void TestQPrefMedia::test_struct_get() prefs.auto_recalculate_thumbnails = true; prefs.extract_video_thumbnails = true; prefs.extract_video_thumbnails_position = 15; - prefs.ffmpeg_executable = copy_qstring("new base16"); + prefs.ffmpeg_executable = "new base16"; QCOMPARE(tst->auto_recalculate_thumbnails(), prefs.auto_recalculate_thumbnails); QCOMPARE(tst->extract_video_thumbnails(), prefs.extract_video_thumbnails); QCOMPARE(tst->extract_video_thumbnails_position(), prefs.extract_video_thumbnails_position); - QCOMPARE(tst->ffmpeg_executable(), QString(prefs.ffmpeg_executable)); + QCOMPARE(tst->ffmpeg_executable(), QString::fromStdString(prefs.ffmpeg_executable)); } void TestQPrefMedia::test_set_struct() @@ -48,7 +48,7 @@ void TestQPrefMedia::test_set_struct() QCOMPARE(prefs.auto_recalculate_thumbnails, false); QCOMPARE(prefs.extract_video_thumbnails, false); QCOMPARE(prefs.extract_video_thumbnails_position, 25); - QCOMPARE(QString(prefs.ffmpeg_executable), QString("new base26")); + QCOMPARE(QString::fromStdString(prefs.ffmpeg_executable), QString("new base26")); } void TestQPrefMedia::test_set_load_struct() @@ -65,13 +65,13 @@ void TestQPrefMedia::test_set_load_struct() prefs.auto_recalculate_thumbnails = false; prefs.extract_video_thumbnails = false; prefs.extract_video_thumbnails_position = 15; - prefs.ffmpeg_executable = copy_qstring("error"); + prefs.ffmpeg_executable = "error"; tst->load(); QCOMPARE(prefs.auto_recalculate_thumbnails, true); QCOMPARE(prefs.extract_video_thumbnails, true); QCOMPARE(prefs.extract_video_thumbnails_position, 35); - QCOMPARE(QString(prefs.ffmpeg_executable), QString("new base36")); + QCOMPARE(QString::fromStdString(prefs.ffmpeg_executable), QString("new base36")); } void TestQPrefMedia::test_struct_disk() @@ -83,19 +83,19 @@ void TestQPrefMedia::test_struct_disk() prefs.auto_recalculate_thumbnails = true; prefs.extract_video_thumbnails = true; prefs.extract_video_thumbnails_position = 45; - prefs.ffmpeg_executable = copy_qstring("base46"); + prefs.ffmpeg_executable = "base46"; tst->sync(); prefs.auto_recalculate_thumbnails = false; prefs.extract_video_thumbnails = false; prefs.extract_video_thumbnails_position = 15; - prefs.ffmpeg_executable = copy_qstring("error"); + prefs.ffmpeg_executable = "error"; tst->load(); QCOMPARE(prefs.auto_recalculate_thumbnails, true); QCOMPARE(prefs.extract_video_thumbnails, true); QCOMPARE(prefs.extract_video_thumbnails_position, 45); - QCOMPARE(QString(prefs.ffmpeg_executable), QString("base46")); + QCOMPARE(QString::fromStdString(prefs.ffmpeg_executable), QString("base46")); } #define TEST(METHOD, VALUE) \ diff --git a/tests/testqPrefProxy.cpp b/tests/testqPrefProxy.cpp index 196a8c6d3..9d14b52ef 100644 --- a/tests/testqPrefProxy.cpp +++ b/tests/testqPrefProxy.cpp @@ -22,18 +22,18 @@ void TestQPrefProxy::test_struct_get() auto tst = qPrefProxy::instance(); prefs.proxy_auth = true; - prefs.proxy_host = copy_qstring("t1 host"); - prefs.proxy_pass = copy_qstring("t1 pass"); + prefs.proxy_host = "t1 host"; + prefs.proxy_pass = "t1 pass"; prefs.proxy_port = 512; prefs.proxy_type = 3; - prefs.proxy_user = copy_qstring("t1 user"); + prefs.proxy_user = "t1 user"; QCOMPARE(tst->proxy_auth(), true); - QCOMPARE(tst->proxy_host(), QString(prefs.proxy_host)); - QCOMPARE(tst->proxy_pass(), QString(prefs.proxy_pass)); + QCOMPARE(tst->proxy_host(), QString::fromStdString(prefs.proxy_host)); + QCOMPARE(tst->proxy_pass(), QString::fromStdString(prefs.proxy_pass)); QCOMPARE(tst->proxy_port(), 512); QCOMPARE(tst->proxy_type(), 3); - QCOMPARE(tst->proxy_user(), QString(prefs.proxy_user)); + QCOMPARE(tst->proxy_user(), QString::fromStdString(prefs.proxy_user)); } void TestQPrefProxy::test_set_struct() @@ -50,11 +50,11 @@ void TestQPrefProxy::test_set_struct() tst->set_proxy_user("t2 user"); QCOMPARE(prefs.proxy_auth, false); - QCOMPARE(QString(prefs.proxy_host), QString("t2 host")); - QCOMPARE(QString(prefs.proxy_pass), QString("t2 pass")); + QCOMPARE(QString::fromStdString(prefs.proxy_host), QString("t2 host")); + QCOMPARE(QString::fromStdString(prefs.proxy_pass), QString("t2 pass")); QCOMPARE(prefs.proxy_port, 524); QCOMPARE(prefs.proxy_type, 2); - QCOMPARE(QString(prefs.proxy_user), QString("t2 user")); + QCOMPARE(QString::fromStdString(prefs.proxy_user), QString("t2 user")); } void TestQPrefProxy::test_set_load_struct() @@ -72,19 +72,19 @@ void TestQPrefProxy::test_set_load_struct() tst->sync(); prefs.proxy_auth = false; - prefs.proxy_host = copy_qstring("error1"); - prefs.proxy_pass = copy_qstring("error1"); + prefs.proxy_host = "error1"; + prefs.proxy_pass = "error1"; prefs.proxy_port = 128; prefs.proxy_type = 0; - prefs.proxy_user = copy_qstring("error1"); + prefs.proxy_user = "error1"; tst->load(); QCOMPARE(prefs.proxy_auth, true); - QCOMPARE(QString(prefs.proxy_host), QString("t3 host")); - QCOMPARE(QString(prefs.proxy_pass), QString("t3 pass")); + QCOMPARE(QString::fromStdString(prefs.proxy_host), QString("t3 host")); + QCOMPARE(QString::fromStdString(prefs.proxy_pass), QString("t3 pass")); QCOMPARE(prefs.proxy_port, 532); QCOMPARE(prefs.proxy_type, 1); - QCOMPARE(QString(prefs.proxy_user), QString("t3 user")); + QCOMPARE(QString::fromStdString(prefs.proxy_user), QString("t3 user")); } void TestQPrefProxy::test_struct_disk() @@ -94,27 +94,27 @@ void TestQPrefProxy::test_struct_disk() auto tst = qPrefProxy::instance(); prefs.proxy_auth = false; - prefs.proxy_host = copy_qstring("t4 host"); - prefs.proxy_pass = copy_qstring("t4 pass"); + prefs.proxy_host = "t4 host"; + prefs.proxy_pass = "t4 pass"; prefs.proxy_port = 544; prefs.proxy_type = 4; - prefs.proxy_user = copy_qstring("t4 user"); + prefs.proxy_user = "t4 user"; tst->sync(); prefs.proxy_auth = true; - prefs.proxy_host = copy_qstring("error1"); - prefs.proxy_pass = copy_qstring("error1"); + prefs.proxy_host = "error1"; + prefs.proxy_pass = "error1"; prefs.proxy_port = 128; prefs.proxy_type = 5; - prefs.proxy_user = copy_qstring("error1"); + prefs.proxy_user = "error1"; tst->load(); QCOMPARE(prefs.proxy_auth, false); - QCOMPARE(QString(prefs.proxy_host), QString("t4 host")); - QCOMPARE(QString(prefs.proxy_pass), QString("t4 pass")); + QCOMPARE(QString::fromStdString(prefs.proxy_host), QString("t4 host")); + QCOMPARE(QString::fromStdString(prefs.proxy_pass), QString("t4 pass")); QCOMPARE(prefs.proxy_port, 544); QCOMPARE(prefs.proxy_type, 4); - QCOMPARE(QString(prefs.proxy_user), QString("t4 user")); + QCOMPARE(QString::fromStdString(prefs.proxy_user), QString("t4 user")); } void TestQPrefProxy::test_multiple() diff --git a/tests/testqPrefUpdateManager.cpp b/tests/testqPrefUpdateManager.cpp index dae0de555..d330d5e68 100644 --- a/tests/testqPrefUpdateManager.cpp +++ b/tests/testqPrefUpdateManager.cpp @@ -22,7 +22,7 @@ void TestQPrefUpdateManager::test_struct_get() auto tst = qPrefUpdateManager::instance(); prefs.update_manager.dont_check_for_updates = true; - prefs.update_manager.last_version_used = copy_qstring("last_version"); + prefs.update_manager.last_version_used = "last_version"; prefs.update_manager.next_check = QDate::fromString("11/09/1957", "dd/MM/yyyy").toJulianDay(); QCOMPARE(tst->dont_check_for_updates(), true); @@ -42,7 +42,7 @@ void TestQPrefUpdateManager::test_set_struct() tst->set_uuidString("uuid"); QCOMPARE(prefs.update_manager.dont_check_for_updates, false); - QCOMPARE(QString(prefs.update_manager.last_version_used), QString("last_version2")); + QCOMPARE(QString::fromStdString(prefs.update_manager.last_version_used), QString("last_version2")); QCOMPARE(QDate::fromJulianDay(prefs.update_manager.next_check), QDate::fromString("11/09/1957", "dd/MM/yyyy")); QCOMPARE(tst->uuidString(), QString("uuid")); } @@ -63,12 +63,12 @@ void TestQPrefUpdateManager::test_set_load_struct() tst->set_uuidString("uuid2"); prefs.update_manager.dont_check_for_updates = true; - prefs.update_manager.last_version_used = copy_qstring("last_version"); + prefs.update_manager.last_version_used = "last_version"; prefs.update_manager.next_check = 1000; tst->load(); QCOMPARE(prefs.update_manager.dont_check_for_updates, false); - QCOMPARE(QString(prefs.update_manager.last_version_used), QString("last_version2")); + QCOMPARE(QString::fromStdString(prefs.update_manager.last_version_used), QString("last_version2")); QCOMPARE(QDate::fromJulianDay(prefs.update_manager.next_check), QDate::fromString("11/09/1957", "dd/MM/yyyy")); QCOMPARE(tst->uuidString(), QString("uuid2")); } @@ -80,12 +80,12 @@ void TestQPrefUpdateManager::test_struct_disk() auto tst = qPrefUpdateManager::instance(); prefs.update_manager.dont_check_for_updates = true; - prefs.update_manager.last_version_used = copy_qstring("last_version"); + prefs.update_manager.last_version_used = "last_version"; prefs.update_manager.next_check = QDate::fromString("11/09/1957", "dd/MM/yyyy").toJulianDay(); tst->sync(); prefs.update_manager.dont_check_for_updates = false; - prefs.update_manager.last_version_used = copy_qstring(""); + prefs.update_manager.last_version_used.clear(); prefs.update_manager.next_check = 1000; tst->load(); diff --git a/tests/testrenumber.cpp b/tests/testrenumber.cpp index dfe8f5e18..bd355e98c 100644 --- a/tests/testrenumber.cpp +++ b/tests/testrenumber.cpp @@ -11,29 +11,26 @@ void TestRenumber::setup() { - prefs.cloud_base_url = strdup(default_prefs.cloud_base_url); + prefs.cloud_base_url = default_prefs.cloud_base_url; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47.xml", &divelog), 0); - process_loaded_dives(); + divelog.process_loaded_dives(); } void TestRenumber::testMerge() { struct divelog log; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47b.xml", &log), 0); - add_imported_dives(&log, IMPORT_MERGE_ALL_TRIPS); - QCOMPARE(divelog.dives->nr, 1); + divelog.add_imported_dives(log, import_flags::merge_all_trips); + QCOMPARE(divelog.dives.size(), 1); } void TestRenumber::testMergeAndAppend() { struct divelog log; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47c.xml", &log), 0); - add_imported_dives(&log, IMPORT_MERGE_ALL_TRIPS); - QCOMPARE(divelog.dives->nr, 2); - struct dive *d = get_dive(1); - QVERIFY(d != NULL); - if (d) - QCOMPARE(d->number, 2); + divelog.add_imported_dives(log, import_flags::merge_all_trips); + QCOMPARE(divelog.dives.size(), 2); + QCOMPARE(divelog.dives[1]->number, 2); } QTEST_GUILESS_MAIN(TestRenumber) diff --git a/tests/testtaglist.cpp b/tests/testtaglist.cpp index dabcf351c..b2926853c 100644 --- a/tests/testtaglist.cpp +++ b/tests/testtaglist.cpp @@ -15,29 +15,29 @@ void TestTagList::cleanupTestCase() void TestTagList::testGetTagstringNoTags() { - struct tag_entry *tag_list = NULL; - std::string tagstring = taglist_get_tagstring(tag_list); + tag_list tags; + std::string tagstring = taglist_get_tagstring(tags); QVERIFY(tagstring.empty()); } void TestTagList::testGetTagstringSingleTag() { - struct tag_entry *tag_list = NULL; - taglist_add_tag(&tag_list, "A new tag"); - std::string tagstring = taglist_get_tagstring(tag_list); + tag_list tags; + taglist_add_tag(tags, "A new tag"); + std::string tagstring = taglist_get_tagstring(tags); QCOMPARE(QString::fromStdString(tagstring), QString::fromUtf8("A new tag")); } void TestTagList::testGetTagstringMultipleTags() { - struct tag_entry *tag_list = NULL; - taglist_add_tag(&tag_list, "A new tag"); - taglist_add_tag(&tag_list, "A new tag 1"); - taglist_add_tag(&tag_list, "A new tag 2"); - taglist_add_tag(&tag_list, "A new tag 3"); - taglist_add_tag(&tag_list, "A new tag 4"); - taglist_add_tag(&tag_list, "A new tag 5"); - std::string tagstring = taglist_get_tagstring(tag_list); + tag_list tags; + taglist_add_tag(tags, "A new tag"); + taglist_add_tag(tags, "A new tag 1"); + taglist_add_tag(tags, "A new tag 2"); + taglist_add_tag(tags, "A new tag 3"); + taglist_add_tag(tags, "A new tag 4"); + taglist_add_tag(tags, "A new tag 5"); + std::string tagstring = taglist_get_tagstring(tags); QCOMPARE(QString::fromStdString(tagstring), QString::fromUtf8( "A new tag, " @@ -50,11 +50,11 @@ void TestTagList::testGetTagstringMultipleTags() void TestTagList::testGetTagstringWithAnEmptyTag() { - struct tag_entry *tag_list = NULL; - taglist_add_tag(&tag_list, "A new tag"); - taglist_add_tag(&tag_list, "A new tag 1"); - taglist_add_tag(&tag_list, ""); - std::string tagstring = taglist_get_tagstring(tag_list); + tag_list tags; + taglist_add_tag(tags, "A new tag"); + taglist_add_tag(tags, "A new tag 1"); + taglist_add_tag(tags, ""); + std::string tagstring = taglist_get_tagstring(tags); QCOMPARE(QString::fromStdString(tagstring), QString::fromUtf8( "A new tag, " @@ -63,11 +63,40 @@ void TestTagList::testGetTagstringWithAnEmptyTag() void TestTagList::testGetTagstringEmptyTagOnly() { - struct tag_entry *tag_list = NULL; - taglist_add_tag(&tag_list, ""); - std::string tagstring = taglist_get_tagstring(tag_list); + tag_list tags; + taglist_add_tag(tags, ""); + std::string tagstring = taglist_get_tagstring(tags); QCOMPARE(QString::fromStdString(tagstring), QString::fromUtf8("")); } +void TestTagList::testMergeTags() +{ + tag_list tags1, tags2; + taglist_add_tag(tags1, "A new tag"); + taglist_add_tag(tags1, "A new tag 6"); + taglist_add_tag(tags1, "A new tag 1"); + taglist_add_tag(tags1, "A new tag 2"); + taglist_add_tag(tags1, ""); + taglist_add_tag(tags1, "A new tag 2"); + taglist_add_tag(tags1, "A new tag 3"); + taglist_add_tag(tags1, "A new tag"); + taglist_add_tag(tags2, ""); + taglist_add_tag(tags2, "A new tag 1"); + taglist_add_tag(tags2, "A new tag 4"); + taglist_add_tag(tags2, "A new tag 2"); + taglist_add_tag(tags2, "A new tag 5"); + tag_list tags3 = taglist_merge(tags1, tags2); + std::string tagstring = taglist_get_tagstring(tags3); + QCOMPARE(QString::fromStdString(tagstring), + QString::fromUtf8( + "A new tag, " + "A new tag 1, " + "A new tag 2, " + "A new tag 3, " + "A new tag 4, " + "A new tag 5, " + "A new tag 6")); +} + QTEST_GUILESS_MAIN(TestTagList) diff --git a/tests/testtaglist.h b/tests/testtaglist.h index 7e6e94742..4e6ea2f20 100644 --- a/tests/testtaglist.h +++ b/tests/testtaglist.h @@ -15,6 +15,7 @@ private slots: void testGetTagstringMultipleTags(); void testGetTagstringWithAnEmptyTag(); void testGetTagstringEmptyTagOnly(); + void testMergeTags(); }; #endif diff --git a/tests/testunitconversion.cpp b/tests/testunitconversion.cpp index 2a4d10dd3..dbc574374 100644 --- a/tests/testunitconversion.cpp +++ b/tests/testunitconversion.cpp @@ -11,14 +11,13 @@ void TestUnitConversion::testUnitConversions() QCOMPARE(nearly_equal(cuft_to_l(1), 28.316847), true); QCOMPARE(nearly_equal(mm_to_feet(1000), 3.280840), true); QCOMPARE(feet_to_mm(1), 305L); - QCOMPARE(to_feet((depth_t){1000}), 3); QCOMPARE(nearly_equal(mkelvin_to_C(647000), 373.85), true); QCOMPARE(nearly_equal(mkelvin_to_F(647000), 704.93), true); QCOMPARE(F_to_mkelvin(704.93), 647000UL); QCOMPARE(C_to_mkelvin(373.85), 647000UL); QCOMPARE(nearly_equal(psi_to_bar(14.6959488), 1.01325), true); QCOMPARE(psi_to_mbar(14.6959488), 1013L); - QCOMPARE(nearly_equal(to_PSI((pressure_t){1013}), 14.6923228594), true); + QCOMPARE(nearly_equal(to_PSI(1_atm), 14.6923228594), true); QCOMPARE(nearly_equal(bar_to_atm(1.013), 1.0), true); QCOMPARE(nearly_equal(mbar_to_atm(1013), 1.0), true); QCOMPARE(nearly_equal(mbar_to_PSI(1013), 14.6923228594), true); diff --git a/tests/tst_qPrefTechnicalDetails.qml b/tests/tst_qPrefTechnicalDetails.qml index 5c8ac4f64..365f73ad4 100644 --- a/tests/tst_qPrefTechnicalDetails.qml +++ b/tests/tst_qPrefTechnicalDetails.qml @@ -113,6 +113,11 @@ TestCase { var x27 = PrefTechnicalDetails.zoomed_plot PrefTechnicalDetails.zoomed_plot = true compare(PrefTechnicalDetails.zoomed_plot, true) + + var x28 = PrefTechnicalDetails.allowOcGasAsDiluent + PrefTechnicalDetails.allowOcGasAsDiluent = true + compare(PrefTechnicalDetails.allowOcGasAsDiluent, true) + } Item { @@ -204,6 +209,7 @@ TestCase { PrefTechnicalDetails.tankbar = ! PrefTechnicalDetails.tankbar PrefTechnicalDetails.vpmb_conservatism = -127 PrefTechnicalDetails.zoomed_plot = ! PrefTechnicalDetails.zoomed_plot + PrefTechnicalDetails.allowOcGasAsDiluent = ! PrefTechnicalDetails.allowOcGasAsDiluent compare(spyCatcher.spy1, true) compare(spyCatcher.spy2, true)